diff --git a/doc/artwork/scenedata-dod.svg b/doc/artwork/scenedata-dod.svg new file mode 100644 index 0000000000..182e608aef --- /dev/null +++ b/doc/artwork/scenedata-dod.svg @@ -0,0 +1,1610 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + materials + + + + skins + + + + meshes + + + + transforms + + + + parents + + + + lights + + + + cameras + + + + 7 + + + + 1 + + + + 0 + + + + 5 + + + + 3 + + + + 6 + + + + 2 + + + + 4 + + + + 1 + + + + 0 + + + + 5 + + + + 6 + + + + 2 + + + + 1 + + + + 1 + + + + 5 + + + + 0 + + + + 4 + + + + 5 + + + + 6 + + + + 3 + + + + 2 + + + + 5 + + + + 6 + + + + 3 + + + + 2 + + + + 2 + + + + 6 + + + + 6 + + + diff --git a/doc/artwork/scenedata-tree.svg b/doc/artwork/scenedata-tree.svg new file mode 100644 index 0000000000..db891c0fa0 --- /dev/null +++ b/doc/artwork/scenedata-tree.svg @@ -0,0 +1,1290 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + material + + + + mesh + + + + skin + + + + material + + + + mesh + + + + light + + + + material + + + + mesh + + + + skin + + + + light + + + + camera + + + + light + + + + transform + + + + transform + + + + transform + + + + transform + + + + + transform + + + + + transform + + + + mesh + + + + transform + + + + transform + + + + material + + + + 3 + + + + 5 + + + + 7 + + + + 0 + + + + 6 + + + + 2 + + + + 1 + + + + 4 + + + + material #2 + + + + mesh #2 + + + diff --git a/doc/changelog.dox b/doc/changelog.dox index 00c587c04e..26ca1c203f 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -219,6 +219,9 @@ See also: @ref Trade::PhongMaterialData::normalTextureScale() and @ref Trade::PhongMaterialData::normalTextureSwizzle() to make new features added for PBR materials recognizable also in classic Phong workflows. +- A completely redesigned @ref Trade::SceneData class that stores data of + the whole scene in a data-oriented way, allowing for storing custom fields + as well. See [mosra/magnum#525](https://github.com/mosra/magnum/pull/525). - New @ref Trade::SkinData class and @ref Trade::AbstractImporter::skin2D() / @ref Trade::AbstractImporter::skin3D() family of APIs for skin import, as well as support in @ref Trade::AnySceneImporter "AnySceneImporter" @@ -629,6 +632,27 @@ See also: directly but rather generated on-the-fly from attribute data, which makes them less efficient than calling @ref Trade::MaterialData::hasAttribute() etc. +- @ref Trade::SceneData constructor taking a @ref std::vector of 2D and 3D + children is deprecated in favor of the new scene representation. Use + @ref Trade::SceneData::SceneData(SceneMappingType, UnsignedLong, Containers::Array&&, Containers::Array&&, const void*) + instead. +- @cpp Trade::SceneData::children2D() @ce and + @cpp Trade::SceneData::children3D() @ce are deprecated in favor of the + new scene representation. Use @ref Trade::SceneData::childrenFor() with + @cpp -1 @ce passed as the @p object argument to get a list of top-level + objects. +- @cpp Trade::ObjectData*D @ce, @cpp Trade::MeshObjectData*D @ce classes, + @cpp Trade::ObjectInstanceType*D @ce, @cpp Trade::ObjectFlag*D @ce, + @cpp Trade::ObjectFlags*D @ce enums and the corresponding + @cpp Trade::AbstractImporter::object*DCount() @ce, + @cpp Trade::AbstractImporter::object*DForName() @ce, + @cpp Trade::AbstractImporter::object*DName() @ce and + @cpp Trade::AbstractImporter::object*D() @ce accessor APIs are deprecated + in favor of the unified representation in @ref Trade::SceneData and the + @ref Trade::AbstractImporter::objectCount(), + @relativeref{Trade::AbstractImporter,objectForName()} and + @relativeref{Trade::AbstractImporter,objectName()} accessors that are + shared for 2D and 3D. - @ref Trade::AbstractImporter::material() now returns @ref Corrade::Containers::Optional instead of a @ref Corrade::Containers::Pointer, as the new @ref Trade::MaterialData class isn't polymorphic anymore. If @@ -802,6 +826,23 @@ See also: @cpp Trade::AbstractMaterialData @ce aliases to, doesn't have a @cpp virtual @ce destructor as subclasses with extra data members aren't a desired use case anymore. +- @ref Trade::SceneData constructor taking a @ref std::vector of 2D and 3D + children that got deprecated in favor of + @ref Trade::SceneData::SceneData(SceneMappingType, UnsignedLong, Containers::Array&&, Containers::Array&&, const void*) + no longer accepts a scene that has both 2D and 3D children. +- The deprecated @cpp Trade::AbstractImporter::object*DCount() @ce, + @cpp Trade::AbstractImporter::object*DForName() @ce, + @cpp Trade::AbstractImporter::object*DName() @ce and + @cpp Trade::AbstractImporter::object*D() @ce accessors now behave different + for objects with multiple mesh assignments. This handling was originally + present in importer plugins themselves, but as the new + @ref Trade::SceneData representation supports multiple mesh/camera/... + assignments to a single object natively, the handling was moved to a single + place in the compatibility layer. Because the compatibility layer cannot + renumber object IDs, the newly added objects are not immediately following + the original ID but instead allocated at the end of the object ID range + reported by the importer. While the newly added objects have different IDs, + they retain the parent name like before. - @ref Trade::AbstractImporter::doDefaultScene() is now @cpp const @ce --- since the function is not expected to fail, no kind of complex lazy population can be done anyway. This is now consistent with `do*Count()` diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index e4a1fb3b17..75ac34c709 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -24,8 +24,11 @@ */ #include +#include #include +#include #include +#include #include #include @@ -47,12 +50,15 @@ #include "Magnum/Trade/LightData.h" #include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" -#include "Magnum/Trade/ObjectData2D.h" -#include "Magnum/Trade/MeshObjectData3D.h" #include "Magnum/Trade/PbrClearCoatMaterialData.h" #include "Magnum/Trade/PbrSpecularGlossinessMaterialData.h" #include "Magnum/Trade/PbrMetallicRoughnessMaterialData.h" #include "Magnum/Trade/PhongMaterialData.h" +#include "Magnum/Trade/SceneData.h" +#include "Magnum/SceneGraph/Drawable.h" +#include "Magnum/SceneGraph/Scene.h" +#include "Magnum/SceneGraph/Object.h" +#include "Magnum/SceneGraph/MatrixTransformation3D.h" #ifdef MAGNUM_TARGET_GL #include "Magnum/GL/Texture.h" #include "Magnum/GL/TextureFormat.h" @@ -66,12 +72,16 @@ #ifdef MAGNUM_BUILD_DEPRECATED #define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ +#define _MAGNUM_NO_DEPRECATED_OBJECTDATA /* So it doesn't yell here */ #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshObjectData3D.h" +#include "Magnum/Trade/ObjectData2D.h" #endif #define DOXYGEN_ELLIPSIS(...) __VA_ARGS__ +#define DOXYGEN_IGNORE(...) __VA_ARGS__ using namespace Magnum; using namespace Magnum::Math::Literals; @@ -180,21 +190,6 @@ importer->openFile("scene.gltf"); // memory-maps all files } #endif -{ -Containers::Pointer importer; -Int materialIndex; -/* [AbstractImporter-usage-cast] */ -Containers::Pointer data = importer->object3D(12); -if(data && data->instanceType() == Trade::ObjectInstanceType3D::Mesh) { - auto& mesh = static_cast(*data); - - materialIndex = mesh.material(); - // ... -} -/* [AbstractImporter-usage-cast] */ -static_cast(materialIndex); -} - { Containers::Pointer importer; /* [AbstractImporter-setFileCallback] */ @@ -844,9 +839,9 @@ MeshTools::transformPointsInPlace(transformation, data.positions(0)); /* [MeshData2D-transform] */ CORRADE_IGNORE_DEPRECATED_POP } -#endif { +CORRADE_IGNORE_DEPRECATED_PUSH Trade::ObjectData2D& baz(); Trade::ObjectData2D& data = baz(); /* [ObjectData2D-transformation] */ @@ -855,9 +850,9 @@ Matrix3 transformation = Matrix3::scaling(data.scaling()); /* [ObjectData2D-transformation] */ static_cast(transformation); +CORRADE_IGNORE_DEPRECATED_POP } -#ifdef MAGNUM_BUILD_DEPRECATED { CORRADE_IGNORE_DEPRECATED_PUSH Trade::MeshData3D& bar(); @@ -871,9 +866,9 @@ MeshTools::transformVectorsInPlace(transformation, data.normals(0)); /* [MeshData3D-transform] */ CORRADE_IGNORE_DEPRECATED_POP } -#endif { +CORRADE_IGNORE_DEPRECATED_PUSH Trade::ObjectData3D& fizz(); Trade::ObjectData3D& data = fizz(); /* [ObjectData3D-transformation] */ @@ -882,6 +877,228 @@ Matrix4 transformation = Matrix4::scaling(data.scaling()); /* [ObjectData3D-transformation] */ static_cast(transformation); +CORRADE_IGNORE_DEPRECATED_POP +} +#endif + +{ +/* [SceneFieldData-usage] */ +Containers::StridedArrayView1D transformationMapping = DOXYGEN_ELLIPSIS({}); +Containers::StridedArrayView1D transformations = DOXYGEN_ELLIPSIS({}); + +Trade::SceneFieldData field{Trade::SceneField::Transformation, + transformationMapping, transformations}; +/* [SceneFieldData-usage] */ +} + +{ +/* [SceneFieldData-usage-offset-only] */ +struct Node { + UnsignedInt object; + Int parent; + Matrix4 transform; +}; + +/* Layout defined statically, 120 objects in total */ +constexpr Trade::SceneFieldData parents{Trade::SceneField::Parent, 120, + Trade::SceneMappingType::UnsignedInt, offsetof(Node, object), sizeof(Node), + Trade::SceneFieldType::Int, offsetof(Node, parent), sizeof(Node)}; +constexpr Trade::SceneFieldData transforms{Trade::SceneField::Transformation, 120, + Trade::SceneMappingType::UnsignedInt, offsetof(Node, object), sizeof(Node), + Trade::SceneFieldType::Matrix4x4, offsetof(Node, transform), sizeof(Node)}; + +/* Actual data populated later */ +Containers::Array data{120*sizeof(Node)}; +DOXYGEN_ELLIPSIS() +Trade::SceneData{Trade::SceneMappingType::UnsignedInt, 120, std::move(data), + {parents, transforms}}; +/* [SceneFieldData-usage-offset-only] */ +} + +{ +typedef SceneGraph::Scene Scene3D; +typedef SceneGraph::Object Object3D; +/* [SceneData-usage1] */ +Trade::SceneData data = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, nullptr}); +if(!data.is3D() || + !data.hasField(Trade::SceneField::Parent) || + !data.hasField(Trade::SceneField::Mesh)) + Fatal{} << "Oh noes!"; + +Scene3D scene; +Containers::Array objects{std::size_t(data.mappingBound())}; +/* [SceneData-usage1] */ + +/* [SceneData-usage2] */ +auto parents = data.parentsAsArray(); +for(Containers::Pair& parent: parents) + objects[parent.first()] = new Object3D{}; +/* [SceneData-usage2] */ + +/* [SceneData-usage3] */ +for(Containers::Pair& parent: parents) + objects[parent.first()]->setParent( + parent.second() == -1 ? &scene : objects[parent.second()] + ); +/* [SceneData-usage3] */ + +/* [SceneData-usage4] */ +for(Containers::Pair& transformation: + data.transformations3DAsArray()) +{ + if(Object3D* const object = objects[transformation.first()]) + object->setTransformation(transformation.second()); +} +/* [SceneData-usage4] */ + +/* [SceneData-usage5] */ +class Drawable: public SceneGraph::Drawable3D { + public: + explicit Drawable(Object3D& object, UnsignedInt mesh, Int material, DOXYGEN_ELLIPSIS(int))DOXYGEN_IGNORE(: SceneGraph::Drawable3D{object} { + static_cast(mesh); + static_cast(material); + } int foo); + + DOXYGEN_ELLIPSIS(void draw(const Matrix4&, SceneGraph::Camera3D&) override {}) +}; + +for(const Containers::Pair>& + meshMaterial: data.meshesMaterialsAsArray()) +{ + if(Object3D* const object = objects[meshMaterial.first()]) + new Drawable{*object, meshMaterial.second().first(), + meshMaterial.second().second(), DOXYGEN_ELLIPSIS(0)}; +} +/* [SceneData-usage5] */ + +/* [SceneData-usage-advanced] */ +Containers::StridedArrayView1D transformationMapping = + data.mapping(Trade::SceneField::Transformation); +Containers::StridedArrayView1D transformations = + data.field(Trade::SceneField::Transformation); +for(std::size_t i = 0; i != transformationMapping.size(); ++i) { + if(Object3D* const object = objects[transformationMapping[i]]) + object->setTransformation(transformations[i]); +} +/* [SceneData-usage-advanced] */ +} + +{ +Trade::SceneData data{{}, 0, nullptr, nullptr}; +/* [SceneData-per-object] */ +Containers::Pointer importer = DOXYGEN_ELLIPSIS({}); + +for(const Containers::Pair& meshMaterial: + data.meshesMaterialsFor(importer->objectForName("Chair"))) +{ + Debug{} << "Mesh:" << importer->meshName(meshMaterial.first()); + if(meshMaterial.second() != -1) + Debug{} << "With a material:" << importer->materialName(meshMaterial.second()); +} +/* [SceneData-per-object] */ +} + +{ +Trade::SceneData data{{}, 0, nullptr, nullptr}; +typedef SceneGraph::Object Object3D; +Containers::Array objects; +/* [SceneData-usage-mutable] */ +Containers::StridedArrayView1D transformationMapping = + data.mapping(Trade::SceneField::Transformation); +Containers::StridedArrayView1D mutableTransformations = + data.mutableField(Trade::SceneField::Transformation); +for(std::size_t i = 0; i != transformationMapping.size(); ++i) { + if(Object3D* const object = objects[transformationMapping[i]]) + mutableTransformations[i] = object->transformation(); +} +/* [SceneData-usage-mutable] */ +} + +{ +const std::size_t nodeCount{}, meshAssignmentCount{}; +/* [SceneData-populating] */ +struct Common { + UnsignedShort object; + Short parent; + Matrix4 transformation; +}; + +Containers::StridedArrayView1D common; +Containers::ArrayView meshMaterialMapping; +Containers::ArrayView meshes; +Containers::ArrayView meshMaterials; +Containers::Array data = Containers::ArrayTuple{ + {nodeCount, common}, + {meshAssignmentCount, meshMaterialMapping}, + {meshAssignmentCount, meshes}, + {meshAssignmentCount, meshMaterials} +}; + +// populate the views ... + +Trade::SceneData scene{ + Trade::SceneMappingType::UnsignedShort, nodeCount, + std::move(data), { + Trade::SceneFieldData{Trade::SceneField::Parent, + common.slice(&Common::object), common.slice(&Common::parent)}, + Trade::SceneFieldData{Trade::SceneField::Transformation, + common.slice(&Common::object), common.slice(&Common::transformation)}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + meshMaterialMapping, meshes}, + Trade::SceneFieldData{Trade::SceneField::MeshMaterial, + meshMaterialMapping, meshMaterials} + }}; +/* [SceneData-populating] */ +} + +{ +std::size_t nodeCount{}; +/* [SceneData-populating-custom1] */ +DOXYGEN_ELLIPSIS() +Containers::ArrayView cellMapping; +Containers::ArrayView cellFrustums; +Containers::StridedArrayView2D cellLights; +Containers::Array data = Containers::ArrayTuple{ + DOXYGEN_ELLIPSIS() + {32*24, cellMapping}, + {32*24, cellFrustums}, + {{32*24, 8}, cellLights}, +}; + +for(std::size_t i = 0; i != cellMapping.size(); ++i) { + cellMapping[i] = nodeCount + i; + cellFrustums[i] = DOXYGEN_ELLIPSIS({}); + for(std::size_t j = 0; j != cellLights[i].size(); ++j) + cellLights[i][j] = DOXYGEN_ELLIPSIS({}); +} +/* [SceneData-populating-custom1] */ + +/* [SceneData-populating-custom2] */ +constexpr Trade::SceneField CellFrustum = Trade::sceneFieldCustom(0x00); +constexpr Trade::SceneField CellLights = Trade::sceneFieldCustom(0x01); + +Trade::SceneData scene{ + Trade::SceneMappingType::UnsignedShort, nodeCount + cellMapping.size(), + std::move(data), { + DOXYGEN_ELLIPSIS() + Trade::SceneFieldData{CellFrustum, cellMapping, cellFrustums}, + Trade::SceneFieldData{CellLights, cellMapping, cellLights}, + }}; +/* [SceneData-populating-custom2] */ +} + +{ +constexpr Trade::SceneField CellFrustum = Trade::sceneFieldCustom(0); +constexpr Trade::SceneField CellLights = Trade::sceneFieldCustom(1); +Trade::SceneData scene{{}, 0, nullptr, nullptr}; +/* [SceneData-populating-custom-retrieve] */ +Containers::StridedArrayView1D cellFrustums = + scene.field(CellFrustum); +Containers::StridedArrayView2D cellLights = + scene.field(CellLights); +/* [SceneData-populating-custom-retrieve] */ +static_cast(cellFrustums); +static_cast(cellLights); } } diff --git a/doc/snippets/README.md b/doc/snippets/README.md index d391a6da2b..64477e04d2 100644 --- a/doc/snippets/README.md +++ b/doc/snippets/README.md @@ -14,12 +14,13 @@ smaller file sizes: The output printed by the application can be used to update the example output in `doc/getting-started.dox`. -### triangulate.svg +### triangulate.svg, scenedata-tree.svg, scenedata-dod.svg Created by Inkscape from `doc/artwork/triangulate.svg` by saving as Optimized SVG and: -- cleaning up the `` header +- cleaning up the `` header (removing `version`, `xmlns`) - converting to a `style=""`, *keeping* `viewBox` - adding `class="m-image"` -- removing metadata and the background layer +- removing metadata, the background layer and all layers that have + `display: none` diff --git a/doc/snippets/scenedata-dod.svg b/doc/snippets/scenedata-dod.svg new file mode 100644 index 0000000000..826bd3b7f2 --- /dev/null +++ b/doc/snippets/scenedata-dod.svg @@ -0,0 +1,148 @@ + + + + + materials + + + + skins + + + + meshes + + + + transforms + + + + parents + + + + lights + + + + cameras + + + + 7 + + + + 1 + + + + 0 + + + + 5 + + + + 3 + + + + 6 + + + + 2 + + + + 4 + + + + 1 + + + + 0 + + + + 5 + + + + 6 + + + + 2 + + + + 1 + + + + 1 + + + + 5 + + + + 0 + + + + 4 + + + + 5 + + + + 6 + + + + 3 + + + + 2 + + + + 5 + + + + 6 + + + + 3 + + + + 2 + + + + 2 + + + + 6 + + + + 6 + + + diff --git a/doc/snippets/scenedata-tree.svg b/doc/snippets/scenedata-tree.svg new file mode 100644 index 0000000000..c64e2ac77f --- /dev/null +++ b/doc/snippets/scenedata-tree.svg @@ -0,0 +1,255 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + materials + + + + + textures + + + + meshes + + + + + transforms + + + + + parents + + + + + physics + + + + camera + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + material + + + + mesh + + + + skin + + + + material + + + + mesh + + + + light + + + + material + + + + mesh + + + + skin + + + + light + + + + camera + + + + light + + + + transform + + + + transform + + + + transform + + + + transform + + + + + transform + + + + + transform + + + + mesh + + + + transform + + + + transform + + + + material + + + + 3 + + + + 5 + + + + 7 + + + + 0 + + + + 6 + + + + 2 + + + + 1 + + + + 4 + + + + material #2 + + + + mesh #2 + + diff --git a/doc/vulkan-mapping.dox b/doc/vulkan-mapping.dox index 617d805c0a..4e0b52354b 100644 --- a/doc/vulkan-mapping.dox +++ b/doc/vulkan-mapping.dox @@ -429,7 +429,7 @@ Vulkan structure | Matching API @type_vk{ClearColorValue} | convertible from/to @ref Magnum::Vector3 "Vector3", @ref Magnum::Color3 "Color3", @ref Magnum::Vector4 "Vector4", @ref Magnum::Color4 "Color4", @ref Magnum::Vector4i "Vector4i", @ref Magnum::Vector4ui "Vector4ui" using @ref Magnum/Vk/Integration.h; only exposed through @ref RenderPassBeginInfo::clearColor() and @ref CommandBuffer::clearColorImage() overloads @type_vk{ClearDepthStencilValue} | only exposed through @ref RenderPassBeginInfo::clearDepthStencil(), @ref CommandBuffer::clearDepthStencilImage() and friends @type_vk{ClearValue} | only exposed through @ref RenderPassBeginInfo::clearColor() and @relativeref{RenderPassBeginInfo,clearDepthStencil()} overloads -@type_vk{ClearRect} | convertible from/to @ref Range3Di using @ref Magnum/Vk/Integration.h +@type_vk{ClearRect} | convertible from/to @relativeref{Magnum,Range3Di} using @ref Magnum/Vk/Integration.h @type_vk{CommandBufferAllocateInfo} | not exposed, internal to @ref CommandPool::allocate() @type_vk{CommandBufferBeginInfo} | @ref CommandBufferBeginInfo @type_vk{CommandBufferInheritanceInfo} | | @@ -692,7 +692,7 @@ Vulkan structure | Matching API @type_vk{RayTracingPipelineCreateInfoKHR} @m_class{m-label m-flat m-warning} **KHR** | | @type_vk{RayTracingPipelineInterfaceCreateInfoKHR} @m_class{m-label m-flat m-warning} **KHR** | | @type_vk{RayTracingShaderGroupCreateInfoKHR} @m_class{m-label m-flat m-warning} **KHR** | | -@type_vk{Rect2D} | convertible from/to @ref Range2Di using @ref Magnum/Vk/Integration.h +@type_vk{Rect2D} | convertible from/to @relativeref{Magnum,Range2Di} using @ref Magnum/Vk/Integration.h @type_vk{RenderPassBeginInfo} | @ref RenderPassBeginInfo @type_vk{RenderPassAttachmentBeginInfo} @m_class{m-label m-flat m-success} **KHR, 1.2** | | @type_vk{RenderPassCreateInfo}, \n @type_vk{RenderPassCreateInfo2} @m_class{m-label m-flat m-success} **KHR, 1.2** | @ref RenderPassCreateInfo @@ -755,7 +755,7 @@ Vulkan structure | Matching API @type_vk{VertexInputBindingDescription} | @ref MeshLayout @type_vk{VertexInputBindingDivisorDescriptionEXT} @m_class{m-label m-flat m-warning} **EXT** | @ref MeshLayout @type_vk{VertexInputAttributeDescription} | @ref MeshLayout -@type_vk{Viewport} | convertible from/to @ref Range3D using @ref Magnum/Vk/Integration.h +@type_vk{Viewport} | convertible from/to @relativeref{Magnum,Range3D} using @ref Magnum/Vk/Integration.h @subsection vulkan-mapping-structures-w W diff --git a/src/Magnum/MeshTools/sceneconverter.cpp b/src/Magnum/MeshTools/sceneconverter.cpp index 7f7d01db8f..d4c0bf07e1 100644 --- a/src/Magnum/MeshTools/sceneconverter.cpp +++ b/src/Magnum/MeshTools/sceneconverter.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -37,6 +38,7 @@ #include "Magnum/PixelFormat.h" #include "Magnum/Implementation/converterUtilities.h" #include "Magnum/Math/Color.h" +#include "Magnum/Math/Matrix4.h" #include "Magnum/Math/FunctionsBatch.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/Trade/AbstractImporter.h" @@ -44,7 +46,7 @@ #include "Magnum/Trade/LightData.h" #include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" -#include "Magnum/Trade/MeshObjectData3D.h" +#include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/SkinData.h" #include "Magnum/Trade/TextureData.h" #include "Magnum/Trade/AbstractSceneConverter.h" @@ -213,6 +215,7 @@ bool isInfoRequested(const Utility::Arguments& args) { args.isSet("info-lights") || args.isSet("info-materials") || args.isSet("info-meshes") || + args.isSet("info-scenes") || args.isSet("info-skins") || args.isSet("info-textures") || args.isSet("info"); @@ -242,6 +245,7 @@ int main(int argc, char** argv) { .addBooleanOption("info-lights").setHelp("info-lights", "print info about images in the input file and exit") .addBooleanOption("info-materials").setHelp("info-materials", "print info about materials in the input file and exit") .addBooleanOption("info-meshes").setHelp("info-meshes", "print info about meshes in the input file and exit") + .addBooleanOption("info-scenes").setHelp("info-scenes", "print info about textures in the input file and exit") .addBooleanOption("info-skins").setHelp("info-skins", "print info about skins in the input file and exit") .addBooleanOption("info-textures").setHelp("info-textures", "print info about textures in the input file and exit") .addBooleanOption("info").setHelp("info", "print info about everything in the input file and exit, same as specifying all other --info-* options together") @@ -372,30 +376,81 @@ used.)") std::string name; }; + struct SceneFieldInfo { + Trade::SceneField name; + std::string customName; + Trade::SceneFieldFlags flags; + Trade::SceneFieldType type; + UnsignedInt arraySize; + std::size_t size; + }; + + struct SceneInfo { + UnsignedInt scene; + Trade::SceneMappingType mappingType; + UnsignedLong mappingBound; + Containers::Array fields; + std::size_t dataSize; + std::string name; + /** @todo object names? */ + }; + /* Parse everything first to avoid errors interleaved with output */ - /* Scene properties. Currently just counting how much is each mesh / - light / material shared. Texture reference count is calculated when - parsing materials. */ + /* Scene properties, together with counting how much is each mesh / + light / material / skin shared. Texture reference count is + calculated when parsing materials. */ + Containers::Array sceneInfos; Containers::Array materialReferenceCount{importer->materialCount()}; Containers::Array lightReferenceCount{importer->lightCount()}; Containers::Array meshReferenceCount{importer->meshCount()}; Containers::Array skinReferenceCount{importer->skin3DCount()}; - for(UnsignedInt i = 0; i != importer->object3DCount(); ++i) { - Containers::Pointer object = importer->object3D(i); - if(!object) continue; - if(object->instanceType() == Trade::ObjectInstanceType3D::Mesh) { - auto& meshObject = static_cast(*object); - if(std::size_t(meshObject.instance()) < meshReferenceCount.size()) - ++meshReferenceCount[meshObject.instance()]; - if(std::size_t(meshObject.material()) < materialReferenceCount.size()) - ++materialReferenceCount[meshObject.material()]; - if(std::size_t(meshObject.skin()) < skinReferenceCount.size()) - ++skinReferenceCount[meshObject.skin()]; - } else if(object->instanceType() == Trade::ObjectInstanceType3D::Light) { - if(std::size_t(object->instance()) < lightReferenceCount.size()) - ++lightReferenceCount[object->instance()]; + if(args.isSet("info") || args.isSet("info-scenes") || args.isSet("info-materials") || args.isSet("info-lights") || args.isSet("info-meshes") || args.isSet("info-skins")) for(UnsignedInt i = 0; i != importer->sceneCount(); ++i) { + Containers::Optional scene = importer->scene(i); + if(!scene) continue; + + SceneInfo info{}; + info.scene = i; + info.mappingType = scene->mappingType(); + info.mappingBound = scene->mappingBound(); + info.dataSize = scene->data().size(); + info.name = importer->sceneName(i); + for(UnsignedInt j = 0; j != scene->fieldCount(); ++j) { + const Trade::SceneField name = scene->fieldName(j); + + if(name == Trade::SceneField::Mesh) for(const Containers::Pair>& meshMaterial: scene->meshesMaterialsAsArray()) { + if(meshMaterial.first() < meshReferenceCount.size()) + ++meshReferenceCount[meshMaterial.first()]; + if(UnsignedInt(meshMaterial.second().second()) < materialReferenceCount.size()) + ++materialReferenceCount[meshMaterial.second().second()]; + } + + if(name == Trade::SceneField::Skin) for(const Containers::Pair skin: scene->skinsAsArray()) { + if(skin.second() < skinReferenceCount.size()) + ++skinReferenceCount[skin.second()]; + /** @todo 2D/3D distinction */ + } + + if(name == Trade::SceneField::Light) for(const Containers::Pair& light: scene->lightsAsArray()) { + if(light.second() < lightReferenceCount.size()) + ++lightReferenceCount[light.second()]; + } + + arrayAppend(info.fields, InPlaceInit, + name, + Trade::isSceneFieldCustom(name) ? + importer->sceneFieldName(name) : "", + scene->fieldFlags(j), + scene->fieldType(j), + scene->fieldArraySize(j), + scene->fieldSize(j)); } + + /* Add it to the array only if scene info was requested. We're + going through this loop also if just light / material / mesh / + skin info is requested, to gather reference count */ + if(args.isSet("info") || args.isSet("info-scenes")) + arrayAppend(sceneInfos, std::move(info)); } /* Animation properties */ @@ -608,6 +663,29 @@ used.)") if(args.isSet("info") || args.isSet("info-images")) imageInfos = Trade::Implementation::imageInfo(*importer, error, compactImages); + for(const SceneInfo& info: sceneInfos) { + Debug d; + d << "Scene" << info.scene << Debug::nospace << ":"; + if(!info.name.empty()) d << info.name; + d << Debug::newline; + d << " bound:" << info.mappingBound << "objects," << info.mappingType + << "(" << Debug::nospace << Utility::formatString("{:.1f}", info.dataSize/1024.0f) << "kB)"; + + for(const SceneFieldInfo& field: info.fields) { + d << Debug::newline << " " << field.name; + if(Trade::isSceneFieldCustom(field.name)) { + d << "(" << Debug::nospace << field.customName + << Debug::nospace << ")"; + } + d << "@" << field.type; + if(field.arraySize) + d << Debug::nospace << Utility::formatString("[{}]", field.arraySize); + d << Debug::nospace << "," << field.size << "entries"; + if(field.flags) + d << Debug::newline << " " << field.flags; + } + } + for(const AnimationInfo& info: animationInfos) { Debug d; d << "Animation" << info.animation << Debug::nospace << ":"; @@ -641,7 +719,7 @@ used.)") d << "Skin" << info.skin; /* Print reference count only if there actually is a scene, otherwise this information is useless */ - if(importer->object3DCount()) + if(importer->objectCount()) d << Utility::formatString("(referenced by {} objects)", info.references); d << Debug::nospace << ":"; if(!info.name.empty()) d << info.name; @@ -654,7 +732,7 @@ used.)") d << "Light" << info.light; /* Print reference count only if there actually is a scene, otherwise this information is useless */ - if(importer->object3DCount()) + if(importer->objectCount()) d << Utility::formatString("(referenced by {} objects)", info.references); d << Debug::nospace << ":"; if(!info.name.empty()) d << info.name; @@ -673,7 +751,7 @@ used.)") d << "Material" << info.material; /* Print reference count only if there actually is a scene, otherwise this information is useless */ - if(importer->object3DCount()) + if(importer->objectCount()) d << Utility::formatString("(referenced by {} objects)", info.references); d << Debug::nospace << ":"; if(!info.name.empty()) d << info.name; @@ -756,7 +834,7 @@ used.)") d << "Mesh" << info.mesh; /* Print reference count only if there actually is a scene, otherwise this information is useless */ - if(importer->object3DCount()) + if(importer->objectCount()) d << Utility::formatString("(referenced by {} objects)", info.references); d << Debug::nospace << ":"; if(!info.name.empty()) d << info.name; diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index 3d7d95f7e5..530d267451 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -40,17 +40,23 @@ #include "Magnum/Trade/LightData.h" #include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" -#include "Magnum/Trade/ObjectData2D.h" -#include "Magnum/Trade/ObjectData3D.h" #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/SkinData.h" #include "Magnum/Trade/TextureData.h" #ifdef MAGNUM_BUILD_DEPRECATED +#include +#include + +#include "Magnum/Trade/Implementation/sceneTools.h" + #define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ +#define _MAGNUM_NO_DEPRECATED_OBJECTDATA /* So it doesn't yell here */ #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshObjectData2D.h" +#include "Magnum/Trade/MeshObjectData3D.h" #endif #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT @@ -62,7 +68,7 @@ namespace Magnum { namespace Trade { std::string AbstractImporter::pluginInterface() { return /* [interface] */ -"cz.mosra.magnum.Trade.AbstractImporter/0.3.4" +"cz.mosra.magnum.Trade.AbstractImporter/0.4" /* [interface] */ ; } @@ -95,6 +101,12 @@ AbstractImporter::AbstractImporter(PluginManager::Manager& man AbstractImporter::AbstractImporter(PluginManager::AbstractManager& manager, const std::string& plugin): PluginManager::AbstractManagingPlugin{manager, plugin} {} +#ifdef MAGNUM_BUILD_DEPRECATED +/* These twp needed because of the Array member */ +AbstractImporter::AbstractImporter(AbstractImporter&&) noexcept = default; +AbstractImporter::~AbstractImporter() = default; +#endif + void AbstractImporter::setFlags(ImporterFlags flags) { CORRADE_ASSERT(!isOpened(), "Trade::AbstractImporter::setFlags(): can't be set while a file is opened", ); @@ -268,6 +280,13 @@ UnsignedInt AbstractImporter::sceneCount() const { UnsignedInt AbstractImporter::doSceneCount() const { return 0; } +UnsignedLong AbstractImporter::objectCount() const { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::objectCount(): no file opened", {}); + return doObjectCount(); +} + +UnsignedLong AbstractImporter::doObjectCount() const { return 0; } + Int AbstractImporter::sceneForName(const std::string& name) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::sceneForName(): no file opened", -1); const Int id = doSceneForName(name); @@ -278,6 +297,16 @@ Int AbstractImporter::sceneForName(const std::string& name) { Int AbstractImporter::doSceneForName(const std::string&) { return -1; } +Long AbstractImporter::objectForName(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::objectForName(): no file opened", {}); + const Long id = doObjectForName(name); + CORRADE_ASSERT(id == -1 || UnsignedLong(id) < doObjectCount(), + "Trade::AbstractImporter::objectForName(): implementation-returned index" << id << "out of range for" << doObjectCount() << "entries", {}); + return id; +} + +Long AbstractImporter::doObjectForName(const std::string&) { return -1; } + std::string AbstractImporter::sceneName(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::sceneName(): no file opened", {}); CORRADE_ASSERT(id < doSceneCount(), "Trade::AbstractImporter::sceneName(): index" << id << "out of range for" << doSceneCount() << "entries", {}); @@ -286,10 +315,23 @@ std::string AbstractImporter::sceneName(const UnsignedInt id) { std::string AbstractImporter::doSceneName(UnsignedInt) { return {}; } +std::string AbstractImporter::objectName(const UnsignedLong id) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::objectName(): no file opened", {}); + CORRADE_ASSERT(id < doObjectCount(), "Trade::AbstractImporter::objectName(): index" << id << "out of range for" << doObjectCount() << "entries", {}); + return doObjectName(id); +} + +std::string AbstractImporter::doObjectName(UnsignedLong) { return {}; } + Containers::Optional AbstractImporter::scene(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::scene(): no file opened", {}); CORRADE_ASSERT(id < doSceneCount(), "Trade::AbstractImporter::scene(): index" << id << "out of range for" << doSceneCount() << "entries", {}); - return doScene(id); + Containers::Optional scene = doScene(id); + CORRADE_ASSERT(!scene || ( + (!scene->_data.deleter() || scene->_data.deleter() == Implementation::nonOwnedArrayDeleter) && + (!scene->_fields.deleter() || scene->_fields.deleter() == reinterpret_cast(Implementation::nonOwnedArrayDeleter))), + "Trade::AbstractImporter::scene(): implementation is not allowed to use a custom Array deleter", {}); + return scene; } Containers::Optional AbstractImporter::doScene(UnsignedInt) { @@ -306,6 +348,25 @@ Containers::Optional AbstractImporter::scene(const std::string& name) return scene(id); /* not doScene(), so we get the range checks also */ } +SceneField AbstractImporter::sceneFieldForName(const std::string& name) { + const SceneField out = doSceneFieldForName(name); + CORRADE_ASSERT(out == SceneField{} || isSceneFieldCustom(out), + "Trade::AbstractImporter::sceneFieldForName(): implementation-returned" << out << "is neither custom nor invalid", {}); + return out; +} + +SceneField AbstractImporter::doSceneFieldForName(const std::string&) { + return {}; +} + +std::string AbstractImporter::sceneFieldName(SceneField name) { + CORRADE_ASSERT(isSceneFieldCustom(name), + "Trade::AbstractImporter::sceneFieldName():" << name << "is not custom", {}); + return doSceneFieldName(sceneFieldCustom(name)); +} + +std::string AbstractImporter::doSceneFieldName(UnsignedInt) { return {}; } + UnsignedInt AbstractImporter::animationCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::animationCount(): no file opened", {}); return doAnimationCount(); @@ -446,39 +507,224 @@ Containers::Optional AbstractImporter::camera(const std::string& nam return camera(id); /* not doCamera(), so we get the range checks also */ } +#ifdef MAGNUM_BUILD_DEPRECATED +struct AbstractImporter::CachedScenes { + UnsignedInt object2DCount{}; + UnsignedInt object3DCount{}; + Containers::Array> scenes; +}; + +void AbstractImporter::populateCachedScenes() { + if(_cachedScenes) return; + + _cachedScenes.emplace(); + _cachedScenes->scenes = Containers::Array>{sceneCount()}; + + UnsignedLong newObjectOffset = objectCount(); + for(UnsignedInt i = 0; i != _cachedScenes->scenes.size(); ++i) { + _cachedScenes->scenes[i] = scene(i); + if(_cachedScenes->scenes[i]) { + /* Convert the scene so that each object has only either a mesh + (potentially with a material and a skin), a camera or a light. + The tool requires SceneField::Parent to be present, however if + it's not then we treat the scene as empty in the backwards + 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); + + /* Return the 2D/3D object count based on which scenes are 2D and + which not. The objectCount() provided by the importer is ignored + except for the above, also because it doesn't take into account + the restriction for unique-functioning objects. */ + if(_cachedScenes->scenes[i]->is2D()) + _cachedScenes->object2DCount = Math::max(_cachedScenes->object2DCount, UnsignedInt(_cachedScenes->scenes[i]->mappingBound())); + if(_cachedScenes->scenes[i]->is3D()) + _cachedScenes->object3DCount = Math::max(_cachedScenes->object3DCount, UnsignedInt(_cachedScenes->scenes[i]->mappingBound())); + + /* Ensure the newly added objects for each scene don't overlap each + other */ + newObjectOffset = Math::max(newObjectOffset, _cachedScenes->scenes[i]->mappingBound()); + } + } + + /* If there are scenes but no objects (because for example all scenes + failed to import), use the dimension-less object count at least, and + assume the scene was 3D. Otherwise this may cause unexpected assertions + in code that expected proper object count to be reported even if a scene + contains errors. + + Not ideal, especially regarding the 3D assumption, but better than + nothing. */ + if(!_cachedScenes->scenes.empty() && !_cachedScenes->object2DCount && !_cachedScenes->object3DCount) + _cachedScenes->object3DCount = objectCount(); +} + UnsignedInt AbstractImporter::object2DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object2DCount(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH return doObject2DCount(); + CORRADE_IGNORE_DEPRECATED_POP } -UnsignedInt AbstractImporter::doObject2DCount() const { return 0; } +UnsignedInt AbstractImporter::doObject2DCount() const { + /* I know, I know. A cleaner option would be to populate this during + openFile() / openData() but that would mean the backwards compatibility + overhead is there even if not using the deprecated APIs, which is way + worse than using this nasty hack in two places. */ + const_cast(*this).populateCachedScenes(); + return _cachedScenes->object2DCount; +} Int AbstractImporter::object2DForName(const std::string& name) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object2DForName(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH const Int id = doObject2DForName(name); CORRADE_ASSERT(id == -1 || UnsignedInt(id) < doObject2DCount(), "Trade::AbstractImporter::object2DForName(): implementation-returned index" << id << "out of range for" << doObject2DCount() << "entries", {}); return id; + CORRADE_IGNORE_DEPRECATED_POP } -Int AbstractImporter::doObject2DForName(const std::string&) { return -1; } +Int AbstractImporter::doObject2DForName(const std::string& name) { + /* Alias to the new interface. If it returns an ID that's larger than + reported 2D object count, then it's probably for a 3D object instead + -- ignore it in that case. Ideally this would be solved by checking if + the ID is actually present in a 2D scene (and same in doObject2DName()) + but that's a lot of extra code for just a backwards compatibility + feature that almost nobody needs. The only pre-existing 2D importer is + PrimitiveImporter, which is rarely used. */ + const Long id = doObjectForName(name); + CORRADE_IGNORE_DEPRECATED_PUSH + return id < doObject2DCount() ? id : -1; + CORRADE_IGNORE_DEPRECATED_POP +} std::string AbstractImporter::object2DName(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object2DName(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ASSERT(id < doObject2DCount(), "Trade::AbstractImporter::object2DName(): index" << id << "out of range for" << doObject2DCount() << "entries", {}); return doObject2DName(id); + CORRADE_IGNORE_DEPRECATED_POP } -std::string AbstractImporter::doObject2DName(UnsignedInt) { return {}; } +std::string AbstractImporter::doObject2DName(const UnsignedInt id) { + /* Alias to the new interface if the ID is known to the new interface, + return an empty string for objects that got newly added in order to make + them single-functioning */ + if(id < doObjectCount()) return doObjectName(id); + + populateCachedScenes(); + for(UnsignedInt i = 0; i != _cachedScenes->scenes.size(); ++i) { + if(!_cachedScenes->scenes[i] || + !_cachedScenes->scenes[i]->is2D() || + _cachedScenes->scenes[i]->mappingBound() <= id) + continue; + if(Containers::Optional parent = _cachedScenes->scenes[i]->parentFor(id)) + return doObjectName(*parent); + } + + return ""; +} + +CORRADE_IGNORE_DEPRECATED_PUSH Containers::Pointer AbstractImporter::object2D(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object2D(): no file opened", {}); CORRADE_ASSERT(id < doObject2DCount(), "Trade::AbstractImporter::object2D(): index" << id << "out of range for" << doObject2DCount() << "entries", {}); return doObject2D(id); } -Containers::Pointer AbstractImporter::doObject2D(UnsignedInt) { - CORRADE_ASSERT_UNREACHABLE("Trade::AbstractImporter::object2D(): not implemented", {}); +Containers::Pointer AbstractImporter::doObject2D(const UnsignedInt id) { + /* The code is mostly the same between doObject2D() and doObject3D(), + except that the 2D variant has no lights. Attempting to unify the common + code would be more complex than just having a slightly modified copy. */ + + populateCachedScenes(); + + /* Find the first 2D scene with this object, which we'll detect from the + mapping bound reported for the scene, whether it's 2D or 3D, and a + presence of a parent attribute. If a parent attribute is not present, it + means the object isn't a part of this scene, in which case we skip it. + It could also mean isn't a part of the hierarchy and is standalone + (skyboxes, scene-wide properties), which the legacy API had no way to + deal with anyway so ignoring those is fine. */ + std::size_t sceneCandidate = ~std::size_t{}; + for(std::size_t i = 0; i != _cachedScenes->scenes.size(); ++i) { + const Containers::Optional& scene = _cachedScenes->scenes[i]; + if(scene && scene->is2D() && id < scene->mappingBound() && scene->parentFor(id)) { + sceneCandidate = i; + break; + } + } + + if(sceneCandidate == ~std::size_t{}) { + Error{} << "Trade::AbstractImporter::object2D(): object" << id << "not found in any 2D scene hierarchy"; + return {}; + } + + const SceneData& scene = *_cachedScenes->scenes[sceneCandidate]; + + ObjectFlags2D flags; + Containers::Optional transformation = scene.transformation2DFor(id); + Containers::Optional> trs = scene.translationRotationScaling2DFor(id); + /* If the object has neither a TRS nor a transformation field, assign an + empty TRS transform. Not a matrix, because a TRS is more flexible and + thus more desired. */ + if(!transformation && !trs) + trs.emplace(Vector2{}, Complex{}, Vector2{1.0f}); + if(trs) + flags |= ObjectFlag2D::HasTranslationRotationScaling; + + std::vector children; /* not const so we can move it */ + { + Containers::Array childrenArray = scene.childrenFor(id); + children = {childrenArray.begin(), childrenArray.end()}; + } + const Containers::Array> mesh = scene.meshesMaterialsFor(id); + const Containers::Array camera = scene.camerasFor(id); + const Containers::Array skin = scene.skinsFor(id); + const Containers::Optional importerState = scene.importerStateFor(id); + + /* All these should have at most 1 item as the SceneData got processed to + have each object contain either just one mesh or one camera (materials + are implicitly shared with a mesh, skins also). Thus it doesn't matter + in which order we decide on the legacy object type. */ + CORRADE_INTERNAL_ASSERT(camera.size() + mesh.size() <= 1); + + if(!mesh.empty()) { + return Containers::pointer(flags & ObjectFlag2D::HasTranslationRotationScaling ? + new MeshObjectData2D{std::move(children), + trs->first(), trs->second(), trs->third(), + mesh.front().first(), mesh.front().second(), + skin.empty() ? -1 : Int(skin.front()), + importerState ? *importerState : nullptr} : + new MeshObjectData2D{std::move(children), + *transformation, + mesh.front().first(), mesh.front().second(), + skin ? Int(*skin) : -1, + importerState ? *importerState : nullptr}); + } + + ObjectInstanceType2D instanceType; + UnsignedInt instance; + if(camera) { + instanceType = ObjectInstanceType2D::Camera; + instance = *camera; + } else { + instanceType = ObjectInstanceType2D::Empty; + instance = UnsignedInt(-1); /* Old APIs, you suck! */ + } + + return Containers::pointer(flags & ObjectFlag2D::HasTranslationRotationScaling ? + new ObjectData2D{std::move(children), + trs->first(), trs->second(), trs->third(), + instanceType, instance, + importerState ? *importerState : nullptr} : + new ObjectData2D{std::move(children), + *transformation, + instanceType, instance, + importerState ? *importerState : nullptr}); } Containers::Pointer AbstractImporter::object2D(const std::string& name) { @@ -490,40 +736,177 @@ Containers::Pointer AbstractImporter::object2D(const std::string& } return object2D(id); /* not doObject2D(), so we get the range checks also */ } +CORRADE_IGNORE_DEPRECATED_POP UnsignedInt AbstractImporter::object3DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object3DCount(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH return doObject3DCount(); + CORRADE_IGNORE_DEPRECATED_POP } -UnsignedInt AbstractImporter::doObject3DCount() const { return 0; } +UnsignedInt AbstractImporter::doObject3DCount() const { + /* I know, I know. A cleaner option would be to populate this during + openFile() / openData() but that would mean the backwards compatibility + overhead is there even if not using the deprecated APIs, which is way + worse than using this nasty hack in two places. */ + const_cast(*this).populateCachedScenes(); + return _cachedScenes->object3DCount; +} Int AbstractImporter::object3DForName(const std::string& name) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object3DForName(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH const Int id = doObject3DForName(name); CORRADE_ASSERT(id == -1 || UnsignedInt(id) < doObject3DCount(), "Trade::AbstractImporter::object3DForName(): implementation-returned index" << id << "out of range for" << doObject3DCount() << "entries", {}); + CORRADE_IGNORE_DEPRECATED_POP return id; } -Int AbstractImporter::doObject3DForName(const std::string&) { return -1; } +Int AbstractImporter::doObject3DForName(const std::string& name) { + /* Alias to the new interface. If it returns an ID that's larger than + reported 3D object count, then it's probably for a 2D object instead + -- ignore it in that case. Ideally this would be solved by checking if + the ID is actually present in a 3D scene (and same in doObject3DName()) + but that's a lot of extra code for just a backwards compatibility + feature that almost nobody needs. The only pre-existing 2D importer is + PrimitiveImporter, which is rarely used. */ + const Long id = doObjectForName(name); + CORRADE_IGNORE_DEPRECATED_PUSH + return id < doObject3DCount() ? id : -1; + CORRADE_IGNORE_DEPRECATED_POP +} std::string AbstractImporter::object3DName(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object3DName(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ASSERT(id < doObject3DCount(), "Trade::AbstractImporter::object3DName(): index" << id << "out of range for" << doObject3DCount() << "entries", {}); return doObject3DName(id); + CORRADE_IGNORE_DEPRECATED_POP } -std::string AbstractImporter::doObject3DName(UnsignedInt) { return {}; } +std::string AbstractImporter::doObject3DName(const UnsignedInt id) { + /* Alias to the new interface if the ID is known to the new interface, + return an empty string for objects that got newly added in order to make + them single-functioning */ + if(id < doObjectCount()) return doObjectName(id); + + populateCachedScenes(); + for(UnsignedInt i = 0; i != _cachedScenes->scenes.size(); ++i) { + if(!_cachedScenes->scenes[i] || + !_cachedScenes->scenes[i]->is3D() || + _cachedScenes->scenes[i]->mappingBound() <= id) + continue; + + if(Containers::Optional parent = _cachedScenes->scenes[i]->parentFor(id)) + return doObjectName(*parent); + } + return ""; +} + +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ Containers::Pointer AbstractImporter::object3D(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object3D(): no file opened", {}); CORRADE_ASSERT(id < doObject3DCount(), "Trade::AbstractImporter::object3D(): index" << id << "out of range for" << doObject3DCount() << "entries", {}); return doObject3D(id); } -Containers::Pointer AbstractImporter::doObject3D(UnsignedInt) { - CORRADE_ASSERT_UNREACHABLE("Trade::AbstractImporter::object3D(): not implemented", {}); +Containers::Pointer AbstractImporter::doObject3D(const UnsignedInt id) { + /* The code is mostly the same between doObject2D() and doObject3D(), + except that the 2D variant has no lights. Attempting to unify the common + code would be more complex than just having a slightly modified copy. */ + + populateCachedScenes(); + + /* Find the first 3D scene with this object, which we'll detect from the + mapping bound reported for the scene, whether it's 2D or 3D, and a + presence of a parent attribute. If a parent attribute is not present, it + means the object isn't a part of this scene, in which case we skip it. + It could also mean isn't a part of the hierarchy and is standalone + (skyboxes, scene-wide properties), which the legacy API had no way to + deal with anyway so ignoring those is fine. */ + std::size_t sceneCandidate = ~std::size_t{}; + for(std::size_t i = 0; i != _cachedScenes->scenes.size(); ++i) { + const Containers::Optional& scene = _cachedScenes->scenes[i]; + if(scene && scene->is3D() && id < scene->mappingBound() && scene->parentFor(id)) { + sceneCandidate = i; + break; + } + } + + if(sceneCandidate == ~std::size_t{}) { + Error{} << "Trade::AbstractImporter::object3D(): object" << id << "not found in any 3D scene hierarchy"; + return {}; + } + + const SceneData& scene = *_cachedScenes->scenes[sceneCandidate]; + + ObjectFlags3D flags; + Containers::Optional transformation = scene.transformation3DFor(id); + Containers::Optional> trs = scene.translationRotationScaling3DFor(id); + /* If the object has neither a TRS nor a transformation field, assign an + empty TRS transform. Not a matrix, because a TRS is more flexible and + thus more desired. */ + if(!transformation && !trs) + trs.emplace(Vector3{}, Quaternion{}, Vector3{1.0f}); + if(trs) + flags |= ObjectFlag3D::HasTranslationRotationScaling; + + std::vector children; /* not const so we can move it */ + { + Containers::Array childrenArray = scene.childrenFor(id); + children = {childrenArray.begin(), childrenArray.end()}; + } + const Containers::Array> mesh = scene.meshesMaterialsFor(id); + const Containers::Array camera = scene.camerasFor(id); + const Containers::Array skin = scene.skinsFor(id); + const Containers::Array light = scene.lightsFor(id); + const Containers::Optional importerState = scene.importerStateFor(id); + + /* All these should have at most 1 item as the SceneData got processed to + have each object contain either just one mesh, one camera or one light + (materials are implicitly shared with a mesh, skins also). Thus it + doesn't matter in which order we decide on the legacy object type. */ + CORRADE_INTERNAL_ASSERT(camera.size() + light.size() + mesh.size() <= 1); + + if(!mesh.empty()) { + return Containers::pointer(flags & ObjectFlag3D::HasTranslationRotationScaling ? + new MeshObjectData3D{std::move(children), + trs->first(), trs->second(), trs->third(), + mesh.front().first(), mesh.front().second(), + skin.empty() ? -1 : Int(skin.front()), + importerState ? *importerState : nullptr} : + new MeshObjectData3D{std::move(children), + *transformation, + mesh.front().first(), mesh.front().second(), + skin ? Int(*skin) : -1, + importerState ? *importerState : nullptr}); + } + + ObjectInstanceType3D instanceType; + UnsignedInt instance; + if(camera) { + instanceType = ObjectInstanceType3D::Camera; + instance = *camera; + } else if(light) { + instanceType = ObjectInstanceType3D::Light; + instance = *light; + } else { + instanceType = ObjectInstanceType3D::Empty; + instance = UnsignedInt(-1); /* Old APIs, you suck! */ + } + + return Containers::pointer(flags & ObjectFlag3D::HasTranslationRotationScaling ? + new ObjectData3D{std::move(children), + trs->first(), trs->second(), trs->third(), + instanceType, instance, + importerState ? *importerState : nullptr} : + new ObjectData3D{std::move(children), + *transformation, + instanceType, instance, + importerState ? *importerState : nullptr}); } Containers::Pointer AbstractImporter::object3D(const std::string& name) { @@ -535,6 +918,8 @@ Containers::Pointer AbstractImporter::object3D(const std::string& } return object3D(id); /* not doObject3D(), so we get the range checks also */ } +CORRADE_IGNORE_DEPRECATED_POP +#endif UnsignedInt AbstractImporter::skin2DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::skin2DCount(): no file opened", {}); diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index a1684f47a5..77500ae289 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -181,14 +181,11 @@ ID of a texture it uses). The following kinds of data can be imported: @ref meshCount(). Similarly as with images, each mesh can also have multiple levels (LODs or for example separate edge/face data), which are requested through the second parameter up to @ref meshLevelCount(). -- @ref ObjectData2D / @ref ObjectData3D using - @ref object2D(UnsignedInt) / @ref object3D(UnsignedInt) up to - @ref object2DCount() / @ref object3DCount(). An object can then reference - its child objects, mesh and a material, camera, light or a skin associated - with it via their IDs. - @ref SceneData using @ref scene(UnsignedInt) up to @ref sceneCount(), with the default scene index exposed through @ref defaultScene(). A scene then - references its child 2D/3D objects via their IDs. + contains all data for its objects such as transformations and parent/child + hierarchy, particular objects are associated with meshes, materials, + cameras, lights or skins via their IDs. - @ref SkinData2D / @ref SkinData3D using @ref skin2D(UnsignedInt) / @ref skin3D(UnsignedInt) up to @ref skin2DCount() / @ref skin3DCount() - @ref TextureData using @ref texture(UnsignedInt) up to @ref textureCount(). @@ -303,11 +300,11 @@ name doesn't exist. @ref mesh(const std::string&, UnsignedInt). Meshes themselves can have custom attributes, for which the name mapping can be retrieved using @ref meshAttributeName() and @ref meshAttributeForName(). -- Objects names using @ref object2DName() / @ref object3DName() & - @ref object2DForName() / @ref object3DForName(), imported with - @ref object2D(const std::string&) / @ref object3D(const std::string&) -- Scene names using @ref sceneName() & @ref sceneForName(), imported with - @ref scene(const std::string&) +- Scene and object names using @ref sceneName() / @ref objectName() & + @ref sceneForName() / @ref objectForName(), imported with + @ref scene(const std::string&). Scenes themselves can have custom fields, + for which the name mapping can be retrieved using @ref sceneFieldName() and + @ref sceneFieldForName(). - Skin names using @ref skin2DName() / @ref skin3DName() & @ref skin2DForName() / @ref skin3DForName(), imported with @ref skin2D(const std::string&) / @ref skin3D(const std::string&) @@ -333,10 +330,9 @@ expose internal state through various accessors: imported by @ref light() - @ref MeshData::importerState() can expose importer state for a mesh imported by @ref mesh() -- @ref ObjectData3D::importerState() can expose importer state for an object - imported by @ref object2D() or @ref object3D() - @ref SceneData::importerState() can expose importer state for a scene - imported by @ref scene() + imported by @ref scene(), per-object importer state can then be stored in + the @ref SceneField::ImporterState field - @ref SkinData::importerState() can expose importer state for a scene imported by @ref skin2D() or @ref skin3D() - @ref TextureData::importerState() can expose importer state for a texture @@ -347,20 +343,6 @@ Besides exposing internal state, importers that support the state using @ref openState(). See documentation of a particular importer for details about concrete types returned and accepted by these functions. -@subsection Trade-AbstractImporter-usage-casting Polymorphic imported data types - -Some data access functions return @relativeref{Corrade,Containers::Pointer} -instead of @relativeref{Corrade,Containers::Optional} because the result might -be a particular subclass of given type. Those functions are @ref object2D() -and @ref object3D(). You can cast the abstract base to a concrete type -depending on its reported type, for example: - -@snippet MagnumTrade.cpp AbstractImporter-usage-cast - -Another option is making use of the @ref Containers::pointerCast() utility, but -note that in that case the original @relativeref{Corrade,Containers::Pointer} -will have to be *moved into* a new instance, which might not be desirable. - @section Trade-AbstractImporter-data-dependency Data dependency The `*Data` instances returned from various functions *by design* have no @@ -491,6 +473,14 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi /** @brief Plugin manager constructor */ explicit AbstractImporter(PluginManager::AbstractManager& manager, const std::string& plugin); + #if defined(MAGNUM_BUILD_DEPRECATED) && !defined(DOXYGEN_GENERATING_OUTPUT) + /* These twp needed because of the Array member + (AnyImageImporter relies on the move), move assignment disabled by + AbstractPlugin already */ + AbstractImporter(AbstractImporter&&) noexcept; + ~AbstractImporter(); + #endif + /** @brief Features supported by this importer */ ImporterFeatures features() const { return doFeatures(); } @@ -721,6 +711,17 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ UnsignedInt sceneCount() const; + /** + * @brief Object count + * + * Total count of all (2D or 3D) objects in all scenes. An object can + * be present in multiple scenes at the same time. Fields corresponding + * to particular objects can be then accessed via the @ref SceneData + * class returned from @ref scene(UnsignedInt). Expects that a file is + * opened. + */ + UnsignedLong objectCount() const; + /** * @brief Scene for given name * @return Scene ID from range [0, @ref sceneCount()) or @cpp -1 @ce if @@ -731,6 +732,16 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ Int sceneForName(const std::string& name); + /** + * @brief Object ID for given name + * + * If no object for given name exists, returns @cpp -1 @ce. Expects + * that a file is opened. Object IDs are shared among all scenes, an + * object can be present in multiple scenes at the same time. + * @see @ref objectName() + */ + Long objectForName(const std::string& name); + /** * @brief Scene name * @param id Scene ID, from range [0, @ref sceneCount()). @@ -741,6 +752,18 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ std::string sceneName(UnsignedInt id); + /** + * @brief Object name + * @param id Object ID, from range [0, @ref objectCount()). + * + * Object IDs are shared among all scenes, an object can be present in + * multiple scenes at the same time. If the object has no name or the + * importer doesn't support object names, returns an empty string. + * Expects that a file is opened. + * @see @ref objectForName() + */ + std::string objectName(UnsignedLong id); + /** * @brief Scene * @param id Scene ID, from range [0, @ref sceneCount()). @@ -763,6 +786,34 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ Containers::Optional scene(const std::string& name); + /** + * @brief Scene field for given name + * @m_since_latest + * + * If the name is not recognized, returns a zero (invalid) + * @ref SceneField, otherwise returns a custom scene field. Note that + * the value returned by this function may depend on whether a file is + * opened or not and also be different for different files --- see + * documentation of a particular importer for more information. + * @see @ref isSceneFieldCustom() + */ + SceneField sceneFieldForName(const std::string& name); + + /** + * @brief String name for given custom scene field + * @m_since_latest + * + * Given a custom @p name returned by @ref scene() in a @ref SceneData, + * returns a string identifier. If a string representation is not + * available or @p name is not recognized, returns an empty string. + * Expects that @p name is custom. Note that the value returned by + * this function may depend on whether a file is opened or not and also + * be different for different files --- see documentation of a + * particular importer for more information. + * @see @ref isSceneFieldCustom() + */ + std::string sceneFieldName(SceneField name); + /** * @brief Animation count * @@ -910,12 +961,15 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ Containers::Optional camera(const std::string& name); + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Two-dimensional object count * * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref objectCount() instead, which is + * shared for both 2D and 3D objects. */ - UnsignedInt object2DCount() const; + CORRADE_DEPRECATED("use objectCount() instead") UnsignedInt object2DCount() const; /** * @brief Two-dimensional object for given name @@ -923,9 +977,11 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @cpp -1 @ce if no object for given name exists * * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref objectForName() instead, which + * is shared for both 2D and 3D objects. * @see @ref object2DName(), @ref object2D(const std::string&) */ - Int object2DForName(const std::string& name); + CORRADE_DEPRECATED("use objectForName() instead") Int object2DForName(const std::string& name); /** * @brief Two-dimensional object name @@ -933,9 +989,11 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Expects that a file is opened. If the object has no name or the * importer doesn't support object names, returns an empty string. + * @m_deprecated_since_latest Use @ref objectName() instead, which is + * shared for both 2D and 3D objects. * @see @ref object2DForName() */ - std::string object2DName(UnsignedInt id); + CORRADE_DEPRECATED("use objectName() instead") std::string object2DName(UnsignedInt id); /** * @brief Two-dimensional object @@ -943,9 +1001,14 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given object or @cpp nullptr @ce if importing failed. * Expects that a file is opened. + * @m_deprecated_since_latest Query object fields on the @ref SceneData + * object returned from @ref scene() instead, which is shared for + * both 2D and 3D objects. * @see @ref object2D(const std::string&) */ - Containers::Pointer object2D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + CORRADE_DEPRECATED("query object fields from scene() instead") Containers::Pointer object2D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Two-dimensional object for given name @@ -956,15 +1019,22 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @cpp -1 @ce, prints an error message and returns * @cpp nullptr @ce, otherwise propagates the result from * @ref object2D(UnsignedInt). Expects that a file is opened. + * @m_deprecated_since_latest Query object fields on the @ref SceneData + * object returned from @ref scene() instead, which is shared for + * both 2D and 3D objects. */ - Containers::Pointer object2D(const std::string& name); + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + CORRADE_DEPRECATED("query object fields from scene() instead") Containers::Pointer object2D(const std::string& name); + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Three-dimensional object count * * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref objectCount() instead, which is + * shared for both 2D and 3D objects. */ - UnsignedInt object3DCount() const; + CORRADE_DEPRECATED("use objectCount() instead") UnsignedInt object3DCount() const; /** * @brief Three-dimensional object for given name @@ -972,9 +1042,11 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @cpp -1 @ce if no object for given name exists * * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref objectForName() instead, which + * is shared for both 2D and 3D objects. * @see @ref object3DName(), @ref object3D(const std::string&) */ - Int object3DForName(const std::string& name); + CORRADE_DEPRECATED("use objectForName() instead") Int object3DForName(const std::string& name); /** * @brief Three-dimensional object name @@ -982,9 +1054,11 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Expects that a file is opened. If the object has no name or the * importer doesn't support object names, returns an empty string. + * @m_deprecated_since_latest Use @ref objectName() instead, which is + * shared for both 2D and 3D objects. * @see @ref object3DForName() */ - std::string object3DName(UnsignedInt id); + CORRADE_DEPRECATED("use objectName() instead") std::string object3DName(UnsignedInt id); /** * @brief Three-dimensional object @@ -992,9 +1066,14 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given object or @cpp nullptr @ce if importing failed. * Expects that a file is opened. + * @m_deprecated_since_latest Query object fields on the @ref SceneData + * object returned from @ref scene() instead, which is shared for + * both 2D and 3D objects. * @see @ref object3D(const std::string&) */ - Containers::Pointer object3D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + CORRADE_DEPRECATED("query object fields from scene() instead") Containers::Pointer object3D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Three-dimensional object for given name @@ -1005,8 +1084,14 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @cpp -1 @ce, prints an error message and returns * @cpp nullptr @ce, otherwise propagates the result from * @ref object3D(UnsignedInt). Expects that a file is opened. + * @m_deprecated_since_latest Query object fields on the @ref SceneData + * object returned from @ref scene() instead, which is shared for + * both 2D and 3D objects. */ - Containers::Pointer object3D(const std::string& name); + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + CORRADE_DEPRECATED("query object fields from scene() instead") Containers::Pointer object3D(const std::string& name); + CORRADE_IGNORE_DEPRECATED_POP + #endif /** * @brief Two-dimensional skin count @@ -1742,6 +1827,16 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ virtual UnsignedInt doSceneCount() const; + /** + * @brief Implementation for @ref objectCount() + * + * Default implementation returns @cpp 0 @ce. This function isn't + * expected to fail --- if an import error occus, it should be handled + * preferably during @ref doScene() (with correct object count + * reported), and if not possible, already during file opening. + */ + virtual UnsignedLong doObjectCount() const; + /** * @brief Implementation for @ref sceneForName() * @@ -1749,6 +1844,13 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ virtual Int doSceneForName(const std::string& name); + /** + * @brief Implementation for @ref objectForName() + * + * Default implementation returns @cpp -1 @ce. + */ + virtual Long doObjectForName(const std::string& name); + /** * @brief Implementation for @ref sceneName() * @@ -1756,9 +1858,33 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ virtual std::string doSceneName(UnsignedInt id); + /** + * @brief Implementation for @ref objectName() + * + * Default implementation returns an empty string. + */ + virtual std::string doObjectName(UnsignedLong id); + /** @brief Implementation for @ref scene() */ virtual Containers::Optional doScene(UnsignedInt id); + /** + * @brief Implementation for @ref sceneFieldForName() + * @m_since_latest + * + * Default implementation returns an invalid (zero) value. + */ + virtual SceneField doSceneFieldForName(const std::string& name); + + /** + * @brief Implementation for @ref sceneFieldName() + * @m_since_latest + * + * Receives the custom ID extracted via @ref sceneFieldCustom(SceneField). + * Default implementation returns an empty string. + */ + virtual std::string doSceneFieldName(UnsignedInt name); + /** * @brief Implementation for @ref animationCount() * @@ -1840,59 +1966,121 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi /** @brief Implementation for @ref camera() */ virtual Containers::Optional doCamera(UnsignedInt id); + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Implementation for @ref object2DCount() * - * Default implementation returns @cpp 0 @ce. This function isn't - * expected to fail --- if an import error occus, it should be handled - * preferably during @ref doObject2D() (with correct object count - * reported), and if not possible, already during file opening. + * Default implementation returns @cpp 0 @ce. There weren't any + * importers in existence known to implement 2D scene import, so unlike + * @ref doObject3DCount() this function doesn't delegate to + * @ref doObjectCount(). + * @m_deprecated_since_latest Implement @ref doObjectCount() instead. */ + /* MSVC warns when overriding such methods and there's no way to + suppress that warning, making the RT build (which treats deprecation + warnings as errors) fail and other builds extremely noisy. So + disabling those on MSVC. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doObjectCount() instead") + #endif virtual UnsignedInt doObject2DCount() const; /** * @brief Implementation for @ref object2DForName() * - * Default implementation returns @cpp -1 @ce. + * Default implementation returns @cpp -1 @ce. There weren't any + * importers in existence known to implement 2D scene import, so unlike + * @ref doObject3DForName() this function doesn't delegate to + * @ref doObjectForName(). + * @m_deprecated_since_latest Implement @ref doObjectForName() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doObjectForName() instead") + #endif /* See above */ virtual Int doObject2DForName(const std::string& name); /** * @brief Implementation for @ref object2DName() * - * Default implementation returns an empty string. + * Default implementation returns an empty string. There weren't any + * importers in existence known to implement 2D scene import, so unlike + * @ref doObject3DName() this function doesn't delegate to + * @ref doObjectName(). + * @m_deprecated_since_latest Implement @ref doObjectName() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doObjectName() instead") + #endif /* See above */ virtual std::string doObject2DName(UnsignedInt id); - /** @brief Implementation for @ref object2D() */ + /** + * @brief Implementation for @ref object2D() + * + * There weren't any importers in existence known to implement 2D scene + * import, so unlike @ref doObject3D() this function doesn't proxy + * per-object data returned from @ref doScene(). + * @m_deprecated_since_latest Implement @ref doScene() instead. + */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doScene() instead") + #endif /* See above */ virtual Containers::Pointer doObject2D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Implementation for @ref object3DCount() * - * Default implementation returns @cpp 0 @ce. This function isn't - * expected to fail --- if an import error occus, it should be handled - * preferably during @ref doObject3D() (with correct object count - * reported), and if not possible, already during file opening. + * Default implementation returns @ref doObjectCount() for backwards + * compatibility. + * @m_deprecated_since_latest Implement @ref doObjectCount() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doObjectCount() instead") + #endif /* See above */ virtual UnsignedInt doObject3DCount() const; /** * @brief Implementation for @ref object3DForName() * - * Default implementation returns @cpp -1 @ce. + * Default implementation returns @ref doObjectForName() for backwards + * compatibility. + * @m_deprecated_since_latest Implement @ref doObjectForName() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doObjectForName() instead") + #endif /* See above */ virtual Int doObject3DForName(const std::string& name); /** * @brief Implementation for @ref object3DName() * - * Default implementation returns an empty string. + * Default implementation returns @ref doObjectName() for backwards + * compatibility. + * @m_deprecated_since_latest Implement @ref doObjectName() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doObjectName() instead") + #endif /* See above */ virtual std::string doObject3DName(UnsignedInt id); - /** @brief Implementation for @ref object3D() */ + /** + * @brief Implementation for @ref object3D() + * + * Default implementation retrieves and caches scenes returned from + * @ref doScene(), finds the first scene that contains any fields for + * object @p id and then returns a subset of the data that's + * representable with a @ref ObjectData3D / @ref MeshObjectData3D + * instance. + * @m_deprecated_since_latest Implement @ref doScene() instead. + */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doScene() instead") + #endif /* See above */ virtual Containers::Pointer doObject3D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_POP + #endif /** * @brief Implementation for @ref skin2DCount() @@ -2321,6 +2509,12 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi const void* userData; /* GCC 4.8 complains loudly about missing initializers otherwise */ } _fileCallbackTemplate{nullptr, nullptr}; + + #ifdef MAGNUM_BUILD_DEPRECATED + struct CachedScenes; + Containers::Pointer _cachedScenes; + void populateCachedScenes(); + #endif }; #ifndef DOXYGEN_GENERATING_OUTPUT diff --git a/src/Magnum/Trade/AnimationData.cpp b/src/Magnum/Trade/AnimationData.cpp index 930c9c2c8f..a120ab2c7e 100644 --- a/src/Magnum/Trade/AnimationData.cpp +++ b/src/Magnum/Trade/AnimationData.cpp @@ -92,7 +92,7 @@ AnimationTrackTargetType AnimationData::trackTargetType(UnsignedInt id) const { return _tracks[id]._targetType; } -UnsignedInt AnimationData::trackTarget(UnsignedInt id) const { +UnsignedLong AnimationData::trackTarget(UnsignedInt id) const { CORRADE_ASSERT(id < _tracks.size(), "Trade::AnimationData::trackTarget(): index out of range", {}); return _tracks[id]._target; } diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index 7c54cfbd3c..bed8e9f455 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -238,14 +238,14 @@ class AnimationTrackData { * @param target Track target * @param view Type-erased @ref Animation::TrackView instance */ - explicit AnimationTrackData(AnimationTrackType type, AnimationTrackType resultType, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{resultType}, _targetType{targetType}, _target{target}, _view{view} {} + explicit AnimationTrackData(AnimationTrackType type, AnimationTrackType resultType, AnimationTrackTargetType targetType, UnsignedLong target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{resultType}, _targetType{targetType}, _target{target}, _view{view} {} /** @overload * * Equivalent to the above with @p type used as both value type and * result type. */ - explicit AnimationTrackData(AnimationTrackType type, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{type}, _targetType{targetType}, _target{target}, _view{view} {} + explicit AnimationTrackData(AnimationTrackType type, AnimationTrackTargetType targetType, UnsignedLong target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{type}, _targetType{targetType}, _target{target}, _view{view} {} /** * @brief Constructor @@ -257,14 +257,14 @@ class AnimationTrackData { * Detects @ref AnimationTrackType from @p view type and delegates to * @ref AnimationTrackData(AnimationTrackType, AnimationTrackType, AnimationTrackTargetType, UnsignedInt, Animation::TrackViewStorage). */ - template explicit AnimationTrackData(AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackView view) noexcept; + template explicit AnimationTrackData(AnimationTrackTargetType targetType, UnsignedLong target, Animation::TrackView view) noexcept; private: friend AnimationData; AnimationTrackType _type, _resultType; AnimationTrackTargetType _targetType; - UnsignedInt _target; + UnsignedLong _target; Animation::TrackViewStorage _view; }; @@ -512,7 +512,7 @@ class MAGNUM_TRADE_EXPORT AnimationData { * @see @ref trackCount(), @ref AbstractImporter::object2D(), * @ref AbstractImporter::object3D() */ - UnsignedInt trackTarget(UnsignedInt id) const; + UnsignedLong trackTarget(UnsignedInt id) const; /** * @brief Track data storage @@ -649,7 +649,7 @@ namespace Implementation { /* LCOV_EXCL_STOP */ } -template inline AnimationTrackData::AnimationTrackData(AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackView view) noexcept: AnimationTrackData{Implementation::animationTypeFor(), Implementation::animationTypeFor(), targetType, target, view} {} +template inline AnimationTrackData::AnimationTrackData(AnimationTrackTargetType targetType, UnsignedLong target, Animation::TrackView view) noexcept: AnimationTrackData{Implementation::animationTypeFor(), Implementation::animationTypeFor(), targetType, target, view} {} template const Animation::TrackView& AnimationData::track(UnsignedInt id) const { const Animation::TrackViewStorage& storage = track(id); diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index 9aa7a27c66..e5b68f2724 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -28,9 +28,6 @@ find_package(Corrade REQUIRED PluginManager) set(MagnumTrade_SRCS ArrayAllocator.cpp Data.cpp - MeshObjectData2D.cpp - MeshObjectData3D.cpp - SceneData.cpp TextureData.cpp) set(MagnumTrade_GracefulAssert_SRCS @@ -44,12 +41,11 @@ set(MagnumTrade_GracefulAssert_SRCS LightData.cpp MaterialData.cpp MeshData.cpp - ObjectData2D.cpp - ObjectData3D.cpp PbrClearCoatMaterialData.cpp PbrMetallicRoughnessMaterialData.cpp PbrSpecularGlossinessMaterialData.cpp PhongMaterialData.cpp + SceneData.cpp SkinData.cpp) set(MagnumTrade_HEADERS @@ -66,10 +62,6 @@ set(MagnumTrade_HEADERS MaterialData.h MaterialLayerData.h MeshData.h - MeshObjectData2D.h - MeshObjectData3D.h - ObjectData2D.h - ObjectData3D.h PbrClearCoatMaterialData.h PbrMetallicRoughnessMaterialData.h PbrSpecularGlossinessMaterialData.h @@ -84,9 +76,13 @@ 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 + MeshObjectData2D.cpp + MeshObjectData3D.cpp) list(APPEND MagnumTrade_GracefulAssert_SRCS # These have to be here instead of in MagnumTrade_SRCS because they # include MeshData.h and call (and thus instantiate) various functions @@ -96,12 +92,19 @@ if(MAGNUM_BUILD_DEPRECATED) # causing the tests to blow up. Happens only on the MSVC linker, but # let's be safe and do this everywhere. MeshData2D.cpp - MeshData3D.cpp) + MeshData3D.cpp + + ObjectData2D.cpp + ObjectData3D.cpp) list(APPEND MagnumTrade_HEADERS AbstractMaterialData.h MeshData2D.h - MeshData3D.h) + MeshData3D.h + MeshObjectData2D.h + MeshObjectData3D.h + ObjectData2D.h + ObjectData3D.h) endif() if(NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT) diff --git a/src/Magnum/Trade/Implementation/sceneTools.h b/src/Magnum/Trade/Implementation/sceneTools.h new file mode 100644 index 0000000000..a7a67e67be --- /dev/null +++ b/src/Magnum/Trade/Implementation/sceneTools.h @@ -0,0 +1,416 @@ +#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 + +#include "Magnum/Math/Functions.h" +#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].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) { + for(std::size_t i = 0; i != fields.size(); ++i) + if(fields[i] == field) return i; + return {}; +} + +/* Creates a SceneData copy where each object has at most one of the fields + listed in the passed `fieldsToConvert` array. This is done by enlarging the + parents array and moving extraneous features to new objects that are marked + as a child of the original. Fields that are connected together (such as + meshes and materials) are assumed to share the same object mapping with only + one of them passed in the fieldsToConvert array, which will result for all + fields from the same set being reassociated to the new object. + + Fields listed in `fieldsToCopy` are copied from the original object. This + is useful for e.g. skins, to preserve them for the separated meshes. + + Requies a SceneField::Parent to be present -- otherwise it wouldn't be + possible to know where to attach the new objects. */ +/** @todo when published, (again) add an initializer_list overload and turn all + internal asserts into (tested!) message asserts */ +inline SceneData sceneConvertToSingleFunctionObjects(const 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); + + /* Skip fields that are not present -- is it's not present, then it + definitely won't be responsible for multi-function objects */ + const Containers::Optional fieldId = scene.findFieldId(field); + if(!fieldId) continue; + + /** @todo use a statically-allocated array & Into() in a loop instead + once this is more than a private backwards-compatibility utility + where PERF WHATEVER WHO CARES */ + for(const UnsignedInt object: scene.mappingAsArray(*fieldId)) { + CORRADE_INTERNAL_ASSERT(object < objectAttachmentCount.size()); + ++objectAttachmentCount[object]; + } + } + + /* entriesToAddToFieldsToCopy[i] specifies how many fields to add for the + 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); + CORRADE_INTERNAL_ASSERT(!findField(fieldsToConvert, field)); + + /* Skip fields that are not present */ + const Containers::Optional fieldId = scene.findFieldId(field); + if(!fieldId) continue; + + /** @todo use a statically-allocated array & Into() in a loop instead + once this is more than a private backwards-compatibility utility + where PERF WHATEVER WHO CARES */ + for(const UnsignedInt object: scene.mappingAsArray(*fieldId)) { + CORRADE_INTERNAL_ASSERT(object < objectAttachmentCount.size()); + if(objectAttachmentCount[object]) + fieldsToCopyAdditionCount[i] += objectAttachmentCount[object] - 1; + } + } + + UnsignedInt objectsToAdd = 0; + for(const UnsignedInt count: objectAttachmentCount) + if(count > 1) objectsToAdd += count - 1; + + /* Ensure we don't overflow the 32-bit object count with the objects to + add. This should also cover the case when the parent field would not be + representable in 32 bits. */ + 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()}; + for(std::size_t i = 0; i != scene.fieldCount(); ++i) { + const 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(), + field.mappingType(), + Containers::ArrayView{nullptr, std::size_t(field.size() + fieldsToCopyAdditionCount[*fieldToCopy])}, + field.fieldType(), + Containers::StridedArrayView1D{ + {nullptr, ~std::size_t{}}, + std::size_t(field.size() + fieldsToCopyAdditionCount[*fieldToCopy]), + std::ptrdiff_t((field.fieldArraySize() ? field.fieldArraySize() : 1)*sceneFieldTypeSize(field.fieldType())) + }, + field.fieldArraySize(), + field.flags() & ~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) { + /** @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)}, + /* If the field is ordered, we preserve that. But if it's + implicit, we can't. */ + field.flags() & ~(SceneFieldFlag::ImplicitMapping & ~SceneFieldFlag::OrderedMapping) + }; + + /* All other fields are copied as-is, but lose the implicit/ordered + flags */ + /** @todo the flags could get preserved for + - fields that don't share their object mapping with any fields in + the fieldsToConvert list + - 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}; + } + + /* Combine the fields into a new SceneData */ + SceneData out = sceneCombine(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); + const Containers::StridedArrayView1D outParents = out.mutableField(parentFieldId); + CORRADE_INTERNAL_ASSERT_OUTPUT(scene.parentsInto(0, outParentMapping, outParents) == scene.fieldSize(parentFieldId)); + + /* 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 Containers::Optional fieldId = scene.findFieldId(field); + if(!fieldId) continue; + + const Containers::StridedArrayView1D outMapping = out.mutableMapping(*fieldId); + const Containers::StridedArrayView2D outField = out.mutableField(*fieldId); + CORRADE_INTERNAL_ASSERT_OUTPUT(scene.mappingInto(*fieldId, 0, outMapping) == scene.fieldSize(*fieldId)); + Utility::copy(scene.field(*fieldId), outField.prefix(scene.fieldSize(*fieldId))); + } + + /* List new objects at the end of the extended parent field */ + const Containers::StridedArrayView1D newParentMapping = outParentMapping.suffix(scene.fieldSize(parentFieldId)); + const Containers::StridedArrayView1D newParents = outParents.suffix(scene.fieldSize(parentFieldId)); + for(std::size_t i = 0; i != newParentMapping.size(); ++i) { + newParentMapping[i] = newObjectOffset + i; + newParents[i] = -1; + } + + /* Clear the objectAttachmentCount array to reuse it below */ + /** @todo use a BitArray instead once it exists? */ + constexpr UnsignedInt zero[1]{}; + Utility::copy(Containers::stridedArrayView(zero).broadcasted<0>(scene.mappingBound()), objectAttachmentCount); + + /* Clear the fieldsToCopyAdditionCount array to reuse it below */ + Utility::copy(Containers::stridedArrayView(zero).broadcasted<0>(fieldsToCopy.size()), fieldsToCopyAdditionCount); + + /* For objects with multiple fields move the extra fields to newly added + children */ + { + std::size_t newParentIndex = 0; + for(const SceneField field: fieldsToConvert) { + const Containers::Optional fieldId = scene.findFieldId(field); + if(!fieldId) continue; + + for(UnsignedInt& fieldObject: out.mutableMapping(*fieldId)) { + /* If the object is not new (could happen when an object + mapping array is shared among multiple fields, in which case + it *might* have been updated already to an ID larger than + the mapping array size) and it already has something + attached, then attach the field to a new object and make + that new object a child of the previous one. */ + if(fieldObject < objectAttachmentCount.size() && objectAttachmentCount[fieldObject]) { + /* Go through all fields to copy and copy each entry that + was assigned to the original object */ + for(std::size_t i = 0; i != fieldsToCopy.size(); ++i) { + const Containers::Optional fieldToCopyId = scene.findFieldId(fieldsToCopy[i]); + if(!fieldToCopyId) continue; + + /* View to copy the data from */ + const Containers::StridedArrayView2D fieldToCopyDataSrc = scene.field(*fieldToCopyId); + + /* Views to put the mapping to and copy the data to */ + const std::size_t newFieldToCopyOffset = scene.fieldSize(*fieldToCopyId); + const Containers::StridedArrayView1D newFieldToCopyMapping = out.mutableMapping(*fieldToCopyId).suffix(newFieldToCopyOffset); + const Containers::StridedArrayView2D newFieldToCopy = out.mutableField(*fieldToCopyId).suffix(newFieldToCopyOffset); + + /* As long as there are entries attached to the + original objects, copy them */ + std::size_t offset = 0; + while(Containers::Optional found = scene.findFieldObjectOffset(*fieldToCopyId, fieldObject, offset)) { + /* Assgn a new field entry to the new object */ + newFieldToCopyMapping[fieldsToCopyAdditionCount[i]] = newParentMapping[newParentIndex]; + + /* Copy the data from the old entry to it */ + Utility::copy(fieldToCopyDataSrc[*found], newFieldToCopy[fieldsToCopyAdditionCount[i]]); + + ++fieldsToCopyAdditionCount[i]; + offset = *found + 1; + } + } + + /* Use the old object as a parent of the new object */ + newParents[newParentIndex] = fieldObject; + /* Assign the field to the new object */ + fieldObject = newParentMapping[newParentIndex]; + /* Move to the next reserved object */ + ++newParentIndex; + + /** @todo mark this field as touched here and the restore + the original flags for all fields that didn't have + their object mapping touched */ + + } else ++objectAttachmentCount[fieldObject]; + } + } + + CORRADE_INTERNAL_ASSERT(newParentIndex == objectsToAdd); + } + + return out; +} + +}}} + +#endif diff --git a/src/Magnum/Trade/MeshObjectData2D.cpp b/src/Magnum/Trade/MeshObjectData2D.cpp index 2d478d8a34..0b587131ab 100644 --- a/src/Magnum/Trade/MeshObjectData2D.cpp +++ b/src/Magnum/Trade/MeshObjectData2D.cpp @@ -23,12 +23,18 @@ DEALINGS IN THE SOFTWARE. */ +#define _MAGNUM_NO_DEPRECATED_OBJECTDATA /* So it doesn't yell here */ + #include "MeshObjectData2D.h" namespace Magnum { namespace Trade { +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ MeshObjectData2D::MeshObjectData2D(std::vector children, const Matrix3& transformation, const UnsignedInt instance, const Int material, const Int skin, const void* const importerState): ObjectData2D{std::move(children), transformation, ObjectInstanceType2D::Mesh, instance, importerState}, _material{material}, _skin{skin} {} +CORRADE_IGNORE_DEPRECATED_POP +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ MeshObjectData2D::MeshObjectData2D(std::vector children, const Vector2& translation, const Complex& rotation, const Vector2& scaling, const UnsignedInt instance, const Int material, const Int skin, const void* const importerState): ObjectData2D{std::move(children), translation, rotation, scaling, ObjectInstanceType2D::Mesh, instance, importerState}, _material{material}, _skin{skin} {} +CORRADE_IGNORE_DEPRECATED_POP }} diff --git a/src/Magnum/Trade/MeshObjectData2D.h b/src/Magnum/Trade/MeshObjectData2D.h index 5171e55560..1e215f7722 100644 --- a/src/Magnum/Trade/MeshObjectData2D.h +++ b/src/Magnum/Trade/MeshObjectData2D.h @@ -25,21 +25,33 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MAGNUM_BUILD_DEPRECATED /** @file * @brief Class @ref Magnum::Trade::MeshObjectData2D */ +#endif + +#include "Magnum/configure.h" +#ifdef MAGNUM_BUILD_DEPRECATED #include "Magnum/Trade/ObjectData2D.h" +#ifndef _MAGNUM_NO_DEPRECATED_OBJECTDATA +CORRADE_DEPRECATED_FILE("use Magnum/Trade/SceneData.h and the SceneData class instead") +#endif + namespace Magnum { namespace Trade { +CORRADE_IGNORE_DEPRECATED_PUSH /* MSVC warns on the inheritance */ + /** @brief Two-dimensional mesh object data +@m_deprecated_since_latest Use @ref SceneData instead. Provides access to material information for given mesh instance. @see @ref AbstractImporter::object2D(), @ref MeshObjectData3D */ -class MAGNUM_TRADE_EXPORT MeshObjectData2D: public ObjectData2D { +class CORRADE_DEPRECATED("use SceneData instead") MAGNUM_TRADE_EXPORT MeshObjectData2D: public ObjectData2D { public: /** * @brief Construct with combined transformation @@ -54,13 +66,11 @@ class MAGNUM_TRADE_EXPORT MeshObjectData2D: public ObjectData2D { */ explicit MeshObjectData2D(std::vector children, const Matrix3& transformation, UnsignedInt instance, Int material, Int skin, const void* importerState = nullptr); - #ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief MeshObjectData2D(std::vector, const Matrix3&, UnsignedInt, Int, Int, const void*) * @m_deprecated_since_latest Use @ref MeshObjectData2D(std::vector, const Matrix3&, UnsignedInt, Int, Int, const void*) * instead. */ explicit CORRADE_DEPRECATED("use MeshObjectData2D(std::vector, const Matrix4&, UnsignedInt, Int, Int, const void*) instead") MeshObjectData2D(std::vector children, const Matrix3& transformation, UnsignedInt instance, Int material, const void* importerState = nullptr): MeshObjectData2D{std::move(children), transformation, instance, material, -1, importerState} {} - #endif /** * @brief Construct with separate transformations @@ -132,6 +142,11 @@ class MAGNUM_TRADE_EXPORT MeshObjectData2D: public ObjectData2D { Int _material, _skin; }; +CORRADE_IGNORE_DEPRECATED_POP /* MSVC warns on the inheritance */ + }} +#else +#error use Magnum/Trade/SceneData.h and the SceneData class instead +#endif #endif diff --git a/src/Magnum/Trade/MeshObjectData3D.cpp b/src/Magnum/Trade/MeshObjectData3D.cpp index 00f58c2b20..67223b6354 100644 --- a/src/Magnum/Trade/MeshObjectData3D.cpp +++ b/src/Magnum/Trade/MeshObjectData3D.cpp @@ -23,12 +23,18 @@ DEALINGS IN THE SOFTWARE. */ +#define _MAGNUM_NO_DEPRECATED_OBJECTDATA /* So it doesn't yell here */ + #include "MeshObjectData3D.h" namespace Magnum { namespace Trade { +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ MeshObjectData3D::MeshObjectData3D(std::vector children, const Matrix4& transformation, const UnsignedInt instance, const Int material, const Int skin, const void* const importerState): ObjectData3D{std::move(children), transformation, ObjectInstanceType3D::Mesh, instance, importerState}, _material{material}, _skin{skin} {} +CORRADE_IGNORE_DEPRECATED_POP +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ MeshObjectData3D::MeshObjectData3D(std::vector children, const Vector3& translation, const Quaternion& rotation, const Vector3& scaling, const UnsignedInt instance, const Int material, const Int skin, const void* const importerState): ObjectData3D{std::move(children), translation, rotation, scaling, ObjectInstanceType3D::Mesh, instance, importerState}, _material{material}, _skin{skin} {} +CORRADE_IGNORE_DEPRECATED_POP }} diff --git a/src/Magnum/Trade/MeshObjectData3D.h b/src/Magnum/Trade/MeshObjectData3D.h index ad43aa5ba5..64236ccc6c 100644 --- a/src/Magnum/Trade/MeshObjectData3D.h +++ b/src/Magnum/Trade/MeshObjectData3D.h @@ -25,21 +25,33 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MAGNUM_BUILD_DEPRECATED /** @file * @brief Class @ref Magnum::Trade::MeshObjectData3D */ +#endif + +#include "Magnum/configure.h" +#ifdef MAGNUM_BUILD_DEPRECATED #include "Magnum/Trade/ObjectData3D.h" +#ifndef _MAGNUM_NO_DEPRECATED_OBJECTDATA +CORRADE_DEPRECATED_FILE("use Magnum/Trade/SceneData.h and the SceneData class instead") +#endif + namespace Magnum { namespace Trade { +CORRADE_IGNORE_DEPRECATED_PUSH /* MSVC warns on the inheritance */ + /** @brief Three-dimensional mesh object data +@m_deprecated_since_latest Use @ref SceneData instead. Provides access to material information for given mesh instance. @see @ref AbstractImporter::object3D(), @ref MeshObjectData2D */ -class MAGNUM_TRADE_EXPORT MeshObjectData3D: public ObjectData3D { +class CORRADE_DEPRECATED("use SceneData instead") MAGNUM_TRADE_EXPORT MeshObjectData3D: public ObjectData3D { public: /** * @brief Construct with combined transformation @@ -132,6 +144,11 @@ class MAGNUM_TRADE_EXPORT MeshObjectData3D: public ObjectData3D { Int _material, _skin; }; +CORRADE_IGNORE_DEPRECATED_POP /* MSVC warns on the inheritance */ + }} +#else +#error use Magnum/Trade/SceneData.h and the SceneData class instead +#endif #endif diff --git a/src/Magnum/Trade/ObjectData2D.cpp b/src/Magnum/Trade/ObjectData2D.cpp index e2a2cc826b..d192bac223 100644 --- a/src/Magnum/Trade/ObjectData2D.cpp +++ b/src/Magnum/Trade/ObjectData2D.cpp @@ -23,62 +23,85 @@ DEALINGS IN THE SOFTWARE. */ +#define _MAGNUM_NO_DEPRECATED_OBJECTDATA /* So it doesn't yell here */ + #include "ObjectData2D.h" #include namespace Magnum { namespace Trade { +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectData2D::ObjectData2D(std::vector children, const Matrix3& transformation, const ObjectInstanceType2D instanceType, const UnsignedInt instance, const void* const importerState): _children{std::move(children)}, _transformation{transformation}, _instanceType{instanceType}, _flags{}, _instance{Int(instance)}, _importerState{importerState} {} +CORRADE_IGNORE_DEPRECATED_POP +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectData2D::ObjectData2D(std::vector children, const Vector2& translation, const Complex& rotation, const Vector2& scaling, const ObjectInstanceType2D instanceType, const UnsignedInt instance, const void* const importerState): _children{std::move(children)}, _transformation{translation, rotation, scaling}, _instanceType{instanceType}, _flags{ObjectFlag2D::HasTranslationRotationScaling}, _instance{Int(instance)}, _importerState{importerState} {} +CORRADE_IGNORE_DEPRECATED_POP +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectData2D::ObjectData2D(std::vector children, const Matrix3& transformation, const void* const importerState): _children{std::move(children)}, _transformation{transformation}, _instanceType{ObjectInstanceType2D::Empty}, _flags{}, _instance{-1}, _importerState{importerState} {} +CORRADE_IGNORE_DEPRECATED_POP +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectData2D::ObjectData2D(std::vector children, const Vector2& translation, const Complex& rotation, const Vector2& scaling, const void* const importerState): _children{std::move(children)}, _transformation{translation, rotation, scaling}, _instanceType{ObjectInstanceType2D::Empty}, _flags{ObjectFlag2D::HasTranslationRotationScaling}, _instance{-1}, _importerState{importerState} {} +CORRADE_IGNORE_DEPRECATED_POP +CORRADE_IGNORE_DEPRECATED_PUSH /* MSVC warns here */ ObjectData2D::ObjectData2D(ObjectData2D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept #endif = default; +CORRADE_IGNORE_DEPRECATED_POP ObjectData2D::~ObjectData2D() = default; +CORRADE_IGNORE_DEPRECATED_PUSH /* GCC why you warn on return and not on param */ ObjectData2D& ObjectData2D::operator=(ObjectData2D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept #endif = default; +CORRADE_IGNORE_DEPRECATED_POP Vector2 ObjectData2D::translation() const { + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ CORRADE_ASSERT(_flags & ObjectFlag2D::HasTranslationRotationScaling, "Trade::ObjectData2D::translation(): object has only a combined transformation", {}); + CORRADE_IGNORE_DEPRECATED_POP return _transformation.trs.translation; } Complex ObjectData2D::rotation() const { + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ CORRADE_ASSERT(_flags & ObjectFlag2D::HasTranslationRotationScaling, "Trade::ObjectData2D::rotation(): object has only a combined transformation", {}); + CORRADE_IGNORE_DEPRECATED_POP return _transformation.trs.rotation; } Vector2 ObjectData2D::scaling() const { + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ CORRADE_ASSERT(_flags & ObjectFlag2D::HasTranslationRotationScaling, "Trade::ObjectData2D::scaling(): object has only a combined transformation", {}); + CORRADE_IGNORE_DEPRECATED_POP return _transformation.trs.scaling; } Matrix3 ObjectData2D::transformation() const { + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ if(_flags & ObjectFlag2D::HasTranslationRotationScaling) /* Has to be on a single line otherwise lcov reports an uncovered line. Ugh. */ return Matrix3::from(_transformation.trs.rotation.toMatrix(), _transformation.trs.translation)* Matrix3::scaling(_transformation.trs.scaling); + CORRADE_IGNORE_DEPRECATED_POP return _transformation.matrix; } #ifndef DOXYGEN_GENERATING_OUTPUT +CORRADE_IGNORE_DEPRECATED_PUSH Debug& operator<<(Debug& debug, const ObjectInstanceType2D value) { debug << "Trade::ObjectInstanceType2D" << Debug::nospace; @@ -113,6 +136,7 @@ Debug& operator<<(Debug& debug, const ObjectFlags2D value) { return enumSetDebugOutput(debug, value, "Trade::ObjectFlags2D{}", { ObjectFlag2D::HasTranslationRotationScaling}); } +CORRADE_IGNORE_DEPRECATED_POP #endif }} diff --git a/src/Magnum/Trade/ObjectData2D.h b/src/Magnum/Trade/ObjectData2D.h index 5023dedbed..8f50c129d8 100644 --- a/src/Magnum/Trade/ObjectData2D.h +++ b/src/Magnum/Trade/ObjectData2D.h @@ -25,10 +25,17 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MAGNUM_BUILD_DEPRECATED /** @file * @brief Class @ref Magnum::Trade::ObjectData2D, enum @ref Magnum::Trade::ObjectInstanceType2D + * @m_deprecated_since_latest Use @ref Magnum/Trade/SceneData.h and the + * @relativeref{Magnum::Trade,SceneData} class instead. */ +#endif + +#include "Magnum/configure.h" +#ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Magnum.h" @@ -36,14 +43,19 @@ #include "Magnum/Math/Complex.h" #include "Magnum/Trade/visibility.h" +#ifndef _MAGNUM_NO_DEPRECATED_OBJECTDATA +CORRADE_DEPRECATED_FILE("use Magnum/Trade/SceneData.h and the SceneData class instead") +#endif + namespace Magnum { namespace Trade { /** @brief Type of instance held by given 2D object +@m_deprecated_since_latest Use @ref SceneData instead. @see @ref ObjectData2D::instanceType() */ -enum class ObjectInstanceType2D: UnsignedByte { +enum class CORRADE_DEPRECATED_ENUM("use SceneData instead") ObjectInstanceType2D: UnsignedByte { Camera, /**< Camera instance (see @ref CameraData) */ /** @@ -57,10 +69,11 @@ enum class ObjectInstanceType2D: UnsignedByte { /** @brief 2D object flag +@m_deprecated_since_latest Use @ref SceneData instead. @see @ref ObjectFlags2D, @ref ObjectData2D::flags() */ -enum class ObjectFlag2D: UnsignedByte { +enum class CORRADE_DEPRECATED_ENUM("use SceneData instead") ObjectFlag2D: UnsignedByte { /** * The object provides separate translation / rotation / scaling * properties. The @ref ObjectData2D::transformation() matrix returns them @@ -73,21 +86,27 @@ enum class ObjectFlag2D: UnsignedByte { /** @brief 2D object flags +@m_deprecated_since_latest Use @ref SceneData instead. @see @ref ObjectData2D::flags() */ -typedef Containers::EnumSet ObjectFlags2D; +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ +typedef CORRADE_DEPRECATED("use SceneData instead") Containers::EnumSet ObjectFlags2D; +CORRADE_IGNORE_DEPRECATED_POP +CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ENUMSET_OPERATORS(ObjectFlags2D) +CORRADE_IGNORE_DEPRECATED_POP /** @brief Two-dimensional object data +@m_deprecated_since_latest Use @ref SceneData instead. Provides access to object transformation and hierarchy. @see @ref AbstractImporter::object2D(), @ref MeshObjectData2D, @ref ObjectData3D */ -class MAGNUM_TRADE_EXPORT ObjectData2D { +class CORRADE_DEPRECATED("use SceneData instead") MAGNUM_TRADE_EXPORT ObjectData2D { public: /** * @brief Construct with combined transformation @@ -97,7 +116,9 @@ class MAGNUM_TRADE_EXPORT ObjectData2D { * @param instance Instance ID * @param importerState Importer-specific state */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ explicit ObjectData2D(std::vector children, const Matrix3& transformation, ObjectInstanceType2D instanceType, UnsignedInt instance, const void* importerState = nullptr); + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Construct with separate transformations @@ -109,7 +130,9 @@ class MAGNUM_TRADE_EXPORT ObjectData2D { * @param instance Instance ID * @param importerState Importer-specific state */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ explicit ObjectData2D(std::vector children, const Vector2& translation, const Complex& rotation, const Vector2& scaling, ObjectInstanceType2D instanceType, UnsignedInt instance, const void* importerState = nullptr); + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Construct empty instance with combined transformation @@ -163,7 +186,9 @@ class MAGNUM_TRADE_EXPORT ObjectData2D { const std::vector& children() const { return _children; } /**< @overload */ /** @brief Flags */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectFlags2D flags() const { return _flags; } + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Translation (relative to parent) @@ -224,7 +249,9 @@ class MAGNUM_TRADE_EXPORT ObjectData2D { * * @see @ref instance() */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectInstanceType2D instanceType() const { return _instanceType; } + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Instance ID @@ -256,21 +283,37 @@ class MAGNUM_TRADE_EXPORT ObjectData2D { Vector2 scaling; } trs; } _transformation; + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectInstanceType2D _instanceType; ObjectFlags2D _flags; + CORRADE_IGNORE_DEPRECATED_POP Int _instance; const void* _importerState; }; -/** @debugoperatorenum{ObjectInstanceType2D} */ +CORRADE_IGNORE_DEPRECATED_PUSH +/** +@debugoperatorenum{ObjectInstanceType2D} +@m_deprecated_since_latest Use @ref SceneData instead. +*/ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, ObjectInstanceType2D value); -/** @debugoperatorenum{ObjectFlag2D} */ +/** +@debugoperatorenum{ObjectFlag2D} +@m_deprecated_since_latest Use @ref SceneData instead. +*/ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, ObjectFlag2D value); -/** @debugoperatorenum{ObjectFlags2D} */ +/** +@debugoperatorenum{ObjectFlags2D} +@m_deprecated_since_latest Use @ref SceneData instead. +*/ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, ObjectFlags2D value); +CORRADE_IGNORE_DEPRECATED_POP }} +#else +#error use Magnum/Trade/SceneData.h and the SceneData class instead +#endif #endif diff --git a/src/Magnum/Trade/ObjectData3D.cpp b/src/Magnum/Trade/ObjectData3D.cpp index 37d5b2f68d..b7eb50c0c5 100644 --- a/src/Magnum/Trade/ObjectData3D.cpp +++ b/src/Magnum/Trade/ObjectData3D.cpp @@ -23,62 +23,81 @@ DEALINGS IN THE SOFTWARE. */ +#define _MAGNUM_NO_DEPRECATED_OBJECTDATA /* So it doesn't yell here */ + #include "ObjectData3D.h" #include namespace Magnum { namespace Trade { +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectData3D::ObjectData3D(std::vector children, const Matrix4& transformation, const ObjectInstanceType3D instanceType, const UnsignedInt instance, const void* const importerState): _children{std::move(children)}, _transformation{transformation}, _instanceType{instanceType}, _flags{}, _instance{Int(instance)}, _importerState{importerState} {} +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectData3D::ObjectData3D(std::vector children, const Vector3& translation, const Quaternion& rotation, const Vector3& scaling, const ObjectInstanceType3D instanceType, const UnsignedInt instance, const void* const importerState): _children{std::move(children)}, _transformation{translation, rotation, scaling}, _instanceType{instanceType}, _flags{ObjectFlag3D::HasTranslationRotationScaling}, _instance{Int(instance)}, _importerState{importerState} {} +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectData3D::ObjectData3D(std::vector children, const Matrix4& transformation, const void* const importerState): _children{std::move(children)}, _transformation{transformation}, _instanceType{ObjectInstanceType3D::Empty}, _flags{}, _instance{-1}, _importerState{importerState} {} +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectData3D::ObjectData3D(std::vector children, const Vector3& translation, const Quaternion& rotation, const Vector3& scaling, const void* const importerState): _children{std::move(children)}, _transformation{translation, rotation, scaling}, _instanceType{ObjectInstanceType3D::Empty}, _flags{ObjectFlag3D::HasTranslationRotationScaling}, _instance{-1}, _importerState{importerState} {} +CORRADE_IGNORE_DEPRECATED_PUSH /* MSVC warns here */ ObjectData3D::ObjectData3D(ObjectData3D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept #endif = default; +CORRADE_IGNORE_DEPRECATED_POP ObjectData3D::~ObjectData3D() = default; +CORRADE_IGNORE_DEPRECATED_PUSH /* GCC why you warn on return and not on param */ ObjectData3D& ObjectData3D::operator=(ObjectData3D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept #endif = default; +CORRADE_IGNORE_DEPRECATED_POP Vector3 ObjectData3D::translation() const { + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ CORRADE_ASSERT(_flags & ObjectFlag3D::HasTranslationRotationScaling, "Trade::ObjectData3D::translation(): object has only a combined transformation", {}); + CORRADE_IGNORE_DEPRECATED_POP return _transformation.trs.translation; } Quaternion ObjectData3D::rotation() const { + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ CORRADE_ASSERT(_flags & ObjectFlag3D::HasTranslationRotationScaling, "Trade::ObjectData3D::rotation(): object has only a combined transformation", {}); + CORRADE_IGNORE_DEPRECATED_POP return _transformation.trs.rotation; } Vector3 ObjectData3D::scaling() const { + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ CORRADE_ASSERT(_flags & ObjectFlag3D::HasTranslationRotationScaling, "Trade::ObjectData3D::scaling(): object has only a combined transformation", {}); + CORRADE_IGNORE_DEPRECATED_POP return _transformation.trs.scaling; } Matrix4 ObjectData3D::transformation() const { + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ if(_flags & ObjectFlag3D::HasTranslationRotationScaling) /* Has to be on a single line otherwise lcov reports an uncovered line. Ugh. */ return Matrix4::from(_transformation.trs.rotation.toMatrix(), _transformation.trs.translation)* Matrix4::scaling(_transformation.trs.scaling); + CORRADE_IGNORE_DEPRECATED_POP return _transformation.matrix; } #ifndef DOXYGEN_GENERATING_OUTPUT +CORRADE_IGNORE_DEPRECATED_PUSH Debug& operator<<(Debug& debug, const ObjectInstanceType3D value) { debug << "Trade::ObjectInstanceType3D" << Debug::nospace; @@ -114,6 +133,7 @@ Debug& operator<<(Debug& debug, const ObjectFlags3D value) { return enumSetDebugOutput(debug, value, "Trade::ObjectFlags3D{}", { ObjectFlag3D::HasTranslationRotationScaling}); } +CORRADE_IGNORE_DEPRECATED_POP #endif }} diff --git a/src/Magnum/Trade/ObjectData3D.h b/src/Magnum/Trade/ObjectData3D.h index f2acd5b221..f96ab29659 100644 --- a/src/Magnum/Trade/ObjectData3D.h +++ b/src/Magnum/Trade/ObjectData3D.h @@ -25,10 +25,17 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MAGNUM_BUILD_DEPRECATED /** @file * @brief Class @ref Magnum::Trade::ObjectData3D, enum @ref Magnum::Trade::ObjectInstanceType3D + * @m_deprecated_since_latest Use @ref Magnum/Trade/SceneData.h and the + * @relativeref{Magnum::Trade,SceneData} class instead. */ +#endif + +#include "Magnum/configure.h" +#ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Magnum.h" @@ -36,14 +43,19 @@ #include "Magnum/Math/Quaternion.h" #include "Magnum/Trade/visibility.h" +#ifndef _MAGNUM_NO_DEPRECATED_OBJECTDATA +CORRADE_DEPRECATED_FILE("use Magnum/Trade/SceneData.h and the SceneData class instead") +#endif + namespace Magnum { namespace Trade { /** @brief Type of instance held by given 3D object +@m_deprecated_since_latest Use @ref SceneData instead. @see @ref ObjectData3D::instanceType() */ -enum class ObjectInstanceType3D: UnsignedByte { +enum class CORRADE_DEPRECATED_ENUM("use SceneData instead") ObjectInstanceType3D: UnsignedByte { Camera, /**< Camera instance (see @ref CameraData) */ Light, /**< Light instance (see @ref LightData) */ @@ -58,10 +70,11 @@ enum class ObjectInstanceType3D: UnsignedByte { /** @brief 3D object flag +@m_deprecated_since_latest Use @ref SceneData instead. @see @ref ObjectFlags3D, @ref ObjectData3D::flags() */ -enum class ObjectFlag3D: UnsignedByte { +enum class CORRADE_DEPRECATED_ENUM("use SceneData instead") ObjectFlag3D: UnsignedByte { /** * The object provides separate translation / rotation / scaling * properties. The @ref ObjectData3D::transformation() matrix returns them @@ -74,21 +87,27 @@ enum class ObjectFlag3D: UnsignedByte { /** @brief 3D object flags +@m_deprecated_since_latest Use @ref SceneData instead. @see @ref ObjectData3D::flags() */ -typedef Containers::EnumSet ObjectFlags3D; +CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ +typedef CORRADE_DEPRECATED("use SceneData instead") Containers::EnumSet ObjectFlags3D; +CORRADE_IGNORE_DEPRECATED_POP +CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ENUMSET_OPERATORS(ObjectFlags3D) +CORRADE_IGNORE_DEPRECATED_POP /** @brief Three-dimensional object data +@m_deprecated_since_latest Use @ref SceneData instead. Provides access to object transformation and hierarchy. @see @ref AbstractImporter::object3D(), @ref MeshObjectData3D, @ref ObjectData2D */ -class MAGNUM_TRADE_EXPORT ObjectData3D { +class CORRADE_DEPRECATED("use SceneData instead") MAGNUM_TRADE_EXPORT ObjectData3D { public: /** * @brief Construct with combined transformation @@ -98,7 +117,9 @@ class MAGNUM_TRADE_EXPORT ObjectData3D { * @param instance Instance ID * @param importerState Importer-specific state */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ explicit ObjectData3D(std::vector children, const Matrix4& transformation, ObjectInstanceType3D instanceType, UnsignedInt instance, const void* importerState = nullptr); + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Construct with separate transformations @@ -110,7 +131,9 @@ class MAGNUM_TRADE_EXPORT ObjectData3D { * @param instance Instance ID * @param importerState Importer-specific state */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ explicit ObjectData3D(std::vector children, const Vector3& translation, const Quaternion& rotation, const Vector3& scaling, ObjectInstanceType3D instanceType, UnsignedInt instance, const void* importerState = nullptr); + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Construct empty instance with combined transformation @@ -164,7 +187,9 @@ class MAGNUM_TRADE_EXPORT ObjectData3D { const std::vector& children() const { return _children; } /**< @overload */ /** @brief Flags */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectFlags3D flags() const { return _flags; } + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Translation (relative to parent) @@ -225,7 +250,9 @@ class MAGNUM_TRADE_EXPORT ObjectData3D { * * @see @ref instance() */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectInstanceType3D instanceType() const { return _instanceType; } + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Instance ID @@ -255,21 +282,37 @@ class MAGNUM_TRADE_EXPORT ObjectData3D { Vector3 scaling; } trs; } _transformation; + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ ObjectInstanceType3D _instanceType; ObjectFlags3D _flags; + CORRADE_IGNORE_DEPRECATED_POP Int _instance; const void* _importerState; }; -/** @debugoperatorenum{ObjectInstanceType3D} */ +CORRADE_IGNORE_DEPRECATED_PUSH +/** +@debugoperatorenum{ObjectInstanceType3D} +@m_deprecated_since_latest Use @ref SceneData instead. +*/ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, ObjectInstanceType3D value); -/** @debugoperatorenum{ObjectFlag3D} */ +/** +@debugoperatorenum{ObjectFlag3D} +@m_deprecated_since_latest Use @ref SceneData instead. +*/ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, ObjectFlag3D value); -/** @debugoperatorenum{ObjectFlags3D} */ +/** +@debugoperatorenum{ObjectFlags3D} +@m_deprecated_since_latest Use @ref SceneData instead. +*/ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, ObjectFlags3D value); +CORRADE_IGNORE_DEPRECATED_POP }} +#else +#error use Magnum/Trade/SceneData.h and the SceneData class instead +#endif #endif diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 44c6bf29e9..97294045a7 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -25,20 +25,2383 @@ #include "SceneData.h" +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" +#include "Magnum/Math/DualComplex.h" +#include "Magnum/Math/DualQuaternion.h" +#include "Magnum/Math/PackingBatch.h" +#include "Magnum/Trade/Implementation/arrayUtilities.h" + +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#include +#endif + namespace Magnum { namespace Trade { -SceneData::SceneData(std::vector children2D, std::vector children3D, const void* const importerState): _children2D{std::move(children2D)}, _children3D{std::move(children3D)}, _importerState{importerState} {} +Debug& operator<<(Debug& debug, const SceneMappingType value) { + debug << "Trade::SceneMappingType" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case SceneMappingType::value: return debug << "::" #value; + _c(UnsignedByte) + _c(UnsignedInt) + _c(UnsignedShort) + _c(UnsignedLong) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +UnsignedInt sceneMappingTypeSize(const SceneMappingType type) { + #ifdef CORRADE_TARGET_GCC + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" + #endif + switch(type) { + case SceneMappingType::UnsignedByte: return 1; + case SceneMappingType::UnsignedShort: return 2; + case SceneMappingType::UnsignedInt: return 4; + case SceneMappingType::UnsignedLong: return 8; + } + #ifdef CORRADE_TARGET_GCC + #pragma GCC diagnostic pop + #endif + + CORRADE_ASSERT_UNREACHABLE("Trade::sceneMappingTypeSize(): invalid type" << type, {}); +} + +UnsignedInt sceneMappingTypeAlignment(const SceneMappingType type) { + #ifdef CORRADE_TARGET_GCC + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" + #endif + switch(type) { + case SceneMappingType::UnsignedByte: return 1; + case SceneMappingType::UnsignedShort: return 2; + case SceneMappingType::UnsignedInt: return 4; + case SceneMappingType::UnsignedLong: return 8; + } + #ifdef CORRADE_TARGET_GCC + #pragma GCC diagnostic pop + #endif + + CORRADE_ASSERT_UNREACHABLE("Trade::sceneMappingTypeAlignment(): invalid type" << type, {}); +} + +Debug& operator<<(Debug& debug, const SceneField value) { + debug << "Trade::SceneField" << Debug::nospace; + + if(UnsignedInt(value) >= UnsignedInt(SceneField::Custom)) + return debug << "::Custom(" << Debug::nospace << (UnsignedInt(value) - UnsignedInt(SceneField::Custom)) << Debug::nospace << ")"; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case SceneField::value: return debug << "::" #value; + _c(Parent) + _c(Transformation) + _c(Translation) + _c(Rotation) + _c(Scaling) + _c(Mesh) + _c(MeshMaterial) + _c(Light) + _c(Camera) + _c(Skin) + _c(ImporterState) + #undef _c + /* LCOV_EXCL_STOP */ + + /* To silence compiler warning about unhandled values */ + case SceneField::Custom: CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; +} -SceneData::SceneData(SceneData&&) - #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 - noexcept +Debug& operator<<(Debug& debug, const SceneFieldType value) { + debug << "Trade::SceneFieldType" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case SceneFieldType::value: return debug << "::" #value; + _c(Float) + _c(Half) + _c(Double) + _c(UnsignedByte) + _c(Byte) + _c(UnsignedShort) + _c(Short) + _c(UnsignedInt) + _c(Int) + _c(UnsignedLong) + _c(Long) + _c(Vector2) + _c(Vector2h) + _c(Vector2d) + _c(Vector2ub) + _c(Vector2b) + _c(Vector2us) + _c(Vector2s) + _c(Vector2ui) + _c(Vector2i) + _c(Vector3) + _c(Vector3h) + _c(Vector3d) + _c(Vector3ub) + _c(Vector3b) + _c(Vector3us) + _c(Vector3s) + _c(Vector3ui) + _c(Vector3i) + _c(Vector4) + _c(Vector4h) + _c(Vector4d) + _c(Vector4ub) + _c(Vector4b) + _c(Vector4us) + _c(Vector4s) + _c(Vector4ui) + _c(Vector4i) + _c(Matrix2x2) + _c(Matrix2x2h) + _c(Matrix2x2d) + _c(Matrix2x3) + _c(Matrix2x3h) + _c(Matrix2x3d) + _c(Matrix2x4) + _c(Matrix2x4h) + _c(Matrix2x4d) + _c(Matrix3x2) + _c(Matrix3x2h) + _c(Matrix3x2d) + _c(Matrix3x3) + _c(Matrix3x3h) + _c(Matrix3x3d) + _c(Matrix3x4) + _c(Matrix3x4h) + _c(Matrix3x4d) + _c(Matrix4x2) + _c(Matrix4x2h) + _c(Matrix4x2d) + _c(Matrix4x3) + _c(Matrix4x3h) + _c(Matrix4x3d) + _c(Matrix4x4) + _c(Matrix4x4h) + _c(Matrix4x4d) + _c(Range1D) + _c(Range1Dh) + _c(Range1Dd) + _c(Range1Di) + _c(Range2D) + _c(Range2Dh) + _c(Range2Dd) + _c(Range2Di) + _c(Range3D) + _c(Range3Dh) + _c(Range3Dd) + _c(Range3Di) + _c(Complex) + _c(Complexd) + _c(DualComplex) + _c(DualComplexd) + _c(Quaternion) + _c(Quaterniond) + _c(DualQuaternion) + _c(DualQuaterniond) + _c(Deg) + _c(Degh) + _c(Degd) + _c(Rad) + _c(Radh) + _c(Radd) + _c(Pointer) + _c(MutablePointer) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedShort(value)) << Debug::nospace << ")"; +} + +UnsignedInt sceneFieldTypeSize(const SceneFieldType type) { + #ifdef CORRADE_TARGET_GCC + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" #endif - = default; + switch(type) { + case SceneFieldType::UnsignedByte: + case SceneFieldType::Byte: + return 1; + case SceneFieldType::UnsignedShort: + case SceneFieldType::Short: + case SceneFieldType::Half: + case SceneFieldType::Vector2ub: + case SceneFieldType::Vector2b: + case SceneFieldType::Degh: + case SceneFieldType::Radh: + return 2; + case SceneFieldType::Vector3ub: + case SceneFieldType::Vector3b: + return 3; + case SceneFieldType::UnsignedInt: + case SceneFieldType::Int: + case SceneFieldType::Float: + case SceneFieldType::Vector2us: + case SceneFieldType::Vector2s: + case SceneFieldType::Vector2h: + case SceneFieldType::Vector4ub: + case SceneFieldType::Vector4b: + case SceneFieldType::Range1Dh: + case SceneFieldType::Deg: + case SceneFieldType::Rad: + return 4; + case SceneFieldType::Vector3us: + case SceneFieldType::Vector3s: + case SceneFieldType::Vector3h: + return 6; + case SceneFieldType::UnsignedLong: + case SceneFieldType::Long: + case SceneFieldType::Double: + case SceneFieldType::Vector2: + case SceneFieldType::Vector2ui: + case SceneFieldType::Vector2i: + case SceneFieldType::Vector4us: + case SceneFieldType::Vector4s: + case SceneFieldType::Vector4h: + case SceneFieldType::Matrix2x2h: + case SceneFieldType::Range1D: + case SceneFieldType::Range1Di: + case SceneFieldType::Range2Dh: + case SceneFieldType::Complex: + case SceneFieldType::Degd: + case SceneFieldType::Radd: + return 8; + case SceneFieldType::Vector3: + case SceneFieldType::Vector3ui: + case SceneFieldType::Vector3i: + case SceneFieldType::Matrix2x3h: + case SceneFieldType::Matrix3x2h: + case SceneFieldType::Range3Dh: + return 12; + case SceneFieldType::Vector2d: + case SceneFieldType::Vector4: + case SceneFieldType::Vector4ui: + case SceneFieldType::Vector4i: + case SceneFieldType::Matrix2x2: + case SceneFieldType::Matrix2x4h: + case SceneFieldType::Matrix4x2h: + case SceneFieldType::Range1Dd: + case SceneFieldType::Range2D: + case SceneFieldType::Range2Di: + case SceneFieldType::Complexd: + case SceneFieldType::DualComplex: + case SceneFieldType::Quaternion: + return 16; + case SceneFieldType::Matrix3x3h: + return 18; + case SceneFieldType::Vector3d: + case SceneFieldType::Matrix2x3: + case SceneFieldType::Matrix3x4h: + case SceneFieldType::Matrix3x2: + case SceneFieldType::Matrix4x3h: + case SceneFieldType::Range3D: + case SceneFieldType::Range3Di: + return 24; + case SceneFieldType::Vector4d: + case SceneFieldType::Matrix2x2d: + case SceneFieldType::Matrix2x4: + case SceneFieldType::Matrix4x2: + case SceneFieldType::Matrix4x4h: + case SceneFieldType::Range2Dd: + case SceneFieldType::DualComplexd: + case SceneFieldType::Quaterniond: + case SceneFieldType::DualQuaternion: + return 32; + case SceneFieldType::Matrix3x3: + return 36; + case SceneFieldType::Matrix2x3d: + case SceneFieldType::Matrix3x4: + case SceneFieldType::Matrix3x2d: + case SceneFieldType::Matrix4x3: + case SceneFieldType::Range3Dd: + return 48; + case SceneFieldType::Matrix2x4d: + case SceneFieldType::Matrix4x2d: + case SceneFieldType::Matrix4x4: + case SceneFieldType::DualQuaterniond: + return 64; + case SceneFieldType::Matrix3x3d: + return 72; + case SceneFieldType::Matrix3x4d: + case SceneFieldType::Matrix4x3d: + return 96; + case SceneFieldType::Matrix4x4d: + return 128; + case SceneFieldType::Pointer: + case SceneFieldType::MutablePointer: + return sizeof(void*); + } + #ifdef CORRADE_TARGET_GCC + #pragma GCC diagnostic pop + #endif + + CORRADE_ASSERT_UNREACHABLE("Trade::sceneFieldTypeSize(): invalid type" << type, {}); +} + +UnsignedInt sceneFieldTypeAlignment(const SceneFieldType type) { + #ifdef CORRADE_TARGET_GCC + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" + #endif + switch(type) { + case SceneFieldType::UnsignedByte: + case SceneFieldType::Vector2ub: + case SceneFieldType::Vector3ub: + case SceneFieldType::Vector4ub: + case SceneFieldType::Byte: + case SceneFieldType::Vector2b: + case SceneFieldType::Vector3b: + case SceneFieldType::Vector4b: + return 1; + case SceneFieldType::UnsignedShort: + case SceneFieldType::Vector2us: + case SceneFieldType::Vector3us: + case SceneFieldType::Vector4us: + case SceneFieldType::Short: + case SceneFieldType::Vector2s: + case SceneFieldType::Vector3s: + case SceneFieldType::Vector4s: + case SceneFieldType::Half: + case SceneFieldType::Vector2h: + case SceneFieldType::Vector3h: + case SceneFieldType::Vector4h: + case SceneFieldType::Matrix2x2h: + case SceneFieldType::Matrix2x3h: + case SceneFieldType::Matrix2x4h: + case SceneFieldType::Matrix3x2h: + case SceneFieldType::Matrix3x3h: + case SceneFieldType::Matrix3x4h: + case SceneFieldType::Matrix4x2h: + case SceneFieldType::Matrix4x3h: + case SceneFieldType::Matrix4x4h: + case SceneFieldType::Range1Dh: + case SceneFieldType::Range2Dh: + case SceneFieldType::Range3Dh: + case SceneFieldType::Degh: + case SceneFieldType::Radh: + return 2; + case SceneFieldType::UnsignedInt: + case SceneFieldType::Vector2ui: + case SceneFieldType::Vector3ui: + case SceneFieldType::Vector4ui: + case SceneFieldType::Int: + case SceneFieldType::Vector2i: + case SceneFieldType::Vector3i: + case SceneFieldType::Vector4i: + case SceneFieldType::Float: + case SceneFieldType::Vector2: + case SceneFieldType::Vector3: + case SceneFieldType::Vector4: + case SceneFieldType::Matrix2x2: + case SceneFieldType::Matrix2x3: + case SceneFieldType::Matrix2x4: + case SceneFieldType::Matrix3x2: + case SceneFieldType::Matrix3x3: + case SceneFieldType::Matrix3x4: + case SceneFieldType::Matrix4x2: + case SceneFieldType::Matrix4x3: + case SceneFieldType::Matrix4x4: + case SceneFieldType::Range1Di: + case SceneFieldType::Range2Di: + case SceneFieldType::Range3Di: + case SceneFieldType::Range1D: + case SceneFieldType::Range2D: + case SceneFieldType::Range3D: + case SceneFieldType::Complex: + case SceneFieldType::Quaternion: + case SceneFieldType::DualComplex: + case SceneFieldType::DualQuaternion: + case SceneFieldType::Deg: + case SceneFieldType::Rad: + return 4; + case SceneFieldType::UnsignedLong: + case SceneFieldType::Long: + case SceneFieldType::Double: + case SceneFieldType::Vector2d: + case SceneFieldType::Vector3d: + case SceneFieldType::Vector4d: + case SceneFieldType::Matrix2x2d: + case SceneFieldType::Matrix2x3d: + case SceneFieldType::Matrix2x4d: + case SceneFieldType::Matrix3x2d: + case SceneFieldType::Matrix3x3d: + case SceneFieldType::Matrix3x4d: + case SceneFieldType::Matrix4x2d: + case SceneFieldType::Matrix4x3d: + case SceneFieldType::Matrix4x4d: + case SceneFieldType::Range1Dd: + case SceneFieldType::Range2Dd: + case SceneFieldType::Range3Dd: + case SceneFieldType::Complexd: + case SceneFieldType::Quaterniond: + case SceneFieldType::DualComplexd: + case SceneFieldType::DualQuaterniond: + case SceneFieldType::Degd: + case SceneFieldType::Radd: + return 8; + case SceneFieldType::Pointer: + case SceneFieldType::MutablePointer: + return sizeof(void*); + } + #ifdef CORRADE_TARGET_GCC + #pragma GCC diagnostic pop + #endif + + CORRADE_ASSERT_UNREACHABLE("Trade::sceneFieldTypeAlignment(): invalid type" << type, {}); +} -SceneData& SceneData::operator=(SceneData&&) - #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 - noexcept +Debug& operator<<(Debug& debug, const SceneFieldFlag value) { + debug << "Trade::SceneFieldFlag" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case SceneFieldFlag::value: return debug << "::" #value; + _c(OffsetOnly) + _c(ImplicitMapping) + _c(OrderedMapping) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedShort(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const SceneFieldFlags value) { + return Containers::enumSetDebugOutput(debug, value, "Trade::SceneFieldFlags{}", { + SceneFieldFlag::OffsetOnly, + SceneFieldFlag::ImplicitMapping, + /* This one is implied by ImplicitMapping, so has to be after */ + SceneFieldFlag::OrderedMapping + }); +} + +SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView2D& mappingData, const SceneFieldType fieldType, const Containers::StridedArrayView2D& fieldData, const UnsignedShort fieldArraySize, const SceneFieldFlags flags) noexcept: SceneFieldData{name, {}, Containers::StridedArrayView1D{{mappingData.data(), ~std::size_t{}}, mappingData.size()[0], mappingData.stride()[0]}, fieldType, Containers::StridedArrayView1D{{fieldData.data(), ~std::size_t{}}, fieldData.size()[0], fieldData.stride()[0]}, fieldArraySize, flags} { + /* Yes, this calls into a constexpr function defined in the header -- + because I feel that makes more sense than duplicating the full assert + logic */ + #ifndef CORRADE_NO_ASSERT + if(fieldArraySize) CORRADE_ASSERT(fieldData.empty()[0] || fieldData.size()[1] == sceneFieldTypeSize(fieldType)*fieldArraySize, + "Trade::SceneFieldData: second field view dimension size" << fieldData.size()[1] << "doesn't match" << fieldType << "and field array size" << fieldArraySize, ); + else CORRADE_ASSERT(fieldData.empty()[0] || fieldData.size()[1] == sceneFieldTypeSize(fieldType), + "Trade::SceneFieldData: second field view dimension size" << fieldData.size()[1] << "doesn't match" << fieldType, ); #endif - = default; + + if(mappingData.size()[1] == 8) _mappingType = SceneMappingType::UnsignedLong; + else if(mappingData.size()[1] == 4) _mappingType = SceneMappingType::UnsignedInt; + else if(mappingData.size()[1] == 2) _mappingType = SceneMappingType::UnsignedShort; + else if(mappingData.size()[1] == 1) _mappingType = SceneMappingType::UnsignedByte; + else CORRADE_ASSERT_UNREACHABLE("Trade::SceneFieldData: expected second mapping view dimension size 1, 2, 4 or 8 but got" << mappingData.size()[1], ); + + CORRADE_ASSERT(mappingData.isContiguous<1>(), "Trade::SceneFieldData: second mapping view dimension is not contiguous", ); + CORRADE_ASSERT(fieldData.isContiguous<1>(), "Trade::SceneFieldData: second field view dimension is not contiguous", ); +} + +Containers::Array sceneFieldDataNonOwningArray(const Containers::ArrayView view) { + /* Ugly, eh? */ + return Containers::Array{const_cast(view.data()), view.size(), reinterpret_cast(Implementation::nonOwnedArrayDeleter)}; +} + +SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mappingBound, Containers::Array&& data, Containers::Array&& fields, const void* const importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _mappingType{mappingType}, _dimensions{}, _mappingBound{mappingBound}, _importerState{importerState}, _fields{std::move(fields)}, _data{std::move(data)} { + /* Check that mapping type is large enough */ + CORRADE_ASSERT( + (mappingType == SceneMappingType::UnsignedByte && mappingBound <= 0xffull) || + (mappingType == SceneMappingType::UnsignedShort && mappingBound <= 0xffffull) || + (mappingType == SceneMappingType::UnsignedInt && mappingBound <= 0xffffffffull) || + mappingType == SceneMappingType::UnsignedLong, + "Trade::SceneData:" << mappingType << "is too small for" << mappingBound << "objects", ); + + #ifndef CORRADE_NO_ASSERT + /* Check various assumptions about field data */ + Math::BoolVector<12> fieldsPresent; /** @todo some constant for this */ + const UnsignedInt mappingTypeSize = sceneMappingTypeSize(_mappingType); + #endif + UnsignedInt transformationField = ~UnsignedInt{}; + UnsignedInt translationField = ~UnsignedInt{}; + UnsignedInt rotationField = ~UnsignedInt{}; + UnsignedInt scalingField = ~UnsignedInt{}; + #ifndef CORRADE_NO_ASSERT + UnsignedInt meshField = ~UnsignedInt{}; + UnsignedInt meshMaterialField = ~UnsignedInt{}; + UnsignedInt skinField = ~UnsignedInt{}; + #endif + for(std::size_t i = 0; i != _fields.size(); ++i) { + const SceneFieldData& field = _fields[i]; + + #ifndef CORRADE_NO_ASSERT + /* The mapping type has to be the same among all fields. Technically it + wouldn't need to be, but if there's 60k objects then using a 8bit + type for certain fields would mean only the first 256 objects can be + referenced, which makes no practical sense, and to improve the + situation there would need to be some additional per-field object + offset and ... it's simpler to just require the mapping type to be + large enough to reference all objects (checked outside of the loop + above) and that it's the same for all fields. This also makes it + more convenient for the user. */ + CORRADE_ASSERT(field._mappingType == _mappingType, + "Trade::SceneData: inconsistent mapping type, got" << field._mappingType << "for field" << i << "but expected" << _mappingType, ); + + /* We could check that object indices are in bounds, but that's rather + expensive. OTOH it's fine if field size is larger than object count, + as a single object can have multiple instances of the same field + attached, so checking that would be wrong. */ + + /* Check that there are only unique fields. To avoid a O(n^2) operation + always (or allocating a sorted field map), builtin fields are + checked against a map and only custom fields are checked in an + O(n^2) way with the assumption there isn't many of them (and that + they'll gradually become builtin). */ + if(!isSceneFieldCustom(_fields[i]._name)) { + CORRADE_INTERNAL_ASSERT(UnsignedInt(_fields[i]._name) < fieldsPresent.Size); + CORRADE_ASSERT(!fieldsPresent[UnsignedInt(_fields[i]._name)], + "Trade::SceneData: duplicate field" << _fields[i]._name, ); + fieldsPresent.set(UnsignedInt(_fields[i]._name), true); + } else for(std::size_t j = 0; j != i; ++j) { + CORRADE_ASSERT(_fields[j]._name != _fields[i]._name, + "Trade::SceneData: duplicate field" << _fields[i]._name, ); + } + + /* Check that both the mapping and field view fits into the provided + data array. If the field is empty, we don't check anything -- + accessing the memory would be invalid anyway and enforcing this + would lead to unnecessary friction with optional fields. */ + if(field._size) { + const UnsignedInt fieldTypeSize = sceneFieldTypeSize(field._fieldType)* + (field._fieldArraySize ? field._fieldArraySize : 1); + if(field._flags & SceneFieldFlag::OffsetOnly) { + const std::size_t mappingSize = field._mappingData.offset + (field._size - 1)*field._mappingStride + mappingTypeSize; + const std::size_t fieldSize = field._fieldData.offset + (field._size - 1)*field._fieldStride + fieldTypeSize; + CORRADE_ASSERT(mappingSize <= _data.size(), + "Trade::SceneData: offset-only mapping data of field" << i << "span" << mappingSize << "bytes but passed data array has only" << _data.size(), ); + CORRADE_ASSERT(fieldSize <= _data.size(), + "Trade::SceneData: offset-only field data of field" << i << "span" << fieldSize << "bytes but passed data array has only" << _data.size(), ); + } else { + const void* const mappingBegin = field._mappingData.pointer; + const void* const fieldBegin = field._fieldData.pointer; + const void* const mappingEnd = static_cast(field._mappingData.pointer) + (field._size - 1)*field._mappingStride + mappingTypeSize; + const void* const fieldEnd = static_cast(field._fieldData.pointer) + (field._size - 1)*field._fieldStride + fieldTypeSize; + CORRADE_ASSERT(mappingBegin >= _data.begin() && mappingEnd <= _data.end(), + "Trade::SceneData: mapping data [" << Debug::nospace << mappingBegin << Debug::nospace << ":" << Debug::nospace << mappingEnd << Debug::nospace << "] of field" << i << "are not contained in passed data array [" << Debug::nospace << static_cast(_data.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_data.end()) << Debug::nospace << "]", ); + CORRADE_ASSERT(fieldBegin >= _data.begin() && fieldEnd <= _data.end(), + "Trade::SceneData: field data [" << Debug::nospace << fieldBegin << Debug::nospace << ":" << Debug::nospace << fieldEnd << Debug::nospace << "] of field" << i << "are not contained in passed data array [" << Debug::nospace << static_cast(_data.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_data.end()) << Debug::nospace << "]", ); + } + } + #endif + + /* Remember TRS and mesh/material fields to figure out whether the + scene is 2D or 3D and check their object mapping consistency outside + of the loop below */ + if(_fields[i]._name == SceneField::Transformation) { + transformationField = i; + } else if(_fields[i]._name == SceneField::Translation) { + translationField = i; + } else if(_fields[i]._name == SceneField::Rotation) { + rotationField = i; + } else if(_fields[i]._name == SceneField::Scaling) { + scalingField = i; + } + #ifndef CORRADE_NO_ASSERT + else if(_fields[i]._name == SceneField::Mesh) { + meshField = i; + } else if(_fields[i]._name == SceneField::MeshMaterial) { + meshMaterialField = i; + } else if(_fields[i]._name == SceneField::Skin) { + skinField = i; + } + #endif + } + + #ifndef CORRADE_NO_ASSERT + /* Check that certain fields share the same object mapping. Printing as if + all would be pointers (and not offset-only), it's not worth the extra + effort just for an assert message. Also, compared to above, where + "begin" was always zero, here we're always comparing four values, so the + message for offset-only wouldn't be simpler either. */ + const auto checkFieldMappingDataMatch = [](const SceneFieldData& a, const SceneFieldData& b) { + const std::size_t mappingTypeSize = sceneMappingTypeSize(a._mappingType); + const void* const aBegin = a._mappingData.pointer; + const void* const bBegin = b._mappingData.pointer; + const void* const aEnd = static_cast(a._mappingData.pointer) + a._size*mappingTypeSize; + const void* const bEnd = static_cast(b._mappingData.pointer) + b._size*mappingTypeSize; + CORRADE_ASSERT(aBegin == bBegin && aEnd == bEnd, + "Trade::SceneData:" << b._name << "mapping data [" << Debug::nospace << bBegin << Debug::nospace << ":" << Debug::nospace << bEnd << Debug::nospace << "] is different from" << a._name << "mapping data [" << Debug::nospace << aBegin << Debug::nospace << ":" << Debug::nospace << aEnd << Debug::nospace << "]", ); + }; + + /* All present TRS fields should share the same object mapping */ + if(translationField != ~UnsignedInt{}) { + if(rotationField != ~UnsignedInt{}) + checkFieldMappingDataMatch(_fields[translationField], _fields[rotationField]); + if(scalingField != ~UnsignedInt{}) + checkFieldMappingDataMatch(_fields[translationField], _fields[scalingField]); + } + if(rotationField != ~UnsignedInt{} && scalingField != ~UnsignedInt{}) + checkFieldMappingDataMatch(_fields[rotationField], _fields[scalingField]); + + /* Mesh and materials also */ + if(meshField != ~UnsignedInt{} && meshMaterialField != ~UnsignedInt{}) + checkFieldMappingDataMatch(_fields[meshField], _fields[meshMaterialField]); + #endif + + /* Decide about dimensionality based on transformation type, if present */ + if(transformationField != ~UnsignedInt{}) { + const SceneFieldType fieldType = _fields[transformationField]._fieldType; + if(fieldType == SceneFieldType::Matrix3x3 || + fieldType == SceneFieldType::Matrix3x3d || + fieldType == SceneFieldType::Matrix3x2 || + fieldType == SceneFieldType::Matrix3x2d || + fieldType == SceneFieldType::DualComplex || + fieldType == SceneFieldType::DualComplexd) + _dimensions = 2; + else if(fieldType == SceneFieldType::Matrix4x4 || + fieldType == SceneFieldType::Matrix4x4d || + fieldType == SceneFieldType::Matrix4x3 || + fieldType == SceneFieldType::Matrix4x3d || + fieldType == SceneFieldType::DualQuaternion || + fieldType == SceneFieldType::DualQuaterniond) + _dimensions = 3; + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Use TRS fields to decide about dimensionality, if the transformation + field is not present. If it is, verify that they match it. */ + if(translationField != ~UnsignedInt{}) { + const SceneFieldType fieldType = _fields[translationField]._fieldType; + if(fieldType == SceneFieldType::Vector2 || + fieldType == SceneFieldType::Vector2d) { + CORRADE_ASSERT(!_dimensions || _dimensions == 2, + "Trade::SceneData: expected a 3D translation field but got" << fieldType, ); + _dimensions = 2; + } else if(fieldType == SceneFieldType::Vector3 || + fieldType == SceneFieldType::Vector3d) { + CORRADE_ASSERT(!_dimensions || _dimensions == 3, + "Trade::SceneData: expected a 2D translation field but got" << fieldType, ); + _dimensions = 3; + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + if(rotationField != ~UnsignedInt{}) { + const SceneFieldType fieldType = _fields[rotationField]._fieldType; + if(fieldType == SceneFieldType::Complex || + fieldType == SceneFieldType::Complexd) { + CORRADE_ASSERT(!_dimensions || _dimensions == 2, + "Trade::SceneData: expected a 3D rotation field but got" << fieldType, ); + _dimensions = 2; + } else if(fieldType == SceneFieldType::Quaternion || + fieldType == SceneFieldType::Quaterniond) { + CORRADE_ASSERT(!_dimensions || _dimensions == 3, + "Trade::SceneData: expected a 2D rotation field but got" << fieldType, ); + _dimensions = 3; + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + if(scalingField != ~UnsignedInt{}) { + const SceneFieldType fieldType = _fields[scalingField]._fieldType; + if(fieldType == SceneFieldType::Vector2 || + fieldType == SceneFieldType::Vector2d) { + CORRADE_ASSERT(!_dimensions || _dimensions == 2, + "Trade::SceneData: expected a 3D scaling field but got" << fieldType, ); + _dimensions = 2; + } else if(fieldType == SceneFieldType::Vector3 || + fieldType == SceneFieldType::Vector3d) { + CORRADE_ASSERT(!_dimensions || _dimensions == 3, + "Trade::SceneData: expected a 2D scaling field but got" << fieldType, ); + _dimensions = 3; + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* A skin field requires some transformation field to exist in order to + disambiguate between 2D and 3D */ + CORRADE_ASSERT(skinField == ~UnsignedInt{} || _dimensions, + "Trade::SceneData: a skin field requires some transformation field to be present in order to disambiguate between 2D and 3D", ); +} + +SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mappingBound, Containers::Array&& data, const std::initializer_list fields, const void* const importerState): SceneData{mappingType, mappingBound, std::move(data), Implementation::initializerListToArrayWithDefaultDeleter(fields), importerState} {} + +SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mappingBound, const DataFlags dataFlags, const Containers::ArrayView data, Containers::Array&& fields, const void* const importerState) noexcept: SceneData{mappingType, mappingBound, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, std::move(fields), importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::SceneData: can't construct with non-owned data but" << dataFlags, ); + _dataFlags = dataFlags; +} + +SceneData::SceneData(const SceneMappingType mappingType, const UnsignedLong mappingBound, const DataFlags dataFlags, const Containers::ArrayView data, const std::initializer_list fields, const void* const importerState): SceneData{mappingType, mappingBound, dataFlags, data, Implementation::initializerListToArrayWithDefaultDeleter(fields), importerState} {} + +#ifdef MAGNUM_BUILD_DEPRECATED +SceneData::SceneData(std::vector children2D, std::vector children3D, const void* const importerState): _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _mappingType {SceneMappingType::UnsignedInt}, _importerState{importerState} { + /* Assume nobody ever created a scene with both 2D and 3D children. + (PrimitiveImporter did, but that's an exception and blame goes on me.) + If this blows up for you, please complain. Or rather upgrade to the new + API which ... forces you to have a separate scene for 2D and 3D. But you + can share the data among the two, at least. */ + CORRADE_ASSERT(children2D.empty() || children3D.empty(), + "Trade::SceneData: it's no longer possible to have a scene with both 2D and 3D objects", ); + Containers::ArrayView children; + if(!children2D.empty()) { + _dimensions = 2; + children = children2D; + } else if(!children3D.empty()) { + _dimensions = 3; + children = children3D; + } else _dimensions = 0; + + /* Set mapping bound to the max found child index. It's not great as it + doesn't take any nested object into account but SceneData created this + way is expected to be used only through the deprecated APIs anyway, + which don't care about this value. */ + _mappingBound = children.empty() ? 0 : Math::max(children) + 1; + + /* Convert the vector with top-level object IDs to the parent field, where + all have -1 as a parent. This way the (also deprecated) children2D() / + children3D() will return the desired values. */ + Containers::ArrayView mapping; + Containers::ArrayView parents; + _data = Containers::ArrayTuple{ + {NoInit, children.size(), mapping}, + {NoInit, children.size(), parents}, + }; + /* Can't use InPlaceInit as that creates an Array with a non-default + deleter, which then trips up on an assertion when such an instance gets + returned from AbstractImporter */ + _fields = Containers::Array{1}; + _fields[0] = SceneFieldData{SceneField::Parent, mapping, parents}; + Utility::copy(children, mapping); + constexpr Int parent[]{-1}; + Utility::copy(Containers::stridedArrayView(parent).broadcasted<0>(parents.size()), parents); +} +#endif + +SceneData::SceneData(SceneData&&) noexcept = default; + +SceneData::~SceneData() = default; + +SceneData& SceneData::operator=(SceneData&&) noexcept = default; + +Containers::ArrayView SceneData::mutableData() & { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::SceneData::mutableData(): data not mutable", {}); + return _data; +} + +Containers::StridedArrayView1D SceneData::fieldDataMappingViewInternal(const SceneFieldData& field, const std::size_t offset, const std::size_t size) const { + CORRADE_INTERNAL_ASSERT(offset + size <= field._size); + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + {static_cast(field._flags & SceneFieldFlag::OffsetOnly ? + _data.data() + field._mappingData.offset : field._mappingData.pointer) + + field._mappingStride*offset, ~std::size_t{}}, + size, field._mappingStride + }; +} + +Containers::StridedArrayView1D SceneData::fieldDataMappingViewInternal(const SceneFieldData& field) const { + return fieldDataMappingViewInternal(field, 0, field._size); +} + +Containers::StridedArrayView1D SceneData::fieldDataFieldViewInternal(const SceneFieldData& field, const std::size_t offset, const std::size_t size) const { + CORRADE_INTERNAL_ASSERT(offset + size <= field._size); + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + {static_cast(field._flags & SceneFieldFlag::OffsetOnly ? + _data.data() + field._fieldData.offset : field._fieldData.pointer) + + field._fieldStride*offset, ~std::size_t{}}, + size, field._fieldStride}; +} + +Containers::StridedArrayView1D SceneData::fieldDataFieldViewInternal(const SceneFieldData& field) const { + return fieldDataFieldViewInternal(field, 0, field._size); +} + +SceneFieldData SceneData::fieldData(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldData(): index" << id << "out of range for" << _fields.size() << "fields", SceneFieldData{}); + const SceneFieldData& field = _fields[id]; + return SceneFieldData{field._name, field._mappingType, fieldDataMappingViewInternal(field), field._fieldType, fieldDataFieldViewInternal(field), field._fieldArraySize, field._flags & ~SceneFieldFlag::OffsetOnly}; +} + +SceneField SceneData::fieldName(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldName(): index" << id << "out of range for" << _fields.size() << "fields", {}); + return _fields[id]._name; +} + +SceneFieldFlags SceneData::fieldFlags(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldFlags(): index" << id << "out of range for" << _fields.size() << "fields", {}); + return _fields[id]._flags; +} + +SceneFieldType SceneData::fieldType(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldType(): index" << id << "out of range for" << _fields.size() << "fields", {}); + return _fields[id]._fieldType; +} + +std::size_t SceneData::fieldSize(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldSize(): index" << id << "out of range for" << _fields.size() << "fields", {}); + return _fields[id]._size; +} + +UnsignedShort SceneData::fieldArraySize(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldArraySize(): index" << id << "out of range for" << _fields.size() << "fields", {}); + return _fields[id]._fieldArraySize; +} + +UnsignedInt SceneData::findFieldIdInternal(const SceneField name) const { + for(std::size_t i = 0; i != _fields.size(); ++i) + if(_fields[i]._name == name) return i; + return ~UnsignedInt{}; +} + +Containers::Optional SceneData::findFieldId(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + return fieldId == ~UnsignedInt{} ? Containers::Optional{} : fieldId; +} + +UnsignedInt SceneData::fieldId(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldId(): field" << name << "not found", {}); + return fieldId; +} + +bool SceneData::hasField(const SceneField name) const { + return findFieldIdInternal(name) != ~UnsignedInt{}; +} + +namespace { + +/* The `objects` view is already adjusted for `offset`, the offset is needed + only to return the correct value for ImplicitMapping */ +template std::size_t findObject(const SceneFieldFlags flags, const Containers::StridedArrayView1D& mapping, const std::size_t offset, const UnsignedLong object) { + const std::size_t max = mapping.size(); + + /* Implicit mapping, position equals object ID (if in bounds) and thus an + O(1)-complexity search. A superset of OrderedMapping so has to be + before. */ + if(flags >= SceneFieldFlag::ImplicitMapping) + return object >= offset && object - offset < mapping.size() ? + object - offset : max; + + const Containers::StridedArrayView1D mappingT = Containers::arrayCast(mapping); + + /* Ordered mapping, so an O(log n)-complexity search. It also has to be + noted that STL algorithms generally suck (this needs a + std::iterator_traits specialization for the StridedArrayView, FFS!), + std::binary_search() is useless because it returns just a bool (!!) and + std::lower_bound() is error prone beyond any reason. */ + if(flags >= SceneFieldFlag::OrderedMapping) { + const Containers::StridedIterator<1, const T> found = std::lower_bound(mappingT.begin(), mappingT.end(), T(object)); + if(found == mappingT.end() || *found != object) return max; + return found - mappingT.begin(); + } + + /* Generally unordered container, O(n)-complexity search. */ + for(std::size_t i = 0; i != max; ++i) + if(mappingT[i] == object) return i; + return max; +} + +} + +std::size_t SceneData::findFieldObjectOffsetInternal(const SceneFieldData& field, const UnsignedLong object, const std::size_t offset) const { + const Containers::StridedArrayView1D mapping = fieldDataMappingViewInternal(field, offset, field._size - offset); + if(field._mappingType == SceneMappingType::UnsignedInt) + return offset + findObject(field._flags, mapping, offset, object); + else if(field._mappingType == SceneMappingType::UnsignedShort) + return offset + findObject(field._flags, mapping, offset, object); + else if(field._mappingType == SceneMappingType::UnsignedByte) + return offset + findObject(field._flags, mapping, offset, object); + else if(field._mappingType == SceneMappingType::UnsignedLong) + return offset + findObject(field._flags, mapping, offset, object); + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Optional SceneData::findFieldObjectOffset(const UnsignedInt fieldId, const UnsignedLong object, const std::size_t offset) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::findFieldObjectOffset(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::findFieldObjectOffset(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + + const SceneFieldData& field = _fields[fieldId]; + CORRADE_ASSERT(offset <= field._size, + "Trade::SceneData::findFieldObjectOffset(): offset" << offset << "out of bounds for a field of size" << field._size, {}); + + const std::size_t found = findFieldObjectOffsetInternal(field, object, offset); + return found == field._size ? Containers::Optional{} : found; +} + +Containers::Optional SceneData::findFieldObjectOffset(const SceneField fieldName, const UnsignedLong object, const std::size_t offset) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::findFieldObjectOffset(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + const UnsignedInt fieldId = findFieldIdInternal(fieldName); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::findFieldObjectOffset(): field" << fieldName << "not found", {}); + + const SceneFieldData& field = _fields[fieldId]; + CORRADE_ASSERT(offset <= field._size, + "Trade::SceneData::findFieldObjectOffset(): offset" << offset << "out of bounds for a field of size" << field._size, {}); + + const std::size_t found = findFieldObjectOffsetInternal(field, object, offset); + return found == field._size ? Containers::Optional{} : found; +} + +std::size_t SceneData::fieldObjectOffset(const UnsignedInt fieldId, const UnsignedLong object, const std::size_t offset) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::fieldObjectOffset(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::fieldObjectOffset(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + + const SceneFieldData& field = _fields[fieldId]; + CORRADE_ASSERT(offset <= field._size, + "Trade::SceneData::fieldObjectOffset(): offset" << offset << "out of bounds for a field of size" << field._size, {}); + + const std::size_t found = findFieldObjectOffsetInternal(field, object, offset); + CORRADE_ASSERT(found != field._size, + "Trade::SceneData::fieldObjectOffset(): object" << object << "not found in field" << field._name << "starting at offset" << offset, {}); + return found; +} + +std::size_t SceneData::fieldObjectOffset(const SceneField fieldName, const UnsignedLong object, const std::size_t offset) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::fieldObjectOffset(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + const UnsignedInt fieldId = findFieldIdInternal(fieldName); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::fieldObjectOffset(): field" << fieldName << "not found", {}); + + const SceneFieldData& field = _fields[fieldId]; + CORRADE_ASSERT(offset <= field._size, + "Trade::SceneData::fieldObjectOffset(): offset" << offset << "out of bounds for a field of size" << field._size, {}); + + const std::size_t found = findFieldObjectOffsetInternal(field, object, offset); + CORRADE_ASSERT(found != field._size, + "Trade::SceneData::fieldObjectOffset(): object" << object << "not found in field" << field._name << "starting at offset" << offset, {}); + return found; +} + +bool SceneData::hasFieldObject(const UnsignedInt fieldId, const UnsignedLong object) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::hasFieldObject(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::hasFieldObject(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + + const SceneFieldData& field = _fields[fieldId]; + return findFieldObjectOffsetInternal(field, object, 0) != field._size; +} + +bool SceneData::hasFieldObject(const SceneField fieldName, const UnsignedLong object) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::hasFieldObject(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + const UnsignedInt fieldId = findFieldIdInternal(fieldName); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::hasFieldObject(): field" << fieldName << "not found", {}); + + const SceneFieldData& field = _fields[fieldId]; + return findFieldObjectOffsetInternal(field, object, 0) != field._size; +} + +SceneFieldFlags SceneData::fieldFlags(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldFlags(): field" << name << "not found", {}); + return _fields[fieldId]._flags; +} + +SceneFieldType SceneData::fieldType(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldType(): field" << name << "not found", {}); + return _fields[fieldId]._fieldType; +} + +std::size_t SceneData::fieldSize(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldSize(): field" << name << "not found", {}); + return _fields[fieldId]._size; +} + +UnsignedShort SceneData::fieldArraySize(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldArraySize(): field" << name << "not found", {}); + return _fields[fieldId]._fieldArraySize; +} + +Containers::StridedArrayView2D SceneData::mapping(const UnsignedInt fieldId) const { + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::mapping(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[fieldId]; + /* Build a 2D view using information about mapping type size */ + return Containers::arrayCast<2, const char>( + fieldDataMappingViewInternal(field), + sceneMappingTypeSize(field._mappingType)); +} + +Containers::StridedArrayView2D SceneData::mutableMapping(const UnsignedInt fieldId) { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::SceneData::mutableMapping(): data not mutable", {}); + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::mutableMapping(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[fieldId]; + /* Build a 2D view using information about attribute type size */ + const auto out = Containers::arrayCast<2, const char>( + fieldDataMappingViewInternal(field), + sceneMappingTypeSize(field._mappingType)); + /** @todo some arrayConstCast? UGH */ + return Containers::StridedArrayView2D{ + /* The view size is there only for a size assert, we're pretty sure the + view is valid */ + {static_cast(const_cast(out.data())), ~std::size_t{}}, + out.size(), out.stride()}; +} + +Containers::StridedArrayView2D SceneData::mapping(const SceneField fieldName) const { + const UnsignedInt fieldId = findFieldIdInternal(fieldName); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::mapping(): field" << fieldName << "not found", {}); + return mapping(fieldId); +} + +Containers::StridedArrayView2D SceneData::mutableMapping(const SceneField fieldName) { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::SceneData::mutableMapping(): data not mutable", {}); + const UnsignedInt fieldId = findFieldIdInternal(fieldName); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::mutableMapping(): field" << fieldName << "not found", {}); + return mutableMapping(fieldId); +} + +Containers::StridedArrayView2D SceneData::field(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::field(): index" << id << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[id]; + /* Build a 2D view using information about mapping type size */ + return Containers::arrayCast<2, const char>( + fieldDataFieldViewInternal(field), + sceneFieldTypeSize(field._fieldType)*(field._fieldArraySize ? field._fieldArraySize : 1)); +} + +Containers::StridedArrayView2D SceneData::mutableField(const UnsignedInt id) { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::SceneData::mutableField(): data not mutable", {}); + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::mutableField(): index" << id << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[id]; + /* Build a 2D view using information about attribute type size */ + const auto out = Containers::arrayCast<2, const char>( + fieldDataFieldViewInternal(field), + sceneFieldTypeSize(field._fieldType)*(field._fieldArraySize ? field._fieldArraySize : 1)); + /** @todo some arrayConstCast? UGH */ + return Containers::StridedArrayView2D{ + /* The view size is there only for a size assert, we're pretty sure the + view is valid */ + {static_cast(const_cast(out.data())), ~std::size_t{}}, + out.size(), out.stride()}; +} + +Containers::StridedArrayView2D SceneData::field(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::field(): field" << name << "not found", {}); + return field(fieldId); +} + +Containers::StridedArrayView2D SceneData::mutableField(const SceneField name) { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::SceneData::mutableField(): data not mutable", {}); + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::mutableField(): field" << name << "not found", {}); + return mutableField(fieldId); +} + +void SceneData::mappingIntoInternal(const UnsignedInt fieldId, const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + /* fieldId, offset and destination.size() is assumed to be in bounds, + checked by the callers */ + + const SceneFieldData& field = _fields[fieldId]; + const Containers::StridedArrayView1D mappingData = fieldDataMappingViewInternal(field, offset, destination.size()); + const auto destination1ui = Containers::arrayCast<2, UnsignedInt>(destination); + + if(field._mappingType == SceneMappingType::UnsignedInt) + Utility::copy(Containers::arrayCast(mappingData), destination); + else if(field._mappingType == SceneMappingType::UnsignedShort) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(mappingData, 1), destination1ui); + else if(field._mappingType == SceneMappingType::UnsignedByte) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(mappingData, 1), destination1ui); + else if(field._mappingType == SceneMappingType::UnsignedLong) { + Math::castInto(Containers::arrayCast<2, const UnsignedLong>(mappingData, 1), destination1ui); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +void SceneData::mappingInto(const UnsignedInt fieldId, const Containers::StridedArrayView1D& destination) const { + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::mappingInto(): index" << fieldId << "out of range for" << _fields.size() << "fields", ); + CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, + "Trade::SceneData::mappingInto(): expected a view with" << _fields[fieldId]._size << "elements but got" << destination.size(), ); + mappingIntoInternal(fieldId, 0, destination); +} + +std::size_t SceneData::mappingInto(const UnsignedInt fieldId, const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::mappingInto(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + CORRADE_ASSERT(offset <= _fields[fieldId]._size, + "Trade::SceneData::mappingInto(): offset" << offset << "out of bounds for a field of size" << _fields[fieldId]._size, {}); + const std::size_t size = Math::min(destination.size(), std::size_t(_fields[fieldId]._size) - offset); + mappingIntoInternal(fieldId, offset, destination.prefix(size)); + return size; +} + +Containers::Array SceneData::mappingAsArray(const UnsignedInt fieldId) const { + CORRADE_ASSERT(fieldId < _fields.size(), + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::mappingInto(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + Containers::Array out{NoInit, std::size_t(_fields[fieldId]._size)}; + mappingIntoInternal(fieldId, 0, out); + return out; +} + +void SceneData::mappingInto(const SceneField name, const Containers::StridedArrayView1D& destination) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::mappingInto(): field" << name << "not found", ); + mappingInto(fieldId, destination); +} + +std::size_t SceneData::mappingInto(const SceneField name, std::size_t offset, const Containers::StridedArrayView1D& destination) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::mappingInto(): field" << name << "not found", {}); + return mappingInto(fieldId, offset, destination); +} + +Containers::Array SceneData::mappingAsArray(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::mappingInto(): field" << name << "not found", {}); + return mappingAsArray(fieldId); +} + +void SceneData::parentsIntoInternal(const UnsignedInt fieldId, const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + /* fieldId, offset and destination.size() is assumed to be in bounds, + checked by the callers */ + + const SceneFieldData& field = _fields[fieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); + const auto destination1i = Containers::arrayCast<2, Int>(destination); + + if(field._fieldType == SceneFieldType::Int) + Utility::copy(Containers::arrayCast(fieldData), destination); + else if(field._fieldType == SceneFieldType::Short) + Math::castInto(Containers::arrayCast<2, const Short>(fieldData, 1), destination1i); + else if(field._fieldType == SceneFieldType::Byte) + Math::castInto(Containers::arrayCast<2, const Byte>(fieldData, 1), destination1i); + else if(field._fieldType == SceneFieldType::Long) { + Math::castInto(Containers::arrayCast<2, const Long>(fieldData, 1), destination1i); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +void SceneData::parentsInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Parent); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::parentsInto(): field not found", ); + CORRADE_ASSERT(!mappingDestination || mappingDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::parentsInto(): expected mapping destination view either empty or with" << _fields[fieldId]._size << "elements but got" << mappingDestination.size(), ); + CORRADE_ASSERT(!fieldDestination || fieldDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::parentsInto(): expected field destination view either empty or with" << _fields[fieldId]._size << "elements but got" << fieldDestination.size(), ); + mappingIntoInternal(fieldId, 0, mappingDestination); + parentsIntoInternal(fieldId, 0, fieldDestination); +} + +std::size_t SceneData::parentsInto(const std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Parent); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::parentsInto(): field not found", {}); + const std::size_t fieldSize = _fields[fieldId]._size; + CORRADE_ASSERT(offset <= fieldSize, + "Trade::SceneData::parentsInto(): offset" << offset << "out of bounds for a field of size" << fieldSize, {}); + CORRADE_ASSERT(!mappingDestination != !fieldDestination|| mappingDestination.size() == fieldDestination.size(), + "Trade::SceneData::parentsInto(): mapping and field destination views have different size," << mappingDestination.size() << "vs" << fieldDestination.size(), {}); + const std::size_t size = Math::min(Math::max(mappingDestination.size(), fieldDestination.size()), fieldSize - offset); + if(mappingDestination) mappingIntoInternal(fieldId, offset, mappingDestination.prefix(size)); + if(fieldDestination) parentsIntoInternal(fieldId, offset, fieldDestination.prefix(size)); + return size; +} + +Containers::Array> SceneData::parentsAsArray() const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Parent); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::parentsInto(): field not found", {}); + Containers::Array> out{NoInit, std::size_t(_fields[fieldId]._size)}; + /** @todo use slicing once Pair exposes members somehow */ + mappingIntoInternal(fieldId, 0, {out, reinterpret_cast(reinterpret_cast(out.data())), out.size(), sizeof(decltype(out)::Type)}); + parentsIntoInternal(fieldId, 0, {out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(UnsignedInt)), out.size(), sizeof(decltype(out)::Type)}); + return out; +} + +namespace { + +template void convertTransformation(const Containers::StridedArrayView1D& source, const Containers::StridedArrayView1D& destination) { + CORRADE_INTERNAL_ASSERT(source.size() == destination.size()); + const auto sourceT = Containers::arrayCast(source); + for(std::size_t i = 0; i != sourceT.size(); ++i) + destination[i] = Destination{sourceT[i].toMatrix()}; +} + +/* Expands 2x3 to 3x3, 4x3 to 4x4, while potentially also converting floats to + doubles */ +/** @todo also have some optimized batched API for this? doing a strided copy + and a fill seemed silly as that would cause the whole memory go through + cache twice */ +template void expandTransformationMatrix(const Containers::StridedArrayView1D& source, const Containers::StridedArrayView1D& destination) { + CORRADE_INTERNAL_ASSERT(source.size() == destination.size()); + const auto sourceT = Containers::arrayCast(source); + for(std::size_t i = 0; i != sourceT.size(); ++i) + destination[i] = Destination{Math::RectangularMatrix{sourceT[i]}}; +} + +/** @todo these (or the float variants at least) should eventually be replaced + with optimized batched APIs (applyTranslationsInto() updating just the + last matrix column etc.) */ + +template void applyTranslation(const Containers::StridedArrayView1D& source, const Containers::StridedArrayView1D& destination) { + CORRADE_INTERNAL_ASSERT(source.size() == destination.size()); + const auto sourceT = Containers::arrayCast(source); + for(std::size_t i = 0; i != sourceT.size(); ++i) + destination[i] = Destination::translation(Math::Vector{sourceT[i]})*destination[i]; +} + +template void applyRotation(const Containers::StridedArrayView1D& source, const Containers::StridedArrayView1D& destination) { + CORRADE_INTERNAL_ASSERT(source.size() == destination.size()); + const auto sourceT = Containers::arrayCast(source); + for(std::size_t i = 0; i != sourceT.size(); ++i) + destination[i] = Destination{Math::Matrix{ sourceT[i].toMatrix()}}*destination[i]; +} + +template void applyScaling(const Containers::StridedArrayView1D& source, const Containers::StridedArrayView1D& destination) { + CORRADE_INTERNAL_ASSERT(source.size() == destination.size()); + const auto sourceT = Containers::arrayCast(source); + for(std::size_t i = 0; i != sourceT.size(); ++i) + destination[i] = Destination::scaling(Math::Vector{sourceT[i]})*destination[i]; +} + +} + +UnsignedInt SceneData::findTransformFields(UnsignedInt& transformationFieldId, UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId) const { + UnsignedInt fieldWithObjectMapping = ~UnsignedInt{}; + transformationFieldId = ~UnsignedInt{}; + translationFieldId = ~UnsignedInt{}; + rotationFieldId = ~UnsignedInt{}; + scalingFieldId = ~UnsignedInt{}; + for(std::size_t i = 0; i != _fields.size(); ++i) { + /* If we find a transformation field, we don't need to look any + further */ + if(_fields[i]._name == SceneField::Transformation) { + fieldWithObjectMapping = transformationFieldId = i; + break; + } else if(_fields[i]._name == SceneField::Translation) { + fieldWithObjectMapping = translationFieldId = i; + } else if(_fields[i]._name == SceneField::Rotation) { + fieldWithObjectMapping = rotationFieldId = i; + } else if(_fields[i]._name == SceneField::Scaling) { + fieldWithObjectMapping = scalingFieldId = i; + } + } + + return fieldWithObjectMapping; +} + +UnsignedInt SceneData::findTranslationRotationScalingFields(UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId) const { + UnsignedInt fieldWithObjectMapping = ~UnsignedInt{}; + translationFieldId = ~UnsignedInt{}; + rotationFieldId = ~UnsignedInt{}; + scalingFieldId = ~UnsignedInt{}; + for(std::size_t i = 0; i != _fields.size(); ++i) { + if(_fields[i]._name == SceneField::Translation) { + fieldWithObjectMapping = translationFieldId = i; + } else if(_fields[i]._name == SceneField::Rotation) { + fieldWithObjectMapping = rotationFieldId = i; + } else if(_fields[i]._name == SceneField::Scaling) { + fieldWithObjectMapping = scalingFieldId = i; + } + } + + return fieldWithObjectMapping; +} + +void SceneData::transformations2DIntoInternal(const UnsignedInt transformationFieldId, const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const { + /* *FieldId, offset and destination.size() is assumed to be in bounds (or + an invalid field ID), checked by the callers */ + + /* If is2D() returned false as well, all *FieldId would be invalid, which + the caller is assumed to check. */ + CORRADE_ASSERT(!is3D(), "Trade::SceneData::transformations2DInto(): scene has a 3D transformation type", ); + + /** @todo apply scalings as well if dual complex? */ + + /* Prefer the transformation field, if present */ + if(transformationFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[transformationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); + const auto destination1f = Containers::arrayCast<2, Float>(destination); + + if(field._fieldType == SceneFieldType::Matrix3x3) { + Utility::copy(Containers::arrayCast(fieldData), destination); + } else if(field._fieldType == SceneFieldType::Matrix3x3d) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 9), destination1f); + } else if(field._fieldType == SceneFieldType::Matrix3x2) { + expandTransformationMatrix(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Matrix3x2d) { + expandTransformationMatrix(fieldData, destination); + } else if(field._fieldType == SceneFieldType::DualComplex) { + convertTransformation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::DualComplexd) { + convertTransformation(fieldData, destination); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + /* If not, combine from TRS components */ + } else if(translationFieldId != ~UnsignedInt{} || rotationFieldId != ~UnsignedInt{} || scalingFieldId != ~UnsignedInt{}) { + /* First fill the destination with identity matrices */ + const Matrix3 identity[1]{Matrix3{Math::IdentityInit}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(destination.size()), destination); + + /* Apply scaling first, if present */ + if(scalingFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[scalingFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); + + if(field._fieldType == SceneFieldType::Vector2) { + applyScaling(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Vector2d) { + applyScaling(fieldData, destination); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Apply rotation second, if present */ + if(rotationFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[rotationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); + + if(field._fieldType == SceneFieldType::Complex) { + applyRotation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Complexd) { + applyRotation(fieldData, destination); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Apply translation last, if present */ + if(translationFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[translationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); + + if(field._fieldType == SceneFieldType::Vector2) { + applyTranslation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Vector2d) { + applyTranslation(fieldData, destination); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Checked in the caller */ + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +void SceneData::transformations2DInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(fieldWithObjectMapping != ~UnsignedInt{}, + "Trade::SceneData::transformations2DInto(): no transformation-related field found", ); + CORRADE_ASSERT(!mappingDestination || mappingDestination.size() == _fields[fieldWithObjectMapping]._size, + "Trade::SceneData::transformations2DInto(): expected mapping destination view either empty or with" << _fields[fieldWithObjectMapping]._size << "elements but got" << mappingDestination.size(), ); + CORRADE_ASSERT(!fieldDestination || fieldDestination.size() == _fields[fieldWithObjectMapping]._size, + "Trade::SceneData::transformations2DInto(): expected field destination view either empty or with" << _fields[fieldWithObjectMapping]._size << "elements but got" << fieldDestination.size(), ); + mappingIntoInternal(fieldWithObjectMapping, 0, mappingDestination); + transformations2DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, 0, fieldDestination); +} + +std::size_t SceneData::transformations2DInto(const std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(fieldWithObjectMapping != ~UnsignedInt{}, + "Trade::SceneData::transformations2DInto(): no transformation-related field found", {}); + const std::size_t fieldSize = _fields[fieldWithObjectMapping]._size; + CORRADE_ASSERT(offset <= fieldSize, + "Trade::SceneData::transformations2DInto(): offset" << offset << "out of bounds for a field of size" << fieldSize, {}); + CORRADE_ASSERT(!mappingDestination != !fieldDestination|| mappingDestination.size() == fieldDestination.size(), + "Trade::SceneData::transformations2DInto(): mapping and field destination views have different size," << mappingDestination.size() << "vs" << fieldDestination.size(), {}); + const std::size_t size = Math::min(Math::max(mappingDestination.size(), fieldDestination.size()), fieldSize - offset); + if(mappingDestination) mappingIntoInternal(fieldWithObjectMapping, offset, mappingDestination.prefix(size)); + if(fieldDestination) transformations2DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, offset, fieldDestination.prefix(size)); + return size; +} + +Containers::Array> SceneData::transformations2DAsArray() const { + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(fieldWithObjectMapping != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::transformations2DInto(): no transformation-related field found", {}); + Containers::Array> out{NoInit, std::size_t(_fields[fieldWithObjectMapping]._size)}; + /** @todo use slicing once Pair exposes members somehow */ + mappingIntoInternal(fieldWithObjectMapping, 0, {out, reinterpret_cast(reinterpret_cast(out.data())), out.size(), sizeof(decltype(out)::Type)}); + transformations2DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, 0, {out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(UnsignedInt)), out.size(), sizeof(decltype(out)::Type)}); + return out; +} + +void SceneData::translationsRotationsScalings2DIntoInternal(const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, const std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const { + /* *FieldId, offset and *Destination.size() is assumed to be in bounds (or + an invalid field ID), checked by the callers */ + + /* If is2D() returned false as well, all *FieldId would be invalid, which + the caller is assumed to check. */ + CORRADE_ASSERT(!is3D(), "Trade::SceneData::translationsRotationsScalings2DInto(): scene has a 3D transformation type", ); + + /* Retrieve translation, if desired. If no field is present, output a zero + vector for all objects. */ + if(translationDestination) { + if(translationFieldId == ~UnsignedInt{}) { + constexpr Vector2 identity[]{Vector2{0.0f}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(translationDestination.size()), translationDestination); + } else { + const SceneFieldData& field = _fields[translationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, translationDestination.size()); + + if(field._fieldType == SceneFieldType::Vector2) { + Utility::copy(Containers::arrayCast(fieldData), translationDestination); + } else if(field._fieldType == SceneFieldType::Vector2d) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 2), Containers::arrayCast<2, Float>(translationDestination)); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } + + /* Retrieve rotation, if desired. If no field is present, output an + identity rotation for all objects. */ + if(rotationDestination) { + if(rotationFieldId == ~UnsignedInt{}) { + constexpr Complex identity[]{Complex{Math::IdentityInit}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(rotationDestination.size()), rotationDestination); + } else { + const SceneFieldData& field = _fields[rotationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, rotationDestination.size()); + + if(field._fieldType == SceneFieldType::Complex) { + Utility::copy(Containers::arrayCast(fieldData), rotationDestination); + } else if(field._fieldType == SceneFieldType::Complexd) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 2), Containers::arrayCast<2, Float>(rotationDestination)); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } + + /* Retrieve scaling, if desired. If no field is present, output an identity + scaling for all objects. */ + if(scalingDestination) { + if(scalingFieldId == ~UnsignedInt{}) { + constexpr Vector2 identity[]{Vector2{1.0f}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(scalingDestination.size()), scalingDestination); + } else { + const SceneFieldData& field = _fields[scalingFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, scalingDestination.size()); + + if(field._fieldType == SceneFieldType::Vector2) { + Utility::copy(Containers::arrayCast(fieldData), scalingDestination); + } else if(field._fieldType == SceneFieldType::Vector2d) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 2), Containers::arrayCast<2, Float>(scalingDestination)); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } +} + +void SceneData::translationsRotationsScalings2DInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const { + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(fieldWithObjectMapping != ~UnsignedInt{}, + "Trade::SceneData::translationsRotationsScalings2DInto(): no transformation-related field found", ); + CORRADE_ASSERT(!mappingDestination || mappingDestination.size() == _fields[fieldWithObjectMapping]._size, + "Trade::SceneData::translationsRotationsScalings2DInto(): expected mapping destination view either empty or with" << _fields[fieldWithObjectMapping]._size << "elements but got" << mappingDestination.size(), ); + CORRADE_ASSERT(!translationDestination || translationDestination.size() == _fields[fieldWithObjectMapping]._size, + "Trade::SceneData::translationsRotationsScalings2DInto(): expected translation destination view either empty or with" << _fields[fieldWithObjectMapping]._size << "elements but got" << translationDestination.size(), ); + CORRADE_ASSERT(!rotationDestination || rotationDestination.size() == _fields[fieldWithObjectMapping]._size, + "Trade::SceneData::translationsRotationsScalings2DInto(): expected rotation destination view either empty or with" << _fields[fieldWithObjectMapping]._size << "elements but got" << rotationDestination.size(), ); + CORRADE_ASSERT(!scalingDestination || scalingDestination.size() == _fields[fieldWithObjectMapping]._size, + "Trade::SceneData::translationsRotationsScalings2DInto(): expected scaling destination view either empty or with" << _fields[fieldWithObjectMapping]._size << "elements but got" << scalingDestination.size(), ); + mappingIntoInternal(fieldWithObjectMapping, 0, mappingDestination); + translationsRotationsScalings2DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, 0, translationDestination, rotationDestination, scalingDestination); +} + +std::size_t SceneData::translationsRotationsScalings2DInto(const std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const { + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(fieldWithObjectMapping != ~UnsignedInt{}, + "Trade::SceneData::translationsRotationsScalings2DInto(): no transformation-related field found", {}); + const std::size_t fieldSize = _fields[fieldWithObjectMapping]._size; + CORRADE_ASSERT(offset <= fieldSize, + "Trade::SceneData::translationsRotationsScalings2DInto(): offset" << offset << "out of bounds for a field of size" << fieldSize, {}); + CORRADE_ASSERT(!mappingDestination != !translationDestination || mappingDestination.size() == translationDestination.size(), + "Trade::SceneData::translationsRotationsScalings2DInto(): mapping and translation destination views have different size," << mappingDestination.size() << "vs" << translationDestination.size(), {}); + CORRADE_ASSERT(!mappingDestination != !rotationDestination || mappingDestination.size() == rotationDestination.size(), + "Trade::SceneData::translationsRotationsScalings2DInto(): mapping and rotation destination views have different size," << mappingDestination.size() << "vs" << rotationDestination.size(), {}); + CORRADE_ASSERT(!mappingDestination != !scalingDestination || mappingDestination.size() == scalingDestination.size(), + "Trade::SceneData::translationsRotationsScalings2DInto(): mapping and scaling destination views have different size," << mappingDestination.size() << "vs" << scalingDestination.size(), {}); + CORRADE_ASSERT(!translationDestination != !rotationDestination || translationDestination.size() == rotationDestination.size(), + "Trade::SceneData::translationsRotationsScalings2DInto(): translation and rotation destination views have different size," << translationDestination.size() << "vs" << rotationDestination.size(), {}); + CORRADE_ASSERT(!translationDestination != !scalingDestination || translationDestination.size() == scalingDestination.size(), + "Trade::SceneData::translationsRotationsScalings2DInto(): translation and scaling destination views have different size," << translationDestination.size() << "vs" << scalingDestination.size(), {}); + CORRADE_ASSERT(!rotationDestination != !scalingDestination || rotationDestination.size() == scalingDestination.size(), + "Trade::SceneData::translationsRotationsScalings2DInto(): rotation and scaling destination views have different size," << rotationDestination.size() << "vs" << scalingDestination.size(), {}); + const std::size_t size = Math::min(Math::max({mappingDestination.size(), translationDestination.size(), rotationDestination.size(), scalingDestination.size()}), fieldSize - offset); + if(mappingDestination) mappingIntoInternal(fieldWithObjectMapping, offset, mappingDestination.prefix(size)); + translationsRotationsScalings2DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, offset, + translationDestination ? translationDestination.prefix(size) : nullptr, + rotationDestination ? rotationDestination.prefix(size) : nullptr, + scalingDestination ? scalingDestination.prefix(size) : nullptr); + return size; +} + +Containers::Array>> SceneData::translationsRotationsScalings2DAsArray() const { + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(fieldWithObjectMapping != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::translationsRotationsScalings2DInto(): no transformation-related field found", {}); + Containers::Array>> out{NoInit, std::size_t(_fields[fieldWithObjectMapping]._size)}; + /** @todo use slicing once Triple exposes members somehow */ + mappingIntoInternal(fieldWithObjectMapping, 0, {out, reinterpret_cast(reinterpret_cast(out.data())), out.size(), sizeof(decltype(out)::Type)}); + const Containers::StridedArrayView1D translationsOut{out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(UnsignedInt)), out.size(), sizeof(decltype(out)::Type)}; + const Containers::StridedArrayView1D rotationsOut{out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(UnsignedInt) + sizeof(Vector2)), out.size(), sizeof(decltype(out)::Type)}; + const Containers::StridedArrayView1D scalingsOut{out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(UnsignedInt) + sizeof(Vector2) + sizeof(Complex)), out.size(), sizeof(decltype(out)::Type)}; + translationsRotationsScalings2DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, 0, translationsOut, rotationsOut, scalingsOut); + return out; +} + +void SceneData::transformations3DIntoInternal(const UnsignedInt transformationFieldId, const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + /* *FieldId, offset and destination.size() is assumed to be in bounds (or + an invalid field ID), checked by the callers */ + + /* If is3D() returned false as well, all *FieldId would be invalid, which + the caller is assumed to check. */ + CORRADE_ASSERT(!is2D(), "Trade::SceneData::transformations3DInto(): scene has a 2D transformation type", ); + + /** @todo apply scalings as well if dual quat? */ + + /* Prefer the transformation field, if present */ + if(transformationFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[transformationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); + const auto destination1f = Containers::arrayCast<2, Float>(destination); + + if(field._fieldType == SceneFieldType::Matrix4x4) { + Utility::copy(Containers::arrayCast(fieldData), destination); + } else if(field._fieldType == SceneFieldType::Matrix4x4d) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 16), destination1f); + } else if(field._fieldType == SceneFieldType::Matrix4x3) { + expandTransformationMatrix(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Matrix4x3d) { + expandTransformationMatrix(fieldData, destination); + } else if(field._fieldType == SceneFieldType::DualQuaternion) { + convertTransformation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::DualQuaterniond) { + convertTransformation(fieldData, destination); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + /* If not, combine from TRS components */ + } else if(translationFieldId != ~UnsignedInt{} || rotationFieldId != ~UnsignedInt{} || scalingFieldId != ~UnsignedInt{}) { + /* First fill the destination with identity matrices */ + const Matrix4 identity[1]{Matrix4{Math::IdentityInit}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(destination.size()), destination); + + /* Apply scaling first, if present */ + if(scalingFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[scalingFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); + + if(field._fieldType == SceneFieldType::Vector3) { + applyScaling(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Vector3d) { + applyScaling(fieldData, destination); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Apply rotation second, if present */ + if(rotationFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[rotationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); + + if(field._fieldType == SceneFieldType::Quaternion) { + applyRotation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Quaterniond) { + applyRotation(fieldData, destination); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Apply translation last, if present */ + if(translationFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[translationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); + + if(field._fieldType == SceneFieldType::Vector3) { + applyTranslation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Vector3d) { + applyTranslation(fieldData, destination); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Checked in the caller */ + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +void SceneData::transformations3DInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + const std::size_t fieldWithObjectMapping = findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(fieldWithObjectMapping != ~UnsignedInt{}, + "Trade::SceneData::transformations3DInto(): no transformation-related field found", ); + CORRADE_ASSERT(!mappingDestination || mappingDestination.size() == _fields[fieldWithObjectMapping]._size, + "Trade::SceneData::transformations3DInto(): expected mapping destination view either empty or with" << _fields[fieldWithObjectMapping]._size << "elements but got" << mappingDestination.size(), ); + CORRADE_ASSERT(!fieldDestination || fieldDestination.size() == _fields[fieldWithObjectMapping]._size, + "Trade::SceneData::transformations3DInto(): expected field destination view either empty or with" << _fields[fieldWithObjectMapping]._size << "elements but got" << fieldDestination.size(), ); + mappingIntoInternal(fieldWithObjectMapping, 0, mappingDestination); + transformations3DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, 0, fieldDestination); +} + +std::size_t SceneData::transformations3DInto(const std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(fieldWithObjectMapping != ~UnsignedInt{}, + "Trade::SceneData::transformations3DInto(): no transformation-related field found", {}); + const std::size_t fieldSize = _fields[fieldWithObjectMapping]._size; + CORRADE_ASSERT(offset <= fieldSize, + "Trade::SceneData::transformations3DInto(): offset" << offset << "out of bounds for a field of size" << fieldSize, {}); + CORRADE_ASSERT(!mappingDestination != !fieldDestination|| mappingDestination.size() == fieldDestination.size(), + "Trade::SceneData::transformations3DInto(): mapping and field destination views have different size," << mappingDestination.size() << "vs" << fieldDestination.size(), {}); + const std::size_t size = Math::min(Math::max(mappingDestination.size(), fieldDestination.size()), fieldSize - offset); + if(mappingDestination) mappingIntoInternal(fieldWithObjectMapping, offset, mappingDestination.prefix(size)); + if(fieldDestination) transformations3DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, offset, fieldDestination.prefix(size)); + return size; +} + +Containers::Array> SceneData::transformations3DAsArray() const { + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(fieldWithObjectMapping != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::transformations3DInto(): no transformation-related field found", {}); + Containers::Array> out{NoInit, std::size_t(_fields[fieldWithObjectMapping]._size)}; + /** @todo use slicing once Pair exposes members somehow */ + mappingIntoInternal(fieldWithObjectMapping, 0, {out, reinterpret_cast(reinterpret_cast(out.data())), out.size(), sizeof(decltype(out)::Type)}); + transformations3DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, 0, {out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(UnsignedInt)), out.size(), sizeof(decltype(out)::Type)}); + return out; +} + +void SceneData::translationsRotationsScalings3DIntoInternal(const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, const std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const { + /* *FieldId, offset and *Destination.size() is assumed to be in bounds (or + an invalid field ID), checked by the callers */ + + /* If is3D() returned false as well, all *FieldId would be invalid, which + the caller is assumed to check. */ + CORRADE_ASSERT(!is2D(), "Trade::SceneData::translationsRotationsScalings3DInto(): scene has a 2D transformation type", ); + + /* Retrieve translation, if desired. If no field is present, output a zero + vector for all objects. */ + if(translationDestination) { + if(translationFieldId == ~UnsignedInt{}) { + constexpr Vector3 identity[]{Vector3{0.0f}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(translationDestination.size()), translationDestination); + } else { + const SceneFieldData& field = _fields[translationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, translationDestination.size()); + + if(field._fieldType == SceneFieldType::Vector3) { + Utility::copy(Containers::arrayCast(fieldData), translationDestination); + } else if(field._fieldType == SceneFieldType::Vector3d) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 3), Containers::arrayCast<2, Float>(translationDestination)); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } + + /* Retrieve rotation, if desired. If no field is present, output an + identity rotation for all objects. */ + if(rotationDestination) { + if(rotationFieldId == ~UnsignedInt{}) { + constexpr Quaternion identity[]{Quaternion{Math::IdentityInit}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(rotationDestination.size()), rotationDestination); + } else { + const SceneFieldData& field = _fields[rotationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, rotationDestination.size()); + + if(field._fieldType == SceneFieldType::Quaternion) { + Utility::copy(Containers::arrayCast(fieldData), rotationDestination); + } else if(field._fieldType == SceneFieldType::Quaterniond) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 4), Containers::arrayCast<2, Float>(rotationDestination)); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } + + /* Retrieve scaling, if desired. If no field is present, output an identity + scaling for all objects. */ + if(scalingDestination) { + if(scalingFieldId == ~UnsignedInt{}) { + constexpr Vector3 identity[]{Vector3{1.0f}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(scalingDestination.size()), scalingDestination); + } else { + const SceneFieldData& field = _fields[scalingFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, scalingDestination.size()); + + if(field._fieldType == SceneFieldType::Vector3) { + Utility::copy(Containers::arrayCast(fieldData), scalingDestination); + } else if(field._fieldType == SceneFieldType::Vector3d) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 3), Containers::arrayCast<2, Float>(scalingDestination)); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } +} + +void SceneData::translationsRotationsScalings3DInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const { + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(fieldWithObjectMapping != ~UnsignedInt{}, + "Trade::SceneData::translationsRotationsScalings3DInto(): no transformation-related field found", ); + CORRADE_ASSERT(!mappingDestination || mappingDestination.size() == _fields[fieldWithObjectMapping]._size, + "Trade::SceneData::translationsRotationsScalings3DInto(): expected mapping destination view either empty or with" << _fields[fieldWithObjectMapping]._size << "elements but got" << mappingDestination.size(), ); + CORRADE_ASSERT(!translationDestination || translationDestination.size() == _fields[fieldWithObjectMapping]._size, + "Trade::SceneData::translationsRotationsScalings3DInto(): expected translation destination view either empty or with" << _fields[fieldWithObjectMapping]._size << "elements but got" << translationDestination.size(), ); + CORRADE_ASSERT(!rotationDestination || rotationDestination.size() == _fields[fieldWithObjectMapping]._size, + "Trade::SceneData::translationsRotationsScalings3DInto(): expected rotation destination view either empty or with" << _fields[fieldWithObjectMapping]._size << "elements but got" << rotationDestination.size(), ); + CORRADE_ASSERT(!scalingDestination || scalingDestination.size() == _fields[fieldWithObjectMapping]._size, + "Trade::SceneData::translationsRotationsScalings3DInto(): expected scaling destination view either empty or with" << _fields[fieldWithObjectMapping]._size << "elements but got" << scalingDestination.size(), ); + mappingIntoInternal(fieldWithObjectMapping, 0, mappingDestination); + translationsRotationsScalings3DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, 0, translationDestination, rotationDestination, scalingDestination); +} + +std::size_t SceneData::translationsRotationsScalings3DInto(const std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const { + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(fieldWithObjectMapping != ~UnsignedInt{}, + "Trade::SceneData::translationsRotationsScalings3DInto(): no transformation-related field found", {}); + const std::size_t fieldSize = _fields[fieldWithObjectMapping]._size; + CORRADE_ASSERT(offset <= fieldSize, + "Trade::SceneData::translationsRotationsScalings3DInto(): offset" << offset << "out of bounds for a field of size" << fieldSize, {}); + CORRADE_ASSERT(!mappingDestination != !translationDestination || mappingDestination.size() == translationDestination.size(), + "Trade::SceneData::translationsRotationsScalings3DInto(): mapping and translation destination views have different size," << mappingDestination.size() << "vs" << translationDestination.size(), {}); + CORRADE_ASSERT(!mappingDestination != !rotationDestination || mappingDestination.size() == rotationDestination.size(), + "Trade::SceneData::translationsRotationsScalings3DInto(): mapping and rotation destination views have different size," << mappingDestination.size() << "vs" << rotationDestination.size(), {}); + CORRADE_ASSERT(!mappingDestination != !scalingDestination || mappingDestination.size() == scalingDestination.size(), + "Trade::SceneData::translationsRotationsScalings3DInto(): mapping and scaling destination views have different size," << mappingDestination.size() << "vs" << scalingDestination.size(), {}); + CORRADE_ASSERT(!translationDestination != !rotationDestination || translationDestination.size() == rotationDestination.size(), + "Trade::SceneData::translationsRotationsScalings3DInto(): translation and rotation destination views have different size," << translationDestination.size() << "vs" << rotationDestination.size(), {}); + CORRADE_ASSERT(!translationDestination != !scalingDestination || translationDestination.size() == scalingDestination.size(), + "Trade::SceneData::translationsRotationsScalings3DInto(): translation and scaling destination views have different size," << translationDestination.size() << "vs" << scalingDestination.size(), {}); + CORRADE_ASSERT(!rotationDestination != !scalingDestination || rotationDestination.size() == scalingDestination.size(), + "Trade::SceneData::translationsRotationsScalings3DInto(): rotation and scaling destination views have different size," << rotationDestination.size() << "vs" << scalingDestination.size(), {}); + const std::size_t size = Math::min(Math::max({mappingDestination.size(), translationDestination.size(), rotationDestination.size(), scalingDestination.size()}), fieldSize - offset); + if(mappingDestination) mappingIntoInternal(fieldWithObjectMapping, offset, mappingDestination.prefix(size)); + translationsRotationsScalings3DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, offset, + translationDestination ? translationDestination.prefix(size) : nullptr, + rotationDestination ? rotationDestination.prefix(size) : nullptr, + scalingDestination ? scalingDestination.prefix(size) : nullptr); + return size; +} + +Containers::Array>> SceneData::translationsRotationsScalings3DAsArray() const { + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(fieldWithObjectMapping != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::translationsRotationsScalings3DInto(): no transformation-related field found", {}); + Containers::Array>> out{NoInit, std::size_t(_fields[fieldWithObjectMapping]._size)}; + /** @todo use slicing once Triple exposes members somehow */ + mappingIntoInternal(fieldWithObjectMapping, 0, {out, reinterpret_cast(reinterpret_cast(out.data())), out.size(), sizeof(decltype(out)::Type)}); + const Containers::StridedArrayView1D translationsOut{out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(UnsignedInt)), out.size(), sizeof(decltype(out)::Type)}; + const Containers::StridedArrayView1D rotationsOut{out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(UnsignedInt) + sizeof(Vector3)), out.size(), sizeof(decltype(out)::Type)}; + const Containers::StridedArrayView1D scalingsOut{out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(UnsignedInt) + sizeof(Vector3) + sizeof(Quaternion)), out.size(), sizeof(decltype(out)::Type)}; + translationsRotationsScalings3DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, 0, translationsOut, rotationsOut, scalingsOut); + return out; +} + +void SceneData::unsignedIndexFieldIntoInternal(const UnsignedInt fieldId, const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + /* fieldId, offset and destination.size() is assumed to be in bounds, + checked by the callers */ + + const SceneFieldData& field = _fields[fieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); + const auto destination1ui = Containers::arrayCast<2, UnsignedInt>(destination); + + if(field._fieldType == SceneFieldType::UnsignedInt) + Utility::copy(Containers::arrayCast(fieldData), destination); + else if(field._fieldType == SceneFieldType::UnsignedShort) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(fieldData, 1), destination1ui); + else if(field._fieldType == SceneFieldType::UnsignedByte) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(fieldData, 1), destination1ui); + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +void SceneData::indexFieldIntoInternal(const UnsignedInt fieldId, const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + /* fieldId, offset and destination.size() is assumed to be in bounds, + checked by the callers */ + + const SceneFieldData& field = _fields[fieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, destination.size()); + const auto destination1ui = Containers::arrayCast<2, Int>(destination); + + if(field._fieldType == SceneFieldType::Int) + Utility::copy(Containers::arrayCast(fieldData), destination); + else if(field._fieldType == SceneFieldType::Short) + Math::castInto(Containers::arrayCast<2, const Short>(fieldData, 1), destination1ui); + else if(field._fieldType == SceneFieldType::Byte) + Math::castInto(Containers::arrayCast<2, const Byte>(fieldData, 1), destination1ui); + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array> SceneData::unsignedIndexFieldAsArrayInternal(const UnsignedInt fieldId) const { + Containers::Array> out{NoInit, std::size_t(_fields[fieldId]._size)}; + /** @todo use slicing once Pair exposes members somehow */ + mappingIntoInternal(fieldId, 0, {out, reinterpret_cast(reinterpret_cast(out.data())), out.size(), sizeof(decltype(out)::Type)}); + unsignedIndexFieldIntoInternal(fieldId, 0, {out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(UnsignedInt)), out.size(), sizeof(decltype(out)::Type)}); + return out; +} + +void SceneData::meshesMaterialsIntoInternal(const UnsignedInt fieldId, const std::size_t offset, const Containers::StridedArrayView1D& meshDestination, const Containers::StridedArrayView1D& meshMaterialDestination) const { + /* fieldId, offset, meshDestination.size() and + meshMaterialDestination.size() is assumed to be in bounds, checked by + the callers */ + + if(meshDestination) + unsignedIndexFieldIntoInternal(fieldId, offset, meshDestination); + + /* Copy also the material, if desired. If no such field is present, output + -1 for all meshes. */ + if(meshMaterialDestination) { + const UnsignedInt materialFieldId = findFieldIdInternal(SceneField::MeshMaterial); + if(materialFieldId == ~UnsignedInt{}) { + constexpr Int invalid[]{-1}; + Utility::copy(Containers::stridedArrayView(invalid).broadcasted<0>(meshMaterialDestination.size()), meshMaterialDestination); + } else indexFieldIntoInternal(materialFieldId, offset, meshMaterialDestination); + } +} + +void SceneData::meshesMaterialsInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& meshDestination, const Containers::StridedArrayView1D& meshMaterialDestination) const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Mesh); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::meshesMaterialsInto(): field" << SceneField::Mesh << "not found", ); + CORRADE_ASSERT(!mappingDestination || mappingDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::meshesMaterialsInto(): expected mapping destination view either empty or with" << _fields[fieldId]._size << "elements but got" << mappingDestination.size(), ); + CORRADE_ASSERT(!meshDestination || meshDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::meshesMaterialsInto(): expected mesh destination view either empty or with" << _fields[fieldId]._size << "elements but got" << meshDestination.size(), ); + CORRADE_ASSERT(!meshMaterialDestination || meshMaterialDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::meshesMaterialsInto(): expected mesh material destination view either empty or with" << _fields[fieldId]._size << "elements but got" << meshMaterialDestination.size(), ); + mappingIntoInternal(fieldId, 0, mappingDestination); + meshesMaterialsIntoInternal(fieldId, 0, meshDestination, meshMaterialDestination); +} + +std::size_t SceneData::meshesMaterialsInto(const std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& meshDestination, const Containers::StridedArrayView1D& meshMaterialDestination) const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Mesh); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::meshesMaterialsInto(): field" << SceneField::Mesh << "not found", {}); + const std::size_t fieldSize = _fields[fieldId]._size; + CORRADE_ASSERT(offset <= fieldSize, + "Trade::SceneData::meshesMaterialsInto(): offset" << offset << "out of bounds for a field of size" << fieldSize, {}); + CORRADE_ASSERT(!mappingDestination != !meshDestination || mappingDestination.size() == meshDestination.size(), + "Trade::SceneData::meshesMaterialsInto(): mapping and mesh destination views have different size," << mappingDestination.size() << "vs" << meshDestination.size(), {}); + CORRADE_ASSERT(!mappingDestination != !meshMaterialDestination || mappingDestination.size() == meshMaterialDestination.size(), + "Trade::SceneData::meshesMaterialsInto(): mapping and mesh material destination views have different size," << mappingDestination.size() << "vs" << meshMaterialDestination.size(), {}); + CORRADE_ASSERT(!meshDestination != !meshMaterialDestination || meshMaterialDestination.size() == meshDestination.size(), + "Trade::SceneData::meshesMaterialsInto(): mesh and mesh material destination views have different size," << meshDestination.size() << "vs" << meshMaterialDestination.size(), {}); + const std::size_t size = Math::min(Math::max({mappingDestination.size(), meshDestination.size(), meshMaterialDestination.size()}), fieldSize - offset); + if(mappingDestination) mappingIntoInternal(fieldId, offset, mappingDestination.prefix(size)); + meshesMaterialsIntoInternal(fieldId, offset, + meshDestination ? meshDestination.prefix(size) : nullptr, + meshMaterialDestination ? meshMaterialDestination.prefix(size) : nullptr); + return size; +} + +Containers::Array>> SceneData::meshesMaterialsAsArray() const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Mesh); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::meshesMaterialsInto(): field" << SceneField::Mesh << "not found", {}); + Containers::Array>> out{NoInit, std::size_t(_fields[fieldId]._size)}; + /** @todo use slicing once Pair exposes members somehow */ + mappingIntoInternal(fieldId, 0, {out, reinterpret_cast(reinterpret_cast(out.data())), out.size(), sizeof(decltype(out)::Type)}); + const Containers::StridedArrayView1D meshesOut{out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(UnsignedInt)), out.size(), sizeof(decltype(out)::Type)}; + const Containers::StridedArrayView1D meshMaterialsOut{out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(UnsignedInt) + sizeof(UnsignedInt)), out.size(), sizeof(decltype(out)::Type)}; + meshesMaterialsIntoInternal(fieldId, 0, meshesOut, meshMaterialsOut); + return out; +} + +void SceneData::lightsInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Light); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::lightsInto(): field not found", ); + CORRADE_ASSERT(!mappingDestination || mappingDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::lightsInto(): expected mapping destination view either empty or with" << _fields[fieldId]._size << "elements but got" << mappingDestination.size(), ); + CORRADE_ASSERT(!fieldDestination || fieldDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::lightsInto(): expected field destination view either empty or with" << _fields[fieldId]._size << "elements but got" << fieldDestination.size(), ); + mappingIntoInternal(fieldId, 0, mappingDestination); + unsignedIndexFieldIntoInternal(fieldId, 0, fieldDestination); +} + +std::size_t SceneData::lightsInto(const std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Light); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::lightsInto(): field not found", {}); + const std::size_t fieldSize = _fields[fieldId]._size; + CORRADE_ASSERT(offset <= fieldSize, + "Trade::SceneData::lightsInto(): offset" << offset << "out of bounds for a field of size" << fieldSize, {}); + CORRADE_ASSERT(!mappingDestination != !fieldDestination|| mappingDestination.size() == fieldDestination.size(), + "Trade::SceneData::lightsInto(): mapping and field destination views have different size," << mappingDestination.size() << "vs" << fieldDestination.size(), {}); + const std::size_t size = Math::min(Math::max(mappingDestination.size(), fieldDestination.size()), fieldSize - offset); + if(mappingDestination) mappingIntoInternal(fieldId, offset, mappingDestination.prefix(size)); + if(fieldDestination) unsignedIndexFieldIntoInternal(fieldId, offset, fieldDestination.prefix(size)); + return size; +} + +Containers::Array> SceneData::lightsAsArray() const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Light); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::lightsInto(): field not found", {}); + return unsignedIndexFieldAsArrayInternal(fieldId); +} + +void SceneData::camerasInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Camera); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::camerasInto(): field not found", ); + CORRADE_ASSERT(!mappingDestination || mappingDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::camerasInto(): expected mapping destination view either empty or with" << _fields[fieldId]._size << "elements but got" << mappingDestination.size(), ); + CORRADE_ASSERT(!fieldDestination || fieldDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::camerasInto(): expected field destination view either empty or with" << _fields[fieldId]._size << "elements but got" << fieldDestination.size(), ); + mappingIntoInternal(fieldId, 0, mappingDestination); + unsignedIndexFieldIntoInternal(fieldId, 0, fieldDestination); +} + +std::size_t SceneData::camerasInto(const std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Camera); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::camerasInto(): field not found", {}); + const std::size_t fieldSize = _fields[fieldId]._size; + CORRADE_ASSERT(offset <= fieldSize, + "Trade::SceneData::camerasInto(): offset" << offset << "out of bounds for a field of size" << fieldSize, {}); + CORRADE_ASSERT(!mappingDestination != !fieldDestination|| mappingDestination.size() == fieldDestination.size(), + "Trade::SceneData::camerasInto(): mapping and field destination views have different size," << mappingDestination.size() << "vs" << fieldDestination.size(), {}); + const std::size_t size = Math::min(Math::max(mappingDestination.size(), fieldDestination.size()), fieldSize - offset); + if(mappingDestination) mappingIntoInternal(fieldId, offset, mappingDestination.prefix(size)); + if(fieldDestination) unsignedIndexFieldIntoInternal(fieldId, offset, fieldDestination.prefix(size)); + return size; +} + +Containers::Array> SceneData::camerasAsArray() const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Camera); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::camerasInto(): field not found", {}); + return unsignedIndexFieldAsArrayInternal(fieldId); +} + +void SceneData::skinsInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Skin); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::skinsInto(): field not found", ); + CORRADE_ASSERT(!mappingDestination || mappingDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::skinsInto(): expected mapping destination view either empty or with" << _fields[fieldId]._size << "elements but got" << mappingDestination.size(), ); + CORRADE_ASSERT(!fieldDestination || fieldDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::skinsInto(): expected field destination view either empty or with" << _fields[fieldId]._size << "elements but got" << fieldDestination.size(), ); + mappingIntoInternal(fieldId, 0, mappingDestination); + unsignedIndexFieldIntoInternal(fieldId, 0, fieldDestination); +} + +std::size_t SceneData::skinsInto(const std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Skin); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::skinsInto(): field not found", {}); + const std::size_t fieldSize = _fields[fieldId]._size; + CORRADE_ASSERT(offset <= fieldSize, + "Trade::SceneData::skinsInto(): offset" << offset << "out of bounds for a field of size" << fieldSize, {}); + CORRADE_ASSERT(!mappingDestination != !fieldDestination|| mappingDestination.size() == fieldDestination.size(), + "Trade::SceneData::skinsInto(): mapping and field destination views have different size," << mappingDestination.size() << "vs" << fieldDestination.size(), {}); + const std::size_t size = Math::min(Math::max(mappingDestination.size(), fieldDestination.size()), fieldSize - offset); + if(mappingDestination) mappingIntoInternal(fieldId, offset, mappingDestination.prefix(size)); + if(fieldDestination) unsignedIndexFieldIntoInternal(fieldId, offset, fieldDestination.prefix(size)); + return size; +} + +Containers::Array> SceneData::skinsAsArray() const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Skin); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::skinsInto(): field not found", {}); + return unsignedIndexFieldAsArrayInternal(fieldId); +} + +void SceneData::importerStateIntoInternal(const UnsignedInt fieldId, const std::size_t offset, const Containers::StridedArrayView1D& destination) const { + /* fieldId, offset and destination.size() is assumed to be in bounds, + checked by the callers */ + + const SceneFieldData& field = _fields[fieldId]; + CORRADE_INTERNAL_ASSERT(field._fieldType == SceneFieldType::Pointer || + field._fieldType == SceneFieldType::MutablePointer); + Utility::copy(Containers::arrayCast(fieldDataFieldViewInternal(field, offset, destination.size())), destination); +} + +void SceneData::importerStateInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::ImporterState); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::importerStateInto(): field not found", ); + CORRADE_ASSERT(!mappingDestination || mappingDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::importerStateInto(): expected mapping destination view either empty or with" << _fields[fieldId]._size << "elements but got" << mappingDestination.size(), ); + CORRADE_ASSERT(!fieldDestination || fieldDestination.size() == _fields[fieldId]._size, + "Trade::SceneData::importerStateInto(): expected field destination view either empty or with" << _fields[fieldId]._size << "elements but got" << fieldDestination.size(), ); + mappingIntoInternal(fieldId, 0, mappingDestination); + importerStateIntoInternal(fieldId, 0, fieldDestination); +} + +std::size_t SceneData::importerStateInto(const std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::ImporterState); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::importerStateInto(): field not found", {}); + const std::size_t fieldSize = _fields[fieldId]._size; + CORRADE_ASSERT(offset <= fieldSize, + "Trade::SceneData::importerStateInto(): offset" << offset << "out of bounds for a field of size" << fieldSize, {}); + CORRADE_ASSERT(!mappingDestination != !fieldDestination|| mappingDestination.size() == fieldDestination.size(), + "Trade::SceneData::importerStateInto(): mapping and field destination views have different size," << mappingDestination.size() << "vs" << fieldDestination.size(), {}); + const std::size_t size = Math::min(Math::max(mappingDestination.size(), fieldDestination.size()), fieldSize - offset); + if(mappingDestination) mappingIntoInternal(fieldId, offset, mappingDestination.prefix(size)); + if(fieldDestination) importerStateIntoInternal(fieldId, offset, fieldDestination.prefix(size)); + return size; +} + +Containers::Array> SceneData::importerStateAsArray() const { + const UnsignedInt fieldId = findFieldIdInternal(SceneField::ImporterState); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::importerStateInto(): field not found", {}); + Containers::Array> out{ + /* There's padding before the pointer on 64bit, zero-initialize to + avoid keeping random bytes in there */ + #ifdef CORRADE_TARGET_32BIT + NoInit + #else + ValueInit + #endif + , std::size_t(_fields[fieldId]._size)}; + /** @todo use slicing once Pair exposes members somehow, especially because + this is EXTREMELY prone to bugs due to the padding before the pointer */ + mappingIntoInternal(fieldId, 0, {out, reinterpret_cast(reinterpret_cast(out.data())), out.size(), sizeof(decltype(out)::Type)}); + importerStateIntoInternal(fieldId, 0, {out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(const void*)), out.size(), sizeof(decltype(out)::Type)}); + return out; +} + +Containers::Optional SceneData::parentFor(const UnsignedLong object) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::parentFor(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Parent); + if(fieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& field = _fields[fieldId]; + const std::size_t offset = findFieldObjectOffsetInternal(field, object, 0); + if(offset == field._size) return {}; + + Int index[1]; + parentsIntoInternal(fieldId, offset, index); + return *index; +} + +Containers::Array SceneData::childrenFor(const Long object) const { + CORRADE_ASSERT(object >= -1 && object < Long(_mappingBound), + "Trade::SceneData::childrenFor(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + const UnsignedInt parentFieldId = findFieldIdInternal(SceneField::Parent); + if(parentFieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& parentField = _fields[parentFieldId]; + + /* Collect IDs of all objects that reference this object */ + Containers::Array out; + for(std::size_t offset = 0; offset != parentField.size(); ++offset) { + Int parentIndex[1]; + parentsIntoInternal(parentFieldId, offset, parentIndex); + if(*parentIndex == object) { + /** @todo this drops the upper 64 bits, might be a problem + eventually (at this point it's more important to have an API + that won't change the return types in the future, breaking + existing code) */ + UnsignedInt child[1]; + /** @todo bleh slow, use the children <-> parent field proxying + when implemented */ + mappingIntoInternal(parentFieldId, offset, child); + arrayAppend(out, *child); + } + } + + return out; +} + +Containers::Optional SceneData::transformation2DFor(const UnsignedLong object) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::transformation2DFor(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + if(fieldWithObjectMapping == ~UnsignedInt{}) return {}; + + /* If is2D() returned false as well, all *FieldId would be invalid, which + is handled above. */ + CORRADE_ASSERT(!is3D(), "Trade::SceneData::transformation2DFor(): scene has a 3D transformation type", {}); + + const SceneFieldData& field = _fields[fieldWithObjectMapping]; + const std::size_t offset = findFieldObjectOffsetInternal(field, object, 0); + if(offset == field._size) return {}; + + Matrix3 transformation[1]; + transformations2DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, offset, transformation); + return *transformation; +} + +Containers::Optional> SceneData::translationRotationScaling2DFor(const UnsignedLong object) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::translationRotationScaling2DFor(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + if(fieldWithObjectMapping == ~UnsignedInt{}) return {}; + + /* If is2D() returned false as well, all *FieldId would be invalid, which + is handled above. */ + CORRADE_ASSERT(!is3D(), "Trade::SceneData::translationRotationScaling2DFor(): scene has a 3D transformation type", {}); + + const SceneFieldData& field = _fields[fieldWithObjectMapping]; + const std::size_t offset = findFieldObjectOffsetInternal(field, object, 0); + if(offset == field._size) return {}; + + Vector2 translation[1]; + Complex rotation[1]; + Vector2 scaling[1]; + translationsRotationsScalings2DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, offset, translation, rotation, scaling); + return {InPlaceInit, *translation, *rotation, *scaling}; +} + +Containers::Optional SceneData::transformation3DFor(const UnsignedLong object) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::transformation3DFor(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + if(fieldWithObjectMapping == ~UnsignedInt{}) return {}; + + /* If is3D() returned false as well, all *FieldId would be invalid, which + is handled above. */ + CORRADE_ASSERT(!is2D(), "Trade::SceneData::transformation3DFor(): scene has a 2D transformation type", {}); + + const SceneFieldData& field = _fields[fieldWithObjectMapping]; + const std::size_t offset = findFieldObjectOffsetInternal(field, object, 0); + if(offset == field._size) return {}; + + Matrix4 transformation[1]; + transformations3DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, offset, transformation); + return *transformation; +} + +Containers::Optional> SceneData::translationRotationScaling3DFor(const UnsignedLong object) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::translationRotationScaling3DFor(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + const UnsignedInt fieldWithObjectMapping = findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + if(fieldWithObjectMapping == ~UnsignedInt{}) return {}; + + /* If is3D() returned false as well, all *FieldId would be invalid, which + is handled above. */ + CORRADE_ASSERT(!is2D(), "Trade::SceneData::translationRotationScaling3DFor(): scene has a 2D transformation type", {}); + + const SceneFieldData& field = _fields[fieldWithObjectMapping]; + const std::size_t offset = findFieldObjectOffsetInternal(field, object, 0); + if(offset == field._size) return {}; + + Vector3 translation[1]; + Quaternion rotation[1]; + Vector3 scaling[1]; + translationsRotationsScalings3DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, offset, translation, rotation, scaling); + return {InPlaceInit, *translation, *rotation, *scaling}; +} + +Containers::Array> SceneData::meshesMaterialsFor(const UnsignedLong object) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::meshesMaterialsFor(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + const UnsignedInt meshFieldId = findFieldIdInternal(SceneField::Mesh); + if(meshFieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& field = _fields[meshFieldId]; + Containers::Array> out; + std::size_t offset = 0; + for(;;) { + offset = findFieldObjectOffsetInternal(field, object, offset); + if(offset == field._size) break; + + UnsignedInt mesh[1]; + Int material[1]; + meshesMaterialsIntoInternal(meshFieldId, offset, mesh, material); + arrayAppend(out, InPlaceInit, *mesh, *material); + ++offset; + } + + return out; +} + +Containers::Array SceneData::lightsFor(const UnsignedLong object) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::lightsFor(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Light); + if(fieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& field = _fields[fieldId]; + Containers::Array out; + std::size_t offset = 0; + for(;;) { + offset = findFieldObjectOffsetInternal(field, object, offset); + if(offset == field._size) break; + + UnsignedInt index[1]; + unsignedIndexFieldIntoInternal(fieldId, offset, index); + arrayAppend(out, InPlaceInit, *index); + ++offset; + } + + return out; +} + +Containers::Array SceneData::camerasFor(const UnsignedLong object) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::camerasFor(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Camera); + if(fieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& field = _fields[fieldId]; + Containers::Array out; + std::size_t offset = 0; + for(;;) { + offset = findFieldObjectOffsetInternal(field, object, offset); + if(offset == field._size) break; + + UnsignedInt index[1]; + unsignedIndexFieldIntoInternal(fieldId, offset, index); + arrayAppend(out, InPlaceInit, *index); + ++offset; + } + + return out; +} + +Containers::Array SceneData::skinsFor(const UnsignedLong object) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::skinsFor(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Skin); + if(fieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& field = _fields[fieldId]; + Containers::Array out; + std::size_t offset = 0; + for(;;) { + offset = findFieldObjectOffsetInternal(field, object, offset); + if(offset == field._size) break; + + UnsignedInt index[1]; + unsignedIndexFieldIntoInternal(fieldId, offset, index); + arrayAppend(out, InPlaceInit, *index); + ++offset; + } + + return out; +} + +Containers::Optional SceneData::importerStateFor(const UnsignedLong object) const { + CORRADE_ASSERT(object < _mappingBound, + "Trade::SceneData::importerStateFor(): object" << object << "out of bounds for" << _mappingBound << "objects", {}); + + const UnsignedInt fieldId = findFieldIdInternal(SceneField::ImporterState); + if(fieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& field = _fields[fieldId]; + const std::size_t offset = findFieldObjectOffsetInternal(field, object, 0); + if(offset == field._size) return {}; + + const void* importerState[1]; + importerStateIntoInternal(fieldId, offset, importerState); + return *importerState; +} + +#ifdef MAGNUM_BUILD_DEPRECATED +std::vector SceneData::children2D() const { + if(_dimensions != 2) return {}; + + /* Even though (or exactly because?) this API is deprecated, it's better to + warn than to spend several hours debugging what's wrong */ + if(!hasField(SceneField::Parent)) + Warning{} << "Trade::SceneData::children2D(): no parent field present, returned array will be empty"; + + const Containers::Array children = childrenFor(-1); + return {children.begin(), children.end()}; +} + +std::vector SceneData::children3D() const { + if(_dimensions != 3) return {}; + + /* Even though (or exactly because?) this API is deprecated, it's better to + warn than to spend several hours debugging what's wrong */ + if(!hasField(SceneField::Parent)) + Warning{} << "Trade::SceneData::children3D(): no parent field present, returned array will be empty"; + + const Containers::Array children = childrenFor(-1); + return {children.begin(), children.end()}; +} +#endif + +Containers::Array SceneData::releaseFieldData() { + Containers::Array out = std::move(_fields); + _fields = {}; + return out; +} + +Containers::Array SceneData::releaseData() { + _fields = {}; + Containers::Array out = std::move(_data); + _data = {}; + return out; +} }} diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index 8a10cfd8fc..bf3cd4d91c 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -26,76 +26,3212 @@ */ /** @file - * @brief Class @ref Magnum::Trade::SceneData + * @brief Class @ref Magnum::Trade::SceneData, @ref Magnum::Trade::SceneFieldData, enum @ref Magnum::Trade::SceneMappingType, @ref Magnum::Trade::SceneField, @ref Magnum::Trade::SceneFieldType, @ref Magnum::Trade::SceneFieldFlag, enum set @ref Magnum::Trade::SceneFieldFlags, function @ref Magnum::sceneMappingTypeSize(), @ref Magnum::sceneMappingTypeAlignment(), @ref Magnum::sceneFieldTypeSize(), @ref Magnum::sceneFieldTypeAlignment(), @ref Magnum::Trade::isSceneFieldCustom(), @ref Magnum::sceneFieldCustom() */ -#include +#include +#include -#include "Magnum/Types.h" +#include "Magnum/Trade/Data.h" +#include "Magnum/Trade/Trade.h" #include "Magnum/Trade/visibility.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#endif + namespace Magnum { namespace Trade { /** -@brief Scene data +@brief Scene object mapping type +@m_since_latest -@see @ref AbstractImporter::scene() +Type used for mapping fields to corresponding objects. Unlike +@ref SceneFieldType that is different for different fields, the object mapping +type is the same for all fields, and is guaranteed to be large enough to fit +@ref SceneData::mappingBound() objects. +@see @ref SceneData::mappingType(), @ref sceneMappingTypeSize(), + @ref sceneMappingTypeAlignment() */ -class MAGNUM_TRADE_EXPORT SceneData { +enum class SceneMappingType: UnsignedByte { + /* Zero used for an invalid value */ + + UnsignedByte = 1, /**< @relativeref{Magnum,UnsignedByte} */ + UnsignedShort, /**< @relativeref{Magnum,UnsignedShort} */ + UnsignedInt, /**< @relativeref{Magnum,UnsignedInt} */ + UnsignedLong /**< @relativeref{Magnum,UnsignedLong} */ +}; + +/** +@debugoperatorenum{SceneMappingType} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneMappingType value); + +/** +@brief Size of given scene object mapping type +@m_since_latest + +@see @ref sceneMappingTypeAlignment() +*/ +MAGNUM_TRADE_EXPORT UnsignedInt sceneMappingTypeSize(SceneMappingType type); + +/** +@brief Alignment of given scene object mapping type +@m_since_latest + +Returns the same value as @ref sceneMappingTypeSize(). +*/ +MAGNUM_TRADE_EXPORT UnsignedInt sceneMappingTypeAlignment(SceneMappingType type); + +/** +@brief Scene field name +@m_since_latest + +See @ref SceneData for more information. +@see @ref SceneFieldData, @ref SceneFieldType, + @ref AbstractImporter::sceneFieldForName(), + @ref AbstractImporter::sceneFieldName() +*/ +enum class SceneField: UnsignedInt { + /* Zero used for an invalid value */ + + /** + * Parent object. Type is usually @ref SceneFieldType::Int, but can be also + * any of @relativeref{SceneFieldType,Byte}, + * @relativeref{SceneFieldType,Short} or a + * @relativeref{SceneFieldType,Long}. A value of @cpp -1 @ce means there's + * no parent. An object should have only one parent, altough this isn't + * enforced in any way, and which of the duplicate fields gets used is not + * defined. + * @see @ref SceneData::parentsAsArray(), @ref SceneData::parentFor(), + * @ref SceneData::childrenFor() + */ + Parent = 1, + + /** + * Transformation. Type is usually @ref SceneFieldType::Matrix3x3 for 2D + * and @ref SceneFieldType::Matrix4x4 for 3D, but can be also any of + * @relativeref{SceneFieldType,Matrix3x3d}, + * @relativeref{SceneFieldType,Matrix3x2} or + * @relativeref{SceneFieldType,Matrix3x2d} (with the bottom row implicitly + * assumed to be @f$ \begin{pmatrix} 0 & 0 & 1 \end{pmatrix} @f$), + * @relativeref{SceneFieldType,DualComplex} or + * @relativeref{SceneFieldType,DualComplexd} for 2D and + * @relativeref{SceneFieldType,Matrix4x4d}, + * @relativeref{SceneFieldType,Matrix4x3} or + * @relativeref{SceneFieldType,Matrix4x3d} (with the bottom row implicitly + * assumed to be @f$ \begin{pmatrix} 0 & 0 & 0 & 1 \end{pmatrix} @f$), + * @relativeref{SceneFieldType,DualQuaternion} or + * @relativeref{SceneFieldType,DualQuaterniond} for 3D. An object should + * have only one transformation, altough this isn't enforced in any way, + * and which of the duplicate fields gets used is not defined. + * + * The transformation can be also represented by separate + * @ref SceneField::Translation, @ref SceneField::Rotation and + * @ref SceneField::Scaling fields. All present transformation-related + * fields are expected to have the same dimensionality --- either all 2D or + * all 3D. If both @ref SceneField::Transformation and TRS fields are + * specified, it's expected that all objects that have TRS fields have a + * combined transformation field as well, and + * @ref SceneData::transformations2DAsArray() / + * @ref SceneData::transformations3DAsArray() then takes into account only + * the combined transformation field. TRS fields can however be specified + * only for a subset of transformed objects, useful for example when only + * certain objects have these properties animated. + * @see @ref SceneData::is2D(), @ref SceneData::is3D(), + * @ref SceneData::transformations2DAsArray(), + * @ref SceneData::transformations3DAsArray(), + * @ref SceneData::transformation2DFor(), + * @ref SceneData::transformation3DFor() + */ + Transformation, + + /** + * Translation. Type is usually @ref SceneFieldType::Vector2 for 2D and + * @ref SceneFieldType::Vector3 for 3D, but can be also any of + * @relativeref{SceneFieldType,Vector2d} for 2D and + * @relativeref{SceneFieldType,Vector3d} for 3D. An object should + * have only one translation, altough this isn't enforced in any way, + * and which of the duplicate fields gets used is not defined. + * + * The translation field usually is (but doesn't have to be) complemented + * by a @ref SceneField::Rotation and @ref SceneField::Scaling, which, if + * present, are expected to all share the same object mapping view and have + * the same dimensionality, either all 2D or all 3D. The TRS + * components can either completely replace @ref SceneField::Transformation + * or be provided just for a subset of it --- see its documentation for + * details. + * @see @ref SceneData::is2D(), @ref SceneData::is3D(), + * @ref SceneData::transformations2DAsArray(), + * @ref SceneData::transformations3DAsArray(), + * @ref SceneData::transformation2DFor(), + * @ref SceneData::transformation3DFor(), + * @ref SceneData::translationsRotationsScalings2DAsArray(), + * @ref SceneData::translationsRotationsScalings3DAsArray(), + * @ref SceneData::translationRotationScaling2DFor(), + * @ref SceneData::translationRotationScaling3DFor() + */ + Translation, + + /** + * Rotation. Type is usually @ref SceneFieldType::Complex for 2D and + * @ref SceneFieldType::Quaternion for 3D, but can be also any of + * @relativeref{SceneFieldType,Complexd} for 2D and + * @relativeref{SceneFieldType,Quaterniond} for 3D. An object should have + * only one rotation, altough this isn't enforced in any way, and which of + * the duplicate fields gets used is not defined. + * + * The rotation field usually is (but doesn't have to be) complemented by a + * @ref SceneField::Translation and @ref SceneField::Scaling, which, if + * present, are expected to all share the same object mapping view and have + * the same dimensionality, either all 2D or all 3D. The TRS + * components can either completely replace @ref SceneField::Transformation + * or be provided just for a subset of it --- see its documentation for + * details. + * @see @ref SceneData::is2D(), @ref SceneData::is3D(), + * @ref SceneData::transformations2DAsArray(), + * @ref SceneData::transformations3DAsArray(), + * @ref SceneData::transformation2DFor(), + * @ref SceneData::transformation3DFor(), + * @ref SceneData::translationsRotationsScalings2DAsArray(), + * @ref SceneData::translationsRotationsScalings3DAsArray(), + * @ref SceneData::translationRotationScaling2DFor(), + * @ref SceneData::translationRotationScaling3DFor() + */ + Rotation, + + /** + * Scaling. Type is usually @ref SceneFieldType::Vector2 for 2D and + * @ref SceneFieldType::Vector3 for 3D, but can be also any of + * @relativeref{SceneFieldType,Vector2d} for 2D and + * @relativeref{SceneFieldType,Vector3d} for 3D. An object should + * have only one scaling, altough this isn't enforced in any way, and which + * of the duplicate fields gets used is not defined. + * + * The scaling field usually is (but doesn't have to be) complemented by a + * @ref SceneField::Translation and @ref SceneField::Rotation, which, if + * present, are expected to all share the same object mapping view and have + * the same dimensionality, either all 2D or all 3D. The TRS + * components can either completely replace @ref SceneField::Transformation + * or be provided just for a subset of it --- see its documentation for + * details. + * @see @ref SceneData::is2D(), @ref SceneData::is3D(), + * @ref SceneData::transformations2DAsArray(), + * @ref SceneData::transformations3DAsArray(), + * @ref SceneData::transformation2DFor(), + * @ref SceneData::transformation3DFor(), + * @ref SceneData::translationsRotationsScalings2DAsArray(), + * @ref SceneData::translationsRotationsScalings3DAsArray(), + * @ref SceneData::translationRotationScaling2DFor(), + * @ref SceneData::translationRotationScaling3DFor() + */ + Scaling, + + /** + * ID of a mesh associated with this object, corresponding to the ID passed + * to @ref AbstractImporter::mesh(). Type is usually + * @ref SceneFieldType::UnsignedInt, but can be also any of + * @relativeref{SceneFieldType,UnsignedByte} or + * @relativeref{SceneFieldType,UnsignedShort}. An object can have multiple + * meshes associated. + * + * Usually complemented with a @ref SceneField::MeshMaterial, although not + * required. If present, both should share the same object mapping view. + * Objects with multiple meshes then have the Nth mesh associated with the + * Nth material. + * @see @ref SceneData::meshesMaterialsAsArray(), + * @ref SceneData::meshesMaterialsFor() + */ + Mesh, + + /** + * ID of a material for a @ref SceneField::Mesh, corresponding to the ID + * passed to @ref AbstractImporter::material() or @cpp -1 @ce if the mesh + * has no material associated. Type is usually @ref SceneFieldType::Int, + * but can be also any of @relativeref{SceneFieldType,Byte} or + * @relativeref{SceneFieldType,Short}. Expected to share the + * object mapping view with @ref SceneField::Mesh. + * @see @ref SceneData::meshesMaterialsAsArray(), + * @ref SceneData::meshesMaterialsFor() + */ + MeshMaterial, + + /** + * ID of a light associated with this object, corresponding to the ID + * passed to @ref AbstractImporter::light(). Type is usually + * @ref SceneFieldType::UnsignedInt, but can be also any of + * @relativeref{SceneFieldType,UnsignedByte} or + * @relativeref{SceneFieldType,UnsignedShort}. An object can have multiple + * lights associated. + * @see @ref SceneData::lightsAsArray(), @ref SceneData::lightsFor() + */ + Light, + + /** + * ID of a camera associated with this object, corresponding to the ID + * passed to @ref AbstractImporter::camera(). Type is usually + * @ref SceneFieldType::UnsignedInt, but can be also any of + * @relativeref{SceneFieldType,UnsignedByte} or + * @relativeref{SceneFieldType,UnsignedShort}. An object can have multiple + * cameras associated. + * @see @ref SceneData::camerasAsArray(), @ref SceneData::camerasFor() + */ + Camera, + + /** + * ID of a skin associated with this object, corresponding to the ID + * passed to @ref AbstractImporter::skin2D() or + * @ref AbstractImporter::skin3D(), depending on whether the scene has a 2D + * or 3D transformation. Type is usually @ref SceneFieldType::UnsignedInt, + * but can be also any of @relativeref{SceneFieldType,UnsignedByte} or + * @relativeref{SceneFieldType,UnsignedShort}. An object can have multiple + * skins associated. + * @see @ref SceneData::is2D(), @ref SceneData::is3D(), + * @ref SceneData::skinsAsArray(), @ref SceneData::skinsFor() + */ + Skin, + + /** + * Importer state for given object, per-object counterpart to + * scene-specific @ref SceneData::importerState(). Type is usually + * @ref SceneFieldType::Pointer but can be also + * @ref SceneFieldType::MutablePointer. An object should have only one + * importer state, altough this isn't enforced in any way, and which of the + * duplicate fields gets used is not defined. + * @see @ref SceneData::importerStateAsArray(), + * @ref SceneData::importerStateFor() + */ + ImporterState, + + /** + * This and all higher values are for importer-specific fields. Can be + * of any type. See documentation of a particular importer for details. + * + * While it's unlikely to have billions of custom fields, the enum + * intentionally reserves a full 31-bit range to avoid the need to remap + * field identifiers coming from 3rd party ECS frameworks, for example. + * @see @ref isSceneFieldCustom(), @ref sceneFieldCustom(SceneField), + * @ref sceneFieldCustom(UnsignedInt) + */ + Custom = 0x80000000u +}; + +/** +@debugoperatorenum{SceneField} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneField value); + +/** +@brief Whether a scene field is custom +@m_since_latest + +Returns @cpp true @ce if @p name has a value larger or equal to +@ref SceneField::Custom, @cpp false @ce otherwise. +@see @ref sceneFieldCustom(UnsignedInt), @ref sceneFieldCustom(SceneField) +*/ +constexpr bool isSceneFieldCustom(SceneField name) { + return UnsignedInt(name) >= UnsignedInt(SceneField::Custom); +} + +/** +@brief Create a custom scene field +@m_since_latest + +Returns a custom scene field with index @p id. The index is expected to be less +than the value of @ref SceneField::Custom. Use @ref sceneFieldCustom(SceneField) +to get the index back. +*/ +/* Constexpr so it's usable for creating compile-time SceneFieldData + instances */ +constexpr SceneField sceneFieldCustom(UnsignedInt id) { + return CORRADE_CONSTEXPR_ASSERT(id < UnsignedInt(SceneField::Custom), + "Trade::sceneFieldCustom(): index" << id << "too large"), + SceneField(UnsignedInt(SceneField::Custom) + id); +} + +/** +@brief Get index of a custom scene field +@m_since_latest + +Inverse to @ref sceneFieldCustom(UnsignedInt). Expects that the field is +custom. +@see @ref isSceneFieldCustom() +*/ +constexpr UnsignedInt sceneFieldCustom(SceneField name) { + return CORRADE_CONSTEXPR_ASSERT(isSceneFieldCustom(name), + "Trade::sceneFieldCustom():" << name << "is not custom"), + UnsignedInt(name) - UnsignedInt(SceneField::Custom); +} + +/** +@brief Scene field type +@m_since_latest + +A type in which a @ref SceneField is stored. See @ref SceneData for more +information. +@see @ref SceneFieldData, @ref sceneFieldTypeSize(), + @ref sceneFieldTypeAlignment() +*/ +enum class SceneFieldType: UnsignedShort { + /* Zero used for an invalid value */ + + /* 1 reserved for Bool (Bit?), which needs [Strided]BitArray[View] first */ + + Float = 2, /**< @relativeref{Magnum,Float} */ + Half, /**< @relativeref{Magnum,Half} */ + Double, /**< @relativeref{Magnum,Double} */ + UnsignedByte, /**< @relativeref{Magnum,UnsignedByte} */ + Byte, /**< @relativeref{Magnum,Byte} */ + UnsignedShort, /**< @relativeref{Magnum,UnsignedShort} */ + Short, /**< @relativeref{Magnum,Short} */ + UnsignedInt, /**< @relativeref{Magnum,UnsignedInt} */ + Int, /**< @relativeref{Magnum,Int} */ + UnsignedLong, /**< @relativeref{Magnum,UnsignedLong} */ + Long, /**< @relativeref{Magnum,Long} */ + + Vector2, /**< @relativeref{Magnum,Vector2} */ + Vector2h, /**< @relativeref{Magnum,Vector2h} */ + Vector2d, /**< @relativeref{Magnum,Vector2d} */ + Vector2ub, /**< @relativeref{Magnum,Vector2ub} */ + Vector2b, /**< @relativeref{Magnum,Vector2b} */ + Vector2us, /**< @relativeref{Magnum,Vector2us} */ + Vector2s, /**< @relativeref{Magnum,Vector2s} */ + Vector2ui, /**< @relativeref{Magnum,Vector2ui} */ + Vector2i, /**< @relativeref{Magnum,Vector2i} */ + + Vector3, /**< @relativeref{Magnum,Vector3} */ + Vector3h, /**< @relativeref{Magnum,Vector3h} */ + Vector3d, /**< @relativeref{Magnum,Vector3d} */ + Vector3ub, /**< @relativeref{Magnum,Vector3ub} */ + Vector3b, /**< @relativeref{Magnum,Vector3b} */ + Vector3us, /**< @relativeref{Magnum,Vector3us} */ + Vector3s, /**< @relativeref{Magnum,Vector3s} */ + Vector3ui, /**< @relativeref{Magnum,Vector3ui} */ + Vector3i, /**< @relativeref{Magnum,Vector3i} */ + + Vector4, /**< @relativeref{Magnum,Vector4} */ + Vector4h, /**< @relativeref{Magnum,Vector4h} */ + Vector4d, /**< @relativeref{Magnum,Vector4d} */ + Vector4ub, /**< @relativeref{Magnum,Vector4ub} */ + Vector4b, /**< @relativeref{Magnum,Vector4b} */ + Vector4us, /**< @relativeref{Magnum,Vector4us} */ + Vector4s, /**< @relativeref{Magnum,Vector4s} */ + Vector4ui, /**< @relativeref{Magnum,Vector4ui} */ + Vector4i, /**< @relativeref{Magnum,Vector4i} */ + + Matrix2x2, /**< @relativeref{Magnum,Matrix2x2} */ + Matrix2x2h, /**< @relativeref{Magnum,Matrix2x2h} */ + Matrix2x2d, /**< @relativeref{Magnum,Matrix2x2d} */ + + Matrix2x3, /**< @relativeref{Magnum,Matrix2x3} */ + Matrix2x3h, /**< @relativeref{Magnum,Matrix2x3h} */ + Matrix2x3d, /**< @relativeref{Magnum,Matrix2x3d} */ + + Matrix2x4, /**< @relativeref{Magnum,Matrix2x4} */ + Matrix2x4h, /**< @relativeref{Magnum,Matrix2x4h} */ + Matrix2x4d, /**< @relativeref{Magnum,Matrix2x4d} */ + + Matrix3x2, /**< @relativeref{Magnum,Matrix3x2} */ + Matrix3x2h, /**< @relativeref{Magnum,Matrix3x2h} */ + Matrix3x2d, /**< @relativeref{Magnum,Matrix3x2d} */ + + Matrix3x3, /**< @relativeref{Magnum,Matrix3x3} */ + Matrix3x3h, /**< @relativeref{Magnum,Matrix3x3h} */ + Matrix3x3d, /**< @relativeref{Magnum,Matrix3x3d} */ + + Matrix3x4, /**< @relativeref{Magnum,Matrix3x4} */ + Matrix3x4h, /**< @relativeref{Magnum,Matrix3x4h} */ + Matrix3x4d, /**< @relativeref{Magnum,Matrix3x4d} */ + + Matrix4x2, /**< @relativeref{Magnum,Matrix4x2} */ + Matrix4x2h, /**< @relativeref{Magnum,Matrix4x2h} */ + Matrix4x2d, /**< @relativeref{Magnum,Matrix4x2d} */ + + Matrix4x3, /**< @relativeref{Magnum,Matrix4x3} */ + Matrix4x3h, /**< @relativeref{Magnum,Matrix4x3h} */ + Matrix4x3d, /**< @relativeref{Magnum,Matrix4x3d} */ + + Matrix4x4, /**< @relativeref{Magnum,Matrix4x4} */ + Matrix4x4h, /**< @relativeref{Magnum,Matrix4x4h} */ + Matrix4x4d, /**< @relativeref{Magnum,Matrix4x4d} */ + + Range1D, /**< @relativeref{Magnum,Range1D} */ + Range1Dh, /**< @relativeref{Magnum,Range1Dh} */ + Range1Dd, /**< @relativeref{Magnum,Range1Dd} */ + Range1Di, /**< @relativeref{Magnum,Range1Di} */ + + Range2D, /**< @relativeref{Magnum,Range2D} */ + Range2Dh, /**< @relativeref{Magnum,Range2Dh} */ + Range2Dd, /**< @relativeref{Magnum,Range2Dd} */ + Range2Di, /**< @relativeref{Magnum,Range2Di} */ + + Range3D, /**< @relativeref{Magnum,Range3D} */ + Range3Dh, /**< @relativeref{Magnum,Range3Dh} */ + Range3Dd, /**< @relativeref{Magnum,Range3Dd} */ + Range3Di, /**< @relativeref{Magnum,Range3Di} */ + + Complex, /**< @relativeref{Magnum,Complex} */ + Complexd, /**< @relativeref{Magnum,Complexd} */ + DualComplex, /**< @relativeref{Magnum,DualComplex} */ + DualComplexd, /**< @relativeref{Magnum,DualComplexd} */ + + Quaternion, /**< @relativeref{Magnum,Quaternion} */ + Quaterniond, /**< @relativeref{Magnum,Quaterniond} */ + DualQuaternion, /**< @relativeref{Magnum,DualQuaternion} */ + DualQuaterniond,/**< @relativeref{Magnum,DualQuaterniond} */ + + Deg, /**< @relativeref{Magnum,Deg} */ + Degh, /**< @relativeref{Magnum,Degh} */ + Degd, /**< @relativeref{Magnum,Degh} */ + Rad, /**< @relativeref{Magnum,Rad} */ + Radh, /**< @relativeref{Magnum,Radh} */ + Radd, /**< @relativeref{Magnum,Radd} */ + + /** + * @cpp const void* @ce, type is not preserved. For convenience it's + * possible to retrieve the value by calling @cpp field() @ce + * with an arbitrary `T` but the user has to ensure the type is correct. + */ + Pointer, + + /** + * @cpp void* @ce, type is not preserved. For convenience it's possible to + * retrieve the value by calling @cpp field() @ce with an arbitrary `T` + * but the user has to ensure the type is correct. + */ + MutablePointer, +}; + +/** +@debugoperatorenum{SceneFieldType} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneFieldType value); + +/** +@brief Size of given scene field type +@m_since_latest + +@see @ref sceneFieldTypeAlignment() +*/ +MAGNUM_TRADE_EXPORT UnsignedInt sceneFieldTypeSize(SceneFieldType type); + +/** +@brief Alignment of given scene field type +@m_since_latest + +@see @ref sceneFieldTypeSize() +*/ +MAGNUM_TRADE_EXPORT UnsignedInt sceneFieldTypeAlignment(SceneFieldType type); + +/** +@brief Scene field flag +@m_since_latest + +@see @ref SceneFieldFlags, @ref SceneFieldData, @ref SceneFieldData::flags(), + @ref SceneData::fieldFlags() +*/ +enum class SceneFieldFlag: UnsignedByte { + /** + * The field is offset-only, i.e. doesn't contain the data views directly + * but referes to unspecified external data. Set implicitly by + * the @ref SceneFieldData::SceneFieldData(SceneField, std::size_t, SceneMappingType, std::size_t, std::ptrdiff_t, SceneFieldType, std::size_t, std::ptrdiff_t, UnsignedShort, SceneFieldFlags) + * constructor, can't be used for any other constructor. + * @see @ref SceneFieldData::mappingData(Containers::ArrayView) const, + * @ref SceneFieldData::fieldData(Containers::ArrayView) const + */ + OffsetOnly = 1 << 0, + + /** + * The field has an ordered object mapping, i.e. a monotonically increasing + * sequence. Object IDs in fields marked with this flag can be looked up + * with an @f$ \mathcal{O}(\log{} n) @f$ complexity, gaps and duplicates + * are possible. + * + * Note that validity of the object mapping data isn't checked in any way + * and if the data doesn't correspond to rules of the flag, queries such + * as @ref SceneData::findFieldObjectOffset() may return a wrong value. + * + * If a field has neither this nor the @ref SceneFieldFlag::ImplicitMapping + * flag, it's assumed to be unordered, with an + * @f$ \mathcal{O}(n) @f$ lookup complexity. + */ + OrderedMapping = 1 << 1, + + /** + * The field has an implicit object mapping, i.e. a contiguous sequence + * from 0 up to size of the field. A superset of + * @ref SceneFieldFlag::OrderedMapping. Object IDs in fields marked with + * this flag can be looked up with an @f$ \mathcal{O}(1) @f$ complexity, + * but the field is restricted to exactly one value for each object. + * + * Note that validity of the object mapping data isn't checked in any way + * and if the data doesn't correspond to rules of the flag, queries such + * as @ref SceneData::findFieldObjectOffset() may return a wrong value. + * + * If a field has neither this nor the @ref SceneFieldFlag::OrderedMapping + * flag, it's assumed to be unordered, with an + * @f$ \mathcal{O}(n) @f$ lookup complexity. + */ + ImplicitMapping = (1 << 2)|OrderedMapping, +}; + +/** +@debugoperatorenum{SceneFieldFlag} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneFieldFlag value); + +/** +@brief Scene field flags +@m_since_latest + +@see @ref SceneFieldData::flags(), @ref SceneData::fieldFlags() +*/ +typedef Containers::EnumSet SceneFieldFlags; + +CORRADE_ENUMSET_OPERATORS(SceneFieldFlags) + +/** +@debugoperatorenum{SceneFieldFlags} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneFieldFlags value); + +/** +@brief Scene field data +@m_since_latest + +Convenience type for populating @ref SceneData, see its documentation for an +introduction. + +@section Trade-SceneFieldData-usage Usage + +The most straightforward usage is constructing an instance from a +@ref SceneField and a strided view for the field data and object mapping. The +@ref SceneMappingType and @ref SceneFieldType get inferred from the view types: + +@snippet MagnumTrade.cpp SceneFieldData-usage + +Alternatively, you can pass typeless @cpp const void @ce or 2D views and supply +@ref SceneMappingType and @ref SceneFieldType explicitly. + +@subsection Trade-SceneFieldData-usage-offset-only Offset-only field data + +If the actual field / object data location is not known yet, the instance can +be created as "offset-only", meaning the actual view gets created only later +when passed to a @ref SceneData instance with a concrete data array. This is +useful mainly to avoid pointer patching during data serialization, less so when +the data layout is static (and thus can be defined at compile time), but the +actual data is allocated / populated at runtime: + +@snippet MagnumTrade.cpp SceneFieldData-usage-offset-only + +Offset-only fields are marked with @ref SceneFieldFlag::OffsetOnly in +@ref flags(). + +@subsection Trade-SceneFieldData-usage-object-mapping Ordered and implicit object mapping + +If you can guarantee the object mapping field is monotonically non-decreasing, +it's recommended to annotate it with @ref SceneFieldFlag::OrderedMapping. This +makes certain convenience APIs such as @ref SceneData::findFieldObjectOffset() +or e.g. @relativeref{SceneData,transformation3DFor()} perform the lookup in +@f$ \mathcal{O}(\log{} n) @f$ instead of @f$ \mathcal{O}(n) @f$. Data consuming +algorithms on the application side can then also adapt based on what flags are +present in @ref SceneData::fieldFlags(). + +In some cases the object mapping is even implicit, i.e. the first entry of the +field specifying data for object @cpp 0 @ce, second entry for object +@cpp 1 @ce, third for object @cpp 2 @ce and so on. You can annotate such fields +with @ref SceneFieldFlag::ImplicitMapping, which is a superset of +@relativeref{SceneFieldFlag,OrderedMapping}. +*/ +class MAGNUM_TRADE_EXPORT SceneFieldData { public: + /** + * @brief Default constructor + * + * Leaves contents at unspecified values. Provided as a convenience for + * initialization of the field array for @ref SceneData, expected to be + * replaced with concrete values later. + */ + constexpr explicit SceneFieldData() noexcept: _size{}, _name{}, _flags{}, _mappingType{}, _mappingStride{}, _mappingData{}, _fieldType{}, _fieldStride{}, _fieldArraySize{}, _fieldData{} {} + + /** + * @brief Type-erased constructor + * @param name Field name + * @param mappingType Object mapping type + * @param mappingData Object mapping data + * @param fieldType Field type + * @param fieldData Field data + * @param fieldArraySize Field array size. Use @cpp 0 @ce for + * non-array fields. + * @param flags Field flags. + * @ref SceneFieldFlag::OffsetOnly is not allowed here. + * + * Expects that @p mappingData and @p fieldData have the same size, + * @p fieldType corresponds to @p name and @p fieldArraySize is zero + * for builtin fields. + */ + constexpr explicit SceneFieldData(SceneField name, SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, UnsignedShort fieldArraySize = 0, SceneFieldFlags flags = {}) noexcept; + + /** @overload */ + constexpr explicit SceneFieldData(SceneField name, SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, SceneFieldFlags flags) noexcept: SceneFieldData{name, mappingType, mappingData, fieldType, fieldData, 0, flags} {} + /** * @brief Constructor - * @param children2D Two-dimensional child objects - * @param children3D Three-dimensional child objects - * @param importerState Importer-specific state + * @param name Field name + * @param mappingData Object mapping data + * @param fieldType Field type + * @param fieldData Field data + * @param fieldArraySize Field array size. Use @cpp 0 @ce for + * non-array fields. + * @param flags Field flags. + * @ref SceneFieldFlag::OffsetOnly is not allowed here. + * + * Expects that @p mappingData and @p fieldData have the same size in + * the first dimension, that the second dimension of @p mappingData is + * contiguous and its size is either 1, 2, 4 or 8, corresponding to one + * of the @ref SceneMappingType values, that the second dimension of + * @p fieldData is contiguous and its size matches @p fieldType and + * @p fieldArraySize and that @p fieldType corresponds to @p name and + * @p fieldArraySize is zero for builtin attributes. */ - explicit SceneData(std::vector children2D, std::vector children3D, const void* importerState = nullptr); + explicit SceneFieldData(SceneField name, const Containers::StridedArrayView2D& mappingData, SceneFieldType fieldType, const Containers::StridedArrayView2D& fieldData, UnsignedShort fieldArraySize = 0, SceneFieldFlags flags = {}) noexcept; - /** @brief Copying is not allowed */ - SceneData(const SceneData&) = delete; + /** @overload */ + explicit SceneFieldData(SceneField name, const Containers::StridedArrayView2D& mappingData, SceneFieldType fieldType, const Containers::StridedArrayView2D& fieldData, SceneFieldFlags flags) noexcept: SceneFieldData{name, mappingData, fieldType, fieldData, 0, flags} {} - /** @brief Move constructor */ - SceneData(SceneData&&) - /* GCC 4.9.0 (the one from Android NDK) thinks this does not match - the implicit signature so it can't be defaulted. Works on 4.8, - 5.0 and everywhere else, so I don't bother. */ - #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 - noexcept - #endif - ; + /** + * @brief Constructor + * @param name Field name + * @param mappingData Object mapping data + * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly is + * not allowed here. + * + * Detects @ref SceneMappingType based on @p T and @ref SceneFieldType + * based on @p U and calls @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort, SceneFieldFlags). + * For all types known by Magnum, the detected @ref SceneFieldType is + * of the same name as the type (so e.g. @relativeref{Magnum,Vector3ui} + * gets recognized as @ref SceneFieldType::Vector3ui). + */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView1D& fieldData, SceneFieldFlags flags = {}) noexcept; - /** @brief Copying is not allowed */ - SceneData& operator=(const SceneData&) = delete; + /** @overload */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::ArrayView& fieldData, SceneFieldFlags flags = {}) noexcept: SceneFieldData{name, mappingData, Containers::stridedArrayView(fieldData), flags} {} - /** @brief Move assignment */ - SceneData& operator=(SceneData&&) - /* GCC 4.9.0 (the one from Android NDK) thinks this does not match - the implicit signature so it can't be defaulted. Works on 4.8, - 5.0 and everywhere else, so I don't bother. */ - #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 - noexcept - #endif - ; + /** @overload */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& mappingData, const Containers::StridedArrayView1D& fieldData, SceneFieldFlags flags = {}) noexcept: SceneFieldData{name, Containers::stridedArrayView(mappingData), fieldData, flags} {} - /** @brief Two-dimensional child objects */ - const std::vector& children2D() const { return _children2D; } + /** @overload */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& mappingData, const Containers::ArrayView& fieldData, SceneFieldFlags flags = {}) noexcept: SceneFieldData{name, Containers::stridedArrayView(mappingData), Containers::stridedArrayView(fieldData), flags} {} - /** @brief Three-dimensional child objects */ - const std::vector& children3D() const { return _children3D; } + /** + * @brief Construct an array field + * @param name Field name + * @param mappingData Object mapping data + * @param fieldData Field data + * @param flags Field flags. @ref SceneFieldFlag::OffsetOnly is + * not allowed here. + * + * Detects @ref SceneMappingType based on @p T and @ref SceneFieldType + * based on @p U and calls @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort, SceneFieldFlags) + * with the @p fieldData second dimension size passed to + * @p fieldArraySize. Expects that the second dimension of @p fieldData + * is contiguous. At the moment only custom fields can be arrays, which + * means this function can't be used with a builtin @p name. See + * @ref SceneFieldData(SceneField, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, SceneFieldFlags) + * for details about @ref SceneMappingType and @ref SceneFieldType + * detection. + */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView2D& fieldData, SceneFieldFlags flags = {}) noexcept; + + /** @overload */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& mappingData, const Containers::StridedArrayView2D& fieldData, SceneFieldFlags flags = {}) noexcept: SceneFieldData{name, Containers::stridedArrayView(mappingData), fieldData, flags} {} /** - * @brief Importer-specific state + * @brief Construct an offset-only field + * @param name Field name + * @param size Number of entries + * @param mappingType Object mapping type + * @param mappingOffset Object mapping data offset + * @param mappingStride Object mapping data stride + * @param fieldType Field type + * @param fieldOffset Field data offset + * @param fieldStride Field data stride + * @param fieldArraySize Field array size. Use @cpp 0 @ce for + * non-array fields. + * @param flags Field flags. + * @ref SceneFieldFlag::OffsetOnly is set implicitly. + * + * Instances created this way refer to offsets in unspecified + * external scene data instead of containing the data views directly. + * Useful when the location of the scene data array is not known at + * field construction time. Expects that @p fieldType corresponds to + * @p name and @p fieldArraySize is zero for builtin attributes. * - * See @ref AbstractImporter::importerState() for more information. + * Note that due to the @cpp constexpr @ce nature of this constructor, + * no @p mappingType checks against @p mappingStride or + * @p fieldType / @p fieldArraySize checks against @p fieldStride can + * be done. You're encouraged to use the @ref SceneFieldData(SceneField, SceneMappingType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort, SceneFieldFlags) + * constructor if you want additional safeguards. + * @see @ref flags(), @ref fieldArraySize(), + * @ref mappingData(Containers::ArrayView) const, + * @ref fieldData(Containers::ArrayView) const */ - const void* importerState() const { return _importerState; } + explicit constexpr SceneFieldData(SceneField name, std::size_t size, SceneMappingType mappingType, std::size_t mappingOffset, std::ptrdiff_t mappingStride, SceneFieldType fieldType, std::size_t fieldOffset, std::ptrdiff_t fieldStride, UnsignedShort fieldArraySize = 0, SceneFieldFlags flags = {}) noexcept; + + /** @overload */ + explicit constexpr SceneFieldData(SceneField name, std::size_t size, SceneMappingType mappingType, std::size_t mappingOffset, std::ptrdiff_t mappingStride, SceneFieldType fieldType, std::size_t fieldOffset, std::ptrdiff_t fieldStride, SceneFieldFlags flags) noexcept: SceneFieldData{name, size, mappingType, mappingOffset, mappingStride, fieldType, fieldOffset, fieldStride, 0, flags} {} + + /** @brief Field flags */ + constexpr SceneFieldFlags flags() const { return _flags; } + + /** @brief Field name */ + constexpr SceneField name() const { return _name; } + + /** @brief Number of entries */ + constexpr UnsignedLong size() const { return _size; } + + /** @brief Object mapping type */ + constexpr SceneMappingType mappingType() const { return _mappingType; } + + /** + * @brief Type-erased object mapping data + * + * Expects that the field does not have @ref SceneFieldFlag::OffsetOnly + * set, in that case use the @ref mappingData(Containers::ArrayView) const + * overload instead. + * @see @ref flags() + */ + constexpr Containers::StridedArrayView1D mappingData() const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + {_mappingData.pointer, ~std::size_t{}}, _size, + (CORRADE_CONSTEXPR_ASSERT(!(_flags & SceneFieldFlag::OffsetOnly), "Trade::SceneFieldData::mappingData(): the field is offset-only, supply a data array"), _mappingStride)}; + } + + /** + * @brief Type-erased object mapping data for an offset-only attribute + * + * If the field does not have @ref SceneFieldFlag::OffsetOnly set, the + * @p data parameter is ignored. + * @see @ref flags(), @ref mappingData() const + */ + Containers::StridedArrayView1D mappingData(Containers::ArrayView data) const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + data, _flags & SceneFieldFlag::OffsetOnly ? reinterpret_cast(data.data()) + _mappingData.offset : _mappingData.pointer, _size, _mappingStride}; + } + + /** @brief Field type */ + constexpr SceneFieldType fieldType() const { return _fieldType; } + + /** @brief Field array size */ + constexpr UnsignedShort fieldArraySize() const { return _fieldArraySize; } + + /** + * @brief Type-erased field data + * + * Expects that the field does not have @ref SceneFieldFlag::OffsetOnly + * set, in that case use the @ref fieldData(Containers::ArrayView) const + * overload instead. + * @see @ref flags() + */ + constexpr Containers::StridedArrayView1D fieldData() const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + {_fieldData.pointer, ~std::size_t{}}, _size, + (CORRADE_CONSTEXPR_ASSERT(!(_flags & SceneFieldFlag::OffsetOnly), "Trade::SceneFieldData::fieldData(): the field is offset-only, supply a data array"), _fieldStride)}; + } + + /** + * @brief Type-erased field data for an offset-only attribute + * + * If the field does not have @ref SceneFieldFlag::OffsetOnly set, the + * @p data parameter is ignored. + * @see @ref flags(), @ref fieldData() const + */ + Containers::StridedArrayView1D fieldData(Containers::ArrayView data) const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + data, _flags & SceneFieldFlag::OffsetOnly ? reinterpret_cast(data.data()) + _fieldData.offset : _fieldData.pointer, _size, _fieldStride}; + } private: - std::vector _children2D, - _children3D; - const void* _importerState; + friend SceneData; + + union Data { + /* FFS C++ why this doesn't JUST WORK goddamit?! It's already past + the End Of Times AND YET this piece of complex shit can't do the + obvious! */ + constexpr Data(const void* pointer = nullptr): pointer{pointer} {} + constexpr Data(std::size_t offset): offset{offset} {} + + const void* pointer; + std::size_t offset; + }; + + UnsignedLong _size; + SceneField _name; + SceneFieldFlags _flags; + SceneMappingType _mappingType; + Short _mappingStride; + Data _mappingData; + + SceneFieldType _fieldType; + Short _fieldStride; + UnsignedShort _fieldArraySize; + /* 2 bytes free */ + Data _fieldData; }; +/** @relatesalso SceneFieldData +@brief Create a non-owning array of @ref SceneFieldData items +@m_since_latest + +Useful when you have the field definitions statically defined (for example when +the data themselves are already defined at compile time) and don't want to +allocate just to pass those to @ref SceneData. +*/ +Containers::Array MAGNUM_TRADE_EXPORT sceneFieldDataNonOwningArray(Containers::ArrayView view); + +/** +@brief Scene data + +Contains scene node hierarchy, transformations, resource assignment as well as +any other data associated with the scene. Populated instances of this class are +returned from @ref AbstractImporter::scene(). + +@section Trade-SceneData-representation Data representation and terminology + +@m_div{m-container-inflate m-col-l-9 m-left-l} +@htmlinclude scenedata-tree.svg +@m_enddiv + +The usual mental image of a scene is a tree hierarchy with varying amount of +data attached to each node, like shown in the first diagram. The @ref SceneData +however decouples the hierarchy from the data and stores everything in linear +arrays, like in the second diagram. + +This allows for a more efficient storage, as only the actually needed +information is stored. For example, three nodes in the tree have an implicit +transformation, which we can simply omit, or because there might be way less +materials than meshes, their references can be in a smaller type. It's also +more flexible --- having multiple meshes per node is just about having multiple +entries associated with the same node. + +@m_div{m-clearfix-l} @m_enddiv + +@m_div{m-container-inflate m-col-l-8 m-right-l} +@htmlinclude scenedata-dod.svg +@m_enddiv + +From a high-level perspective, the scene data storage can thought of as a set +of *Fields*, with field entries mapped to *Objects*. Scene *Nodes* are a +special case of *Objects*. + +An *Object* is an arbitrary numeric identifier, not containing anything on its +own. All objects referenced by a particular scene are contained in a range from +@cpp 0 @ce up to @ref mappingBound() minus one. The range is allowed to be +sparse. + +A *Field* is a list of data --- for example transformations, mesh IDs, or +parent objects. The @ref SceneField enum lists all predefined fields together +with possible restrictions and the expected @ref SceneFieldType they're +expected to be in. Custom fields are supported as well. Field entries are +mapped to objects with the same 8-, 16-, 32- or 64-bit type for all fields, +indicated with @ref SceneMappingType. Generally there's a 1:N mapping between +objects and fields (not all objects need to have a transformation, a single +object can reference multiple meshes...), but certain field types expect +various restrictions (such as an object allowed to only have one parent or +transformation). + +Finally, scene *Nodes* are *Objects* that have the @ref SceneField::Parent +field associated. An *Object* thus doesn't have to represent just a node in the +hierarchy. For example, a scene can also contain an alternative representation +in the form of an octree, and thus some objects would be nodes and some octree +cells. + +@subsection Trade-SceneData-representation-multi-scene Object identifiers and multiple scenes + +For a standalone scene, a common case is that the object identifiers form a +contigous range of numbers, and each of the objects has at least one field +assigned. + +The @ref AbstractImporter supports files with multiple scenes. All imported +scenes share a single object range, from @cpp 0 @ce to +@ref AbstractImporter::objectCount(). A particular object can be part of any of +the scenes, causing the @ref SceneData::mappingBound() ranges to be sparse --- +a particular scene having certain object IDs that have no fields assigned. This +is something to be aware of when consuming the scene data, that not all objects +identifiers in the mapping range may actually exist. + +It's also possible for a single object identifier to be contained in multiple +scenes at the same time --- for example, when two scenes are variants of the +same model, with most data shared but certain textures or colors different. +Another theoretical use case is that an object could identify a building in a +3D scene and a corresponding area on a map in a 2D scene. There's no set of +rules the objects should follow, but such identifier reusal should not be +abused for completely unrelated objects. + +@todoc mention handles and how they would affect the basic use below (aaaa!) + +@section Trade-SceneData-usage Basic usage + +A simple goal could be to populate a @ref SceneGraph with a node hierarchy +and attach drawables for meshes where appropriate. First we check if the scene +is 3D with @ref is3D(), because if it's not, it could mean it's either 2D or +that it has no transformation field altogether, suggesting a need for +specialized handling. It's also of no use for this example if there's no node +hierarchy, or if there are no meshes we could draw. + +Then we create the scene instance and an array of pointers that will act as a +map from object identifiers to live objects. The @ref mappingBound() is an +upper bound to all object identifiers referenced by the scene, but as mentioned +above, not all of them may be actual nodes so we don't allocate actual scene +graph object instances for them yet. Alternatively, for very sparse ranges, a hashmap could be also used here. + +@snippet MagnumTrade.cpp SceneData-usage1 + + + +@m_class{m-noindent} + +Next we go through objects that have an associated parent using +@ref parentsAsArray(). Those are the actual nodes we want, so we allocate a +scene graph object for each ... + +@snippet MagnumTrade.cpp SceneData-usage2 + +@m_class{m-noindent} + + + +... and then we assign a proper parent, or add it directly to the scene if the +parent is @cpp -1 @ce. We do this in a separate pass to ensure the parent +object is already allocated by the time we pass it to +@ref SceneGraph::Object::setParent() --- generally there's no guarantee that a +parent appears in the field before its children. + +@snippet MagnumTrade.cpp SceneData-usage3 + +With the hierarchy done, we assign transformations. The transformation field +can be present for only a subset of the nodes, with the rest implicitly having +an indentity transformation, but it can also be present for objects that aren't +nodes, so we only set it for objects present in our hierarchy. The +@ref transformations3DAsArray() function also conveniently converts separate +transformation / rotation / scaling fields into a matrix for us, if the scene +contains only those. + +@snippet MagnumTrade.cpp SceneData-usage4 + +Finally, assuming there's a `Drawable` class derived from +@ref SceneGraph::Drawable that accepts a mesh and material ID (retrieving them +subsequently from @ref AbstractImporter::mesh() / +@relativeref{AbstractImporter,material()}, for example), the process of +assigning actual meshes to corresponding scene nodes is just another +@cpp for @ce loop over @ref meshesMaterialsAsArray(): + +@snippet MagnumTrade.cpp SceneData-usage5 + + + +@m_class{m-note m-success} + +@par + The full process of importing a scene including meshes, materials and + textures is shown in the @ref examples-viewer example. + +@section Trade-SceneData-usage-advanced Advanced usage + +The @ref parentsAsArray(), ... functions shown above always return a +newly-allocated @relativeref{Corrade,Containers::Array} instance in a +well-defined canonical type. While that's convenient and fine at a smaller +scale, it may prove problematic with huge scenes. Or maybe the internal +representation is already optimized for best processing efficiency and the +convenience functions would ruin that. The @ref SceneData class thus provides +access directly to the stored object mapping and field data using the +@ref mapping() and @ref field() accessors. + +However, since each @ref SceneField can be in a variety of types, you're +expected to either check that the type is indeed what you expect using +@ref fieldType(SceneField) const, or at least check with documentation of the +corresponding importer. For example, because glTF files represent the scene +in a textual form, @ref CgltfImporter will always parse the data into canonical +32-bit types. With that assumption, the above snippet that used +@ref transformations3DAsArray() can be rewritten to a zero-copy form like this: + +@snippet MagnumTrade.cpp SceneData-usage-advanced + +@section Trade-SceneData-usage-per-object Per-object access + +While the designated way to access scene data is by iterating through the field +and object arrays, it's also possible to directly look at fields for a +particular object without having to do a lookup on your own and with simplified +error handling. The @ref parentFor(), @ref childrenFor(), +@ref transformation3DFor(), @ref meshesMaterialsFor() and other functions +return either an @relativeref{Corrade,Containers::Optional} or an +@relativeref{Corrade,Containers::Array} depending on whether there's expected +just one occurence of the field or more, returning an empty optional or array +if the field is not present in the scene or if the object was not found in the +field array. + +For example, together with an @ref AbstractImporter instance the scene comes +from, the following snippet lists meshes and material names that are associated +with a "Chair" object, assuming such object exists: + +@snippet MagnumTrade.cpp SceneData-per-object + +The actual object ID lookup is done by @ref findFieldObjectOffset() and +depending on what @ref SceneFieldFlags are present for given field, it can be +done in constant, logarithmic or, worst case, linear time. As such, for general +scene representations these are suited mainly for introspection and debugging +purposes and retrieving field data for many objects is better achieved by +accessing the field data directly. + +@section Trade-SceneData-usage-mutable Mutable data access + +The interfaces implicitly provide @cpp const @ce views on the contained object +and field data through the @ref data(), @ref mapping() and @ref field() +accessors. This is done because in general case the data can also refer to a +memory-mapped file or constant memory. In cases when it's desirable to modify +the data in-place, there's the @ref mutableData(), @ref mutableMapping() and +@ref mutableField() set of functions. To use these, you need to check that +the data are mutable using @ref dataFlags() first. The following snippet +updates all transformations with the live state of a scene imported earlier, +for example in order to bake in a certain animation state: + +@snippet MagnumTrade.cpp SceneData-usage-mutable + +@section Trade-SceneData-populating Populating an instance + +The actual data in a @ref SceneData instance are represented as a single block +of contiguous memory, which all object and field views point to. This is +easiest to achieve with an @relativeref{Corrade,Containers::ArrayTuple}. In the +example below, all objects have a parent and a transformation field, which are +stored together in a @cpp struct @ce, while a subset of them has a mesh and a +material assigned, which are stored in separate arrays. And because the scene +is small, we save space by using just 16-bit indices for everything. + +@snippet MagnumTrade.cpp SceneData-populating + +Note that the above layout is just an example, you're free to choose any +representation that matches your use case best, with fields interleaved +together or not. See also the @ref SceneFieldData class documentation for +additional ways how to specify and annotate the data. + +@subsection Trade-SceneData-populating-custom Custom scene fields and non-node objects + +Let's say that, in addition to the node hierarchy from above, our scene +contains also a precomputed [camera-space light culling grid](https://wickedengine.net/2018/01/10/optimizing-tile-based-light-culling/), +where each cell of the grid contains a list of lights that affect given area of +the screen. And we want to add it into the @ref SceneData for verification with +external tools. + +For simplicity let's assume we have a 32x24 grid and the shader we have can +work with up to 8 lights. So there will be a fixed-size array for each of those +cells, and we save calculated frustums for inspection as well. For the new data +we allocate object IDs from a range after `nodeCount`, and copy in the actual +data. + +@snippet MagnumTrade.cpp SceneData-populating-custom1 + +Then, similarly as with @ref MeshData, the scene can have custom fields as +well, created with @ref sceneFieldCustom(). We create one for the cell light +reference array and one for the cell frustum and then use them to annotate +the views allocated above. Note that we also increased the total object count +to include the light culling grid cells as well. + +@snippet MagnumTrade.cpp SceneData-populating-custom2 + +Later, the fields can be retrieved back using the same custom identifiers. +The light references are actually a 2D array (8 lights for each cell), so a +@cpp [] @ce needs to be used: + +@snippet MagnumTrade.cpp SceneData-populating-custom-retrieve + +@see @ref AbstractImporter::scene() +*/ +class MAGNUM_TRADE_EXPORT SceneData { + public: + /** + * @brief Construct scene data + * @param mappingType Object mapping type + * @param mappingBound Upper bound on object mapping indices in the + * scene + * @param data Data for all fields and object mappings + * @param fields Description of all scene field data + * @param importerState Importer-specific state + * @m_since_latest + * + * The @p mappingType is expected to be large enough to index + * @p mappingBound objects. The @p fields are expected to reference + * (sparse) sub-ranges of @p data, each having an unique + * @ref SceneField, and @ref SceneMappingType equal to @p mappingType. + * Particular fields can have additional restrictions, see + * documentation of @ref SceneField values for more information. + * + * The @ref dataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For non-owned data + * use the @ref SceneData(SceneMappingType, UnsignedLong, DataFlags, Containers::ArrayView, Containers::Array&&, const void*) + * constructor or its variants instead. + */ + explicit SceneData(SceneMappingType mappingType, UnsignedLong mappingBound, Containers::Array&& data, Containers::Array&& fields, const void* importerState = nullptr) noexcept; + + /** + * @overload + * @m_since_latest + */ + /* Not noexcept because allocation happens inside */ + explicit SceneData(SceneMappingType mappingType, UnsignedLong mappingBound, Containers::Array&& data, std::initializer_list fields, const void* importerState = nullptr); + + /** + * @brief Construct non-owned scene data + * @param mappingType Object mapping type + * @param mappingBound Upper bound on object mapping indices in the + * scene + * @param dataFlags Data flags + * @param data View on data for all fields and object mappings + * @param fields Description of all scene field data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref SceneData(SceneMappingType, UnsignedLong, Containers::Array&&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit SceneData(SceneMappingType mappingType, UnsignedLong mappingBound, DataFlags dataFlags, Containers::ArrayView data, Containers::Array&& fields, const void* importerState = nullptr) noexcept; + + /** + * @overload + * @m_since_latest + */ + /* Not noexcept because allocation happens inside */ + explicit SceneData(SceneMappingType mappingType, UnsignedLong mappingBound, DataFlags dataFlags, Containers::ArrayView data, std::initializer_list fields, const void* importerState = nullptr); + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Constructor + * @param children2D Two-dimensional child objects + * @param children3D Three-dimensional child objects + * @param importerState Importer-specific state + * @m_deprecated_since_latest Use @ref SceneData(SceneMappingType, UnsignedLong, Containers::Array&&, Containers::Array&&, const void*) + * instead. + */ + explicit CORRADE_DEPRECATED("use SceneData(SceneMappingType, UnsignedLong, Containers::Array&&, Containers::Array&&, const void*) instead") SceneData(std::vector children2D, std::vector children3D, const void* importerState = nullptr); + #endif + + /** @brief Copying is not allowed */ + SceneData(const SceneData&) = delete; + + /** @brief Move constructor */ + SceneData(SceneData&&) noexcept; + + ~SceneData(); + + /** @brief Copying is not allowed */ + SceneData& operator=(const SceneData&) = delete; + + /** @brief Move assignment */ + SceneData& operator=(SceneData&&) noexcept; + + /** + * @brief Data flags + * @m_since_latest + * + * @see @ref releaseData(), @ref mutableData(), @ref mutableMapping(), + * @ref mutableField() + */ + DataFlags dataFlags() const { return _dataFlags; } + + /** + * @brief Raw data + * @m_since_latest + * + * Returns @cpp nullptr @ce if the scene has no data. + */ + Containers::ArrayView data() const & { return _data; } + + /** + * @brief Taking a view to a r-value instance is not allowed + * @m_since_latest + */ + Containers::ArrayView data() const && = delete; + + /** + * @brief Mutable raw data + * @m_since_latest + * + * Like @ref data(), but returns a non-const view. Expects that the + * scene is mutable. + * @see @ref dataFlags() + */ + Containers::ArrayView mutableData() &; + + /** + * @brief Taking a view to a r-value instance is not allowed + * @m_since_latest + */ + Containers::ArrayView mutableData() && = delete; + + /** + * @brief Type used for object mapping + * @m_since_latest + * + * Type returned from @ref mapping() and @ref mutableMapping(). It's + * the same for all fields and is guaranteed to be large enough to fit + * @ref mappingBound() objects. + */ + SceneMappingType mappingType() const { return _mappingType; } + + /** + * @brief Object mapping bound + * @m_since_latest + * + * Upper bound on object mapping indices of all fields in the scene. + * @see @ref fieldCount(), @ref fieldSize() + */ + UnsignedLong mappingBound() const { return _mappingBound; } + + /** + * @brief Field count + * @m_since_latest + * + * Count of different fields contained in the scene, or @cpp 0 @ce for + * a scene with no fields. Each @ref SceneField can be present only + * once, however an object can have a certain field associated with it + * multiple times with different values (for example an object having + * multiple meshes). + */ + UnsignedInt fieldCount() const { return _fields.size(); } + + /** + * @brief Raw field metadata + * @m_since_latest + * + * Returns the raw data that are used as a base for all `field*()` + * accessors, or @cpp nullptr @ce if the scene has no fields. In most + * cases you don't want to access those directly, but rather use the + * @ref mapping(), @ref field(), @ref fieldName(), @ref fieldType(), + * @ref fieldSize() and @ref fieldArraySize() accessors. Compared to + * those and to @ref fieldData(UnsignedInt) const, the + * @ref SceneFieldData instances returned by this function may have + * different data pointers, and some of them might have + * @ref SceneFieldFlag::OffsetOnly set --- use this function only if + * you *really* know what are you doing. + * @see @ref SceneFieldData::flags() + */ + Containers::ArrayView fieldData() const & { return _fields; } + + /** + * @brief Taking a view to a r-value instance is not allowed + * @m_since_latest + */ + Containers::ArrayView fieldData() const && = delete; + + /** + * @brief Raw field data + * @m_since_latest + * + * Returns the raw data that are used as a base for all `field*()` + * accessors. In most cases you don't want to access those directly, + * but rather use the @ref mapping(), @ref field(), @ref fieldName(), + * @ref fieldType(), @ref fieldSize() and @ref fieldArraySize() + * accessors. This is also the reason why there's no overload taking a + * @ref SceneField, unlike the other accessors. + * + * Unlike with @ref fieldData() and @ref releaseFieldData(), returned + * instances are guaranteed to always have an absolute data pointer + * (i.e., @ref SceneFieldData::flags() never having + * @ref SceneFieldFlag::OffsetOnly set). The @p id is expected to be + * smaller than @ref fieldCount(). + */ + SceneFieldData fieldData(UnsignedInt id) const; + + /** + * @brief Field name + * @m_since_latest + * + * The @p id is expected to be smaller than @ref fieldCount(). + * @see @ref fieldType(), @ref isSceneFieldCustom(), + * @ref AbstractImporter::sceneFieldForName(), + * @ref AbstractImporter::sceneFieldName() + */ + SceneField fieldName(UnsignedInt id) const; + + /** + * @brief Field flags + * @m_since_latest + * + * The @p id is expected to be smaller than @ref fieldCount(). + * @see @ref findFieldObjectOffset(UnsignedInt, UnsignedInt, std::size_t) const + */ + SceneFieldFlags fieldFlags(UnsignedInt id) const; + + /** + * @brief Field type + * @m_since_latest + * + * The @p id is expected to be smaller than @ref fieldCount(). You can + * also use @ref fieldType(SceneField) const to directly get a type of + * given named field. + * @see @ref fieldName(), @ref mappingType() + */ + SceneFieldType fieldType(UnsignedInt id) const; + + /** + * @brief Size of given field + * @m_since_latest + * + * Size of the view returned by @ref mapping() / @ref mutableMapping() + * and @ref field() / @ref mutableField() for given @p id. Since an + * object can have multiple entries of the same field (for example + * multiple meshes associated with an object), the size doesn't + * necessarily match the number of objects having given field. + * + * The @p id is expected to be smaller than @ref fieldCount(). You can + * also use @ref fieldSize(SceneField) const to directly get a size of + * given named field. + */ + std::size_t fieldSize(UnsignedInt id) const; + + /** + * @brief Field array size + * @m_since_latest + * + * In case given field is an array (the euqivalent of e.g. + * @cpp int[30] @ce), returns array size, otherwise returns @cpp 0 @ce. + * At the moment only custom fields can be arrays, no builtin + * @ref SceneField is an array attribute. Note that this is different + * from the count of entries for given field, which is exposed through + * @ref fieldSize(). See @ref Trade-SceneData-populating-custom for an + * example. + * + * The @p id is expected to be smaller than @ref fieldCount(). You can + * also use @ref fieldArraySize(SceneField) const to directly get a + * type of given named field. + */ + UnsignedShort fieldArraySize(UnsignedInt id) const; + + /** + * @brief Whether the scene is two-dimensional + * @m_since_latest + * + * Returns @cpp true @ce if the present + * @ref SceneField::Transformation, + * @relativeref{SceneField,Translation}, + * @relativeref{SceneField,Rotation} and + * @relativeref{SceneField,Scaling} fields have a 2D type, + * @cpp false @ce otherwise. + * + * If there's no transformation-related field, the scene is treated as + * neither 2D nor 3D and both @ref is2D() and @ref is3D() return + * @cpp false @ce. On the other hand, a scene can't be both 2D and 3D. + * @see @ref hasField() + */ + bool is2D() const { return _dimensions == 2; } + + /** + * @brief Whether the scene is three-dimensional + * @m_since_latest + * + * Returns @cpp true @ce if the present + * @ref SceneField::Transformation, + * @relativeref{SceneField,Translation}, + * @relativeref{SceneField,Rotation} and + * @relativeref{SceneField,Scaling} fields have a 3D type, + * @cpp false @ce otherwise. + * + * If there's no transformation-related field, the scene is treated as + * neither 2D nor 3D and both @ref is2D() and @ref is3D() return + * @cpp false @ce. On the other hand, a scene can't be both 2D and 3D. + * @see @ref hasField() + */ + bool is3D() const { return _dimensions == 3; } + + /** + * @brief Find an absolute ID of a named field + * @m_since_latest + * + * If @p name doesn't exist, returns @ref Containers::NullOpt. The + * lookup is done in an @f$ \mathcal{O}(n) @f$ complexity with + * @f$ n @f$ being the field count. + * @see @ref hasField(), @ref fieldId() + */ + Containers::Optional findFieldId(SceneField name) const; + + /** + * @brief Absolute ID of a named field + * @m_since_latest + * + * Like @ref findFieldId(), but the @p name is expected to exist. + * @see @ref hasField(), @ref fieldName(UnsignedInt) const + */ + UnsignedInt fieldId(SceneField name) const; + + /** + * @brief Whether the scene has given field + * @m_since_latest + * + * @see @ref is2D(), @ref is3D() + */ + bool hasField(SceneField name) const; + + /** + * @brief Find offset of an object in given field + * @m_since_latest + * + * If @p object isn't present in @p fieldId starting at @p offset, + * returns @ref Containers::NullOpt. The @p fieldId is expected to be + * smaller than @ref fieldCount(), @p object smaller than + * @ref mappingBound() and @p offset not larger than + * @ref fieldSize(UnsignedInt) const. + * + * If the field has @ref SceneFieldFlag::ImplicitMapping, the lookup is + * done in an @f$ \mathcal{O}(1) @f$ complexity. Otherwise, if the + * field has @ref SceneFieldFlag::OrderedMapping, the lookup is done in + * an @f$ \mathcal{O}(\log{} n) @f$ complexity with @f$ n @f$ being the + * size of the field. Otherwise, the lookup is done in an + * @f$ \mathcal{O}(n) @f$ complexity. + * + * You can also use @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * to directly find offset of an object in given named field. + * @see @ref hasFieldObject(UnsignedInt, UnsignedInt) const, + * @ref fieldObjectOffset(UnsignedInt, UnsignedInt, std::size_t) const + */ + Containers::Optional findFieldObjectOffset(UnsignedInt fieldId, UnsignedLong object, std::size_t offset = 0) const; + + /** + * @brief Find offset of an object in given named field + * @m_since_latest + * + * If @p object isn't present in @p fieldName starting at @p offset, + * returns @ref Containers::NullOpt. The @p fieldName is expected to + * exist, @p object is expected to be smaller than @ref mappingBound() + * and @p offset not be larger than @ref fieldSize(SceneField) const. + * + * If the field has @ref SceneFieldFlag::ImplicitMapping, the lookup is + * done in an @f$ \mathcal{O}(m) @f$ complexity with @f$ m @f$ being + * the * field count. Otherwise, if the field has + * @ref SceneFieldFlag::OrderedMapping, the lookup is done in an + * @f$ \mathcal{O}(m + \log{} n) @f$ complexity with @f$ m @f$ being + * the field count and @f$ n @f$ the size of the field. Otherwise, the + * lookup is done in an @f$ \mathcal{O}(m + n) @f$ complexity. + * + * @see @ref hasField(), @ref hasFieldObject(SceneField, UnsignedInt) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + Containers::Optional findFieldObjectOffset(SceneField fieldName, UnsignedLong object, std::size_t offset = 0) const; + + /** + * @brief Offset of an object in given field + * @m_since_latest + * + * Like @ref findFieldObjectOffset(UnsignedInt, UnsignedInt, std::size_t) const, + * but @p object is additionally expected to be present in @p fieldId + * starting at @p offset. + * + * You can also use @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * to directly get offset of an object in given named field. + */ + std::size_t fieldObjectOffset(UnsignedInt fieldId, UnsignedLong object, std::size_t offset = 0) const; + + /** + * @brief Offset of an object in given named field + * @m_since_latest + * + * Like @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const, + * but @p object is additionally expected to be present in @p fieldName + * starting at @p offset. + */ + std::size_t fieldObjectOffset(SceneField fieldName, UnsignedLong object, std::size_t offset = 0) const; + + /** + * @brief Whether a scene field has given object + * @m_since_latest + * + * The @p fieldId is expected to be smaller than @ref fieldCount() and + * @p object smaller than @ref mappingBound(). + */ + bool hasFieldObject(UnsignedInt fieldId, UnsignedLong object) const; + + /** + * @brief Whether a named scene field has given object + * @m_since_latest + * + * The @p fieldName is expected to exist and @p object is expected to + * be smaller than @ref mappingBound(). + * @see @ref hasField() + */ + bool hasFieldObject(SceneField fieldName, UnsignedLong object) const; + + /** + * @brief Field flags + * @m_since_latest + * + * The @p name is expected to exist. + * @see @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + SceneFieldFlags fieldFlags(SceneField name) const; + + /** + * @brief Type of a named field + * @m_since_latest + * + * The @p name is expected to exist. + * @see @ref hasField(), @ref fieldType(UnsignedInt) const + */ + SceneFieldType fieldType(SceneField name) const; + + /** + * @brief Number of entries for given named field + * @m_since_latest + * + * The @p name is expected to exist. + * @see @ref hasField(), @ref fieldSize(UnsignedInt) const + */ + std::size_t fieldSize(SceneField name) const; + + /** + * @brief Array size of a named field + * @m_since_latest + * + * The @p name is expected to exist. + * @see @ref hasField(), @ref fieldArraySize(UnsignedInt) const + */ + UnsignedShort fieldArraySize(SceneField name) const; + + /** + * @brief Object mapping data for given field + * @m_since_latest + * + * The @p fieldId is expected to be smaller than @ref fieldCount(). The + * second dimension represents the actual data type (its size is equal + * to @ref SceneMappingType size) and is guaranteed to be contiguous. + * Use the templated overload below to get the mapping in a concrete + * type. + * @see @ref mutableMapping(UnsignedInt), + * @ref Corrade::Containers::StridedArrayView::isContiguous(), + * @ref sceneMappingTypeSize() + */ + Containers::StridedArrayView2D mapping(UnsignedInt fieldId) const; + + /** + * @brief Mutable object mapping data for given field + * @m_since_latest + * + * Like @ref mapping(UnsignedInt) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + Containers::StridedArrayView2D mutableMapping(UnsignedInt fieldId); + + /** + * @brief Object mapping for given field + * @m_since_latest + * + * The @p fieldId is expected to be smaller than @ref fieldCount() and + * @p T is expected to correspond to @ref mappingType(). + * + * You can also use the non-templated @ref mappingAsArray() accessor + * (or the combined @ref parentsAsArray(), + * @ref transformations2DAsArray(), @ref transformations3DAsArray(), + * @ref translationsRotationsScalings2DAsArray(), + * @ref translationsRotationsScalings3DAsArray(), + * @ref meshesMaterialsAsArray(), @ref lightsAsArray(), + * @ref camerasAsArray(), @ref skinsAsArray(), + * @ref importerStateAsArray() accessors) to get the object mapping + * converted to the usual type, but note that these operations involve + * extra allocation and data conversion. + * @see @ref mutableMapping(UnsignedInt) + */ + template Containers::StridedArrayView1D mapping(UnsignedInt fieldId) const; + + /** + * @brief Mutable object mapping for given field + * @m_since_latest + * + * Like @ref mapping(UnsignedInt) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + template Containers::StridedArrayView1D mutableMapping(UnsignedInt fieldId); + + /** + * @brief Object mapping data for given named field + * @m_since_latest + * + * The @p fieldName is expected to exist. The second dimension + * represents the actual data type (its size is equal to + * @ref SceneMappingType size) and is guaranteed to be contiguous. Use + * the templated overload below to get the object mapping in a concrete + * type. + * @see @ref hasField(), @ref mapping(UnsignedInt) const, + * @ref mutableMapping(SceneField), + * @ref Corrade::Containers::StridedArrayView::isContiguous() + */ + Containers::StridedArrayView2D mapping(SceneField fieldName) const; + + /** + * @brief Mutable object mapping data for given named field + * @m_since_latest + * + * Like @ref mapping(SceneField) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + Containers::StridedArrayView2D mutableMapping(SceneField fieldName); + + /** + * @brief Object mapping for given named field + * @m_since_latest + * + * The @p fieldName is expected to exist and @p T is expected to + * correspond to @ref mappingType(). + * + * You can also use the non-templated @ref mappingAsArray() accessor + * (or the combined @ref parentsAsArray(), + * @ref transformations2DAsArray(), @ref transformations3DAsArray(), + * @ref translationsRotationsScalings2DAsArray(), + * @ref translationsRotationsScalings3DAsArray(), + * @ref meshesMaterialsAsArray(), @ref lightsAsArray(), + * @ref camerasAsArray(), @ref skinsAsArray(), + * @ref importerStateAsArray() accessors) to get the object mapping + * converted to the usual type, but note that these operations involve + * extra allocation and data conversion. + * @see @ref hasField(), @ref mapping(UnsignedInt) const, + * @ref mutableMapping(UnsignedInt) + */ + template Containers::StridedArrayView1D mapping(SceneField fieldName) const; + + /** + * @brief Mutable object mapping for given named field + * @m_since_latest + * + * Like @ref mapping(SceneField) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + template Containers::StridedArrayView1D mutableMapping(SceneField fieldName); + + /** + * @brief Data for given field + * @m_since_latest + * + * The @p id is expected to be smaller than @ref fieldCount(). The + * second dimension represents the actual data type (its size is equal + * to @ref SceneFieldType size, possibly multiplied by array size) and + * is guaranteed to be contiguous. Use the templated overload below to + * get the field in a concrete type. + * @see @ref Corrade::Containers::StridedArrayView::isContiguous(), + * @ref sceneFieldTypeSize(), @ref mutableField(UnsignedInt) + */ + Containers::StridedArrayView2D field(UnsignedInt id) const; + + /** + * @brief Mutable data for given field + * @m_since_latest + * + * Like @ref field(UnsignedInt) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + Containers::StridedArrayView2D mutableField(UnsignedInt id); + + /** + * @brief Data for given field in a concrete type + * @m_since_latest + * + * The @p id is expected to be smaller than @ref fieldCount() and @p T + * is expected to correspond to @ref fieldType(UnsignedInt) const. The + * field is also expected to not be an array, in that case you need to + * use the overload below by using @cpp T[] @ce instead of @cpp T @ce. + * + * You can also use the non-templated @ref parentsAsArray(), + * @ref transformations2DAsArray(), @ref transformations3DAsArray(), + * @ref translationsRotationsScalings2DAsArray(), + * @ref translationsRotationsScalings3DAsArray(), + * @ref meshesMaterialsAsArray(), @ref lightsAsArray(), + * @ref camerasAsArray(), @ref skinsAsArray(), + * @ref importerStateAsArray() accessors to get common fields converted + * to usual types, but note that these operations involve extra + * allocation and data conversion. + * @see @ref field(SceneField) const, @ref mutableField(UnsignedInt), + * @ref fieldArraySize() + */ + template::value>::type> Containers::StridedArrayView1D field(UnsignedInt id) const; + + /** + * @brief Data for given array field in a concrete type + * @m_since_latest + * + * Same as above, except that it works with array fields instead --- + * you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref fieldArraySize(UnsignedInt) const for given field. + */ + template::value>::type> Containers::StridedArrayView2D::type> field(UnsignedInt id) const; + + /** + * @brief Mutable data for given field in a concrete type + * @m_since_latest + * + * Like @ref field(UnsignedInt) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + template::value>::type> Containers::StridedArrayView1D mutableField(UnsignedInt id); + + /** + * @brief Mutable data for given array field in a concrete type + * @m_since_latest + * + * Same as above, except that it works with array fields instead --- + * you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref fieldArraySize(UnsignedInt) const for given field. + */ + template::value>::type> Containers::StridedArrayView2D::type> mutableField(UnsignedInt id); + + /** + * @brief Data for given named field + * @m_since_latest + * + * The @p name is expected to exist. The second dimension represents + * the actual data type (its size is equal to @ref SceneFieldType size, + * possibly multiplied by array size) and is guaranteed to be + * contiguous. Use the templated overload below to get the field in a + * concrete type. + * @see @ref hasField(), @ref field(UnsignedInt) const, + * @ref mutableField(SceneField), + * @ref Corrade::Containers::StridedArrayView::isContiguous() + */ + Containers::StridedArrayView2D field(SceneField name) const; + + /** + * @brief Mutable data for given named field + * @m_since_latest + * + * Like @ref field(SceneField) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + Containers::StridedArrayView2D mutableField(SceneField name); + + /** + * @brief Data for given named field in a concrete type + * @m_since_latest + * + * The @p name is expected to exist and @p T is expected to correspond + * to @ref fieldType(SceneField) const. The field is also expected to + * not be an array, in that case you need to use the overload below by + * using @cpp T[] @ce instead of @cpp T @ce. + * + * You can also use the non-templated @ref parentsAsArray(), + * @ref transformations2DAsArray(), @ref transformations3DAsArray(), + * @ref translationsRotationsScalings2DAsArray(), + * @ref translationsRotationsScalings3DAsArray(), + * @ref meshesMaterialsAsArray(), @ref lightsAsArray(), + * @ref camerasAsArray(), @ref skinsAsArray(), + * @ref importerStateAsArray() accessors to get common fields converted + * to usual types, but note that these operations involve extra + * allocation and data conversion. + * @see @ref field(UnsignedInt) const, @ref mutableField(SceneField) + */ + template::value>::type> Containers::StridedArrayView1D field(SceneField name) const; + + /** + * @brief Data for given named array field in a concrete type + * @m_since_latest + * + * Same as above, except that it works with array fields instead --- + * you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref fieldArraySize(SceneField) const for given field. + */ + template::value>::type> Containers::StridedArrayView2D::type> field(SceneField name) const; + + /** + * @brief Mutable data for given named field + * @m_since_latest + * + * Like @ref field(SceneField) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + template::value>::type> Containers::StridedArrayView1D mutableField(SceneField name); + + /** + * @brief Mutable data for given named array field in a concrete type + * @m_since_latest + * + * Same as above, except that it works with array fields instead --- + * you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref fieldArraySize(SceneField) const for given field. + */ + template::value>::type> Containers::StridedArrayView2D::type> mutableField(SceneField name); + + /** + * @brief Object mapping for given field as 32-bit integers + * @m_since_latest + * + * Convenience alternative to the templated + * @ref mapping(UnsignedInt) const that converts the field from an + * arbitrary underlying type and returns it in a newly-allocated array. + * The @p fieldId is expected to be smaller than @ref fieldCount(). + * + * Note that, for common fields, you can also use the + * @ref parentsAsArray(), @ref transformations2DAsArray(), + * @ref transformations3DAsArray(), + * @ref translationsRotationsScalings2DAsArray(), + * @ref translationsRotationsScalings3DAsArray(), + * @ref meshesMaterialsAsArray(), @ref lightsAsArray(), + * @ref camerasAsArray(), @ref skinsAsArray(), + * @ref importerStateAsArray() accessors, which give out the object + * mapping together with the field data. + * @see @ref mappingInto(UnsignedInt, const Containers::StridedArrayView1D&) const + */ + Containers::Array mappingAsArray(UnsignedInt fieldId) const; + + /** + * @brief Object mapping for given field as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref mappingAsArray(UnsignedInt) const, but puts the result + * into @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * + * Note that, for common fields, you can also use the + * @ref parentsInto(), @ref transformations2DInto(), + * @ref transformations3DInto(), + * @ref translationsRotationsScalings2DInto(), + * @ref translationsRotationsScalings3DInto(), + * @ref meshesMaterialsInto(), @ref lightsInto(), @ref camerasInto(), + * @ref skinsInto(), @ref importerStateInto() accessors, which can give + * out the object mapping together with the field data. + * + * @see @ref fieldSize(UnsignedInt) const + */ + void mappingInto(UnsignedInt fieldId, const Containers::StridedArrayView1D& destination) const; + + /** + * @brief A subrange of object mapping for given field as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref mappingInto(UnsignedInt, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the object mapping defined by @p offset + * and size of the @p destination view, returning the count of items + * actually extracted. The @p offset is expected to not be larger than + * the field size. + * + * Note that, for common fields, you can also use the + * @ref parentsInto(), @ref transformations2DInto(), + * @ref transformations3DInto(), + * @ref translationsRotationsScalings2DInto(), + * @ref translationsRotationsScalings3DInto(), + * @ref meshesMaterialsInto(), @ref lightsInto(), @ref camerasInto(), + * @ref skinsInto(), @ref importerStateInto() accessors, which can give + * out the object mapping together with the field data. + * + * @see @ref fieldSize(UnsignedInt) const, + * @ref fieldObjectOffset(UnsignedInt, UnsignedInt, std::size_t) const + */ + std::size_t mappingInto(UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + + /** + * @brief Object mapping for given named field as 32-bit integers + * @m_since_latest + * + * Convenience alternative to the templated + * @ref mapping(SceneField) const that converts the field from an + * arbitrary underlying type and returns it in a newly-allocated array. + * The @p fieldName is expected to exist. + * + * Note that, for common fields, you can also use the + * @ref parentsAsArray(), @ref transformations2DAsArray(), + * @ref transformations3DAsArray(), + * @ref translationsRotationsScalings2DAsArray(), + * @ref translationsRotationsScalings3DAsArray(), + * @ref meshesMaterialsAsArray(), @ref lightsAsArray(), + * @ref camerasAsArray(), @ref skinsAsArray(), + * @ref importerStateAsArray() accessors, which give out the object + * mapping together with the field data. + * @see @ref mappingInto(SceneField, const Containers::StridedArrayView1D&) const, + * @ref hasField() + */ + Containers::Array mappingAsArray(SceneField fieldName) const; + + /** + * @brief Object mapping for given named field as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref mappingAsArray(SceneField) const, but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * + * Note that, for common fields, you can also use the + * @ref parentsInto(), @ref transformations2DInto(), + * @ref transformations3DInto(), + * @ref translationsRotationsScalings2DInto(), + * @ref translationsRotationsScalings3DInto(), + * @ref meshesMaterialsInto(), @ref lightsInto(), @ref camerasInto(), + * @ref skinsInto(), @ref importerStateInto() accessors, which can give + * out the object mapping together with the field data. + * + * @see @ref fieldSize(SceneField) const + */ + void mappingInto(SceneField fieldName, const Containers::StridedArrayView1D& destination) const; + + /** + * @brief A subrange of object mapping for given named field as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref mappingInto(SceneField, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the object mapping defined by @p offset + * and size of the @p destination view, returning the count of items + * actually extracted. The @p offset is expected to not be larger than + * the field size. + * + * Note that, for common fields, you can also use the + * @ref parentsInto(), @ref transformations2DInto(), + * @ref transformations3DInto(), + * @ref translationsRotationsScalings2DInto(), + * @ref translationsRotationsScalings3DInto(), + * @ref meshesMaterialsInto(), @ref lightsInto(), @ref camerasInto(), + * @ref skinsInto(), @ref importerStateInto() accessors, which can give + * out the object mapping together with the field data. + * + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + std::size_t mappingInto(SceneField fieldName, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + + /** + * @brief Parent indices as 32-bit integers + * @m_since_latest + * + * Convenience alternative to @ref mapping(SceneField) const together + * with @ref field(SceneField) const with @ref SceneField::Parent as + * the argument. Converts the object mapping and the field from + * arbitrary underlying types and returns them in a newly-allocated + * array. The field is expected to exist. + * @see @ref parentsInto(), @ref hasField(), @ref parentFor(), + * @ref childrenFor() + */ + Containers::Array> parentsAsArray() const; + + /** + * @brief Parent indices as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref parentsAsArray(), but puts the result into + * @p mappingDestination and @p fieldDestination instead of allocating + * a new array. Expects that each view is either @cpp nullptr @ce or + * sized to contain exactly all data. If @p fieldDestination is + * @cpp nullptr @ce, the effect is the same as calling + * @ref mappingInto() with @ref SceneField::Parent. + * @see @ref fieldSize(SceneField) const + */ + void parentsInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const; + + /** + * @brief A subrange of parent indices as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref parentsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the views, returning the count of items actually extracted. The + * @p offset is expected to not be larger than the field size, views + * that are not @cpp nullptr @ce are expected to have the same size. + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + std::size_t parentsInto(std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const; + + /** + * @brief 2D transformations as 3x3 float matrices + * @m_since_latest + * + * Convenience alternative to @ref mapping(SceneField) const together + * with @ref field(SceneField) const with + * @ref SceneField::Transformation as the argument, or, if not present, + * to a matrix created out of a subset of the + * @ref SceneField::Translation, @ref SceneField::Rotation and + * @ref SceneField::Scaling fields that's present. Converts the object + * mapping and the fields from arbitrary underlying types and returns + * them in a newly-allocated array. At least one of the fields is + * expected to exist and they are expected to have a type corresponding + * to 2D, otherwise you're supposed to use + * @ref transformations3DAsArray(). + * @see @ref is2D(), @ref transformations2DInto(), @ref hasField(), + * @ref fieldType(SceneField) const, @ref transformation2DFor() + */ + Containers::Array> transformations2DAsArray() const; + + /** + * @brief 2D transformations as 3x3 float matrices into a pre-allocated view + * @m_since_latest + * + * Like @ref transformations2DAsArray(), but puts the result into + * @p mappingDestination and @p fieldDestination instead of allocating + * a new array. Expects that each view is either @cpp nullptr @ce or + * sized to contain exactly all data. If @p fieldDestination is + * @cpp nullptr @ce, the effect is the same as calling + * @ref mappingInto() with the first of the + * @ref SceneField::Transformation, @ref SceneField::Translation, + * @ref SceneField::Rotation and @ref SceneField::Scaling fields that's + * present. + * @see @ref fieldSize(SceneField) const + */ + void transformations2DInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const; + + /** + * @brief A subrange of 2D transformations as 3x3 float matrices into a pre-allocated view + * @m_since_latest + * + * Compared to @ref transformations2DInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the views, returning the count of items actually extracted. The + * @p offset is expected to not be larger than the field size, views + * that are not @cpp nullptr @ce are expected to have the same size. + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + std::size_t transformations2DInto(std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const; + + /** + * @brief 2D transformations as float translation, rotation and scaling components + * @m_since_latest + * + * Convenience alternative to @ref mapping(SceneField) const together + * with @ref field(SceneField) const with @ref SceneField::Translation, + * @ref SceneField::Rotation and @ref SceneField::Scaling as the + * arguments, as these are required to share the same object mapping. + * Converts the object mapping and the fields from arbitrary underlying + * types and returns them in a newly-allocated array. At least one of + * the fields is expected to exist and they are expected to have a type + * corresponding to 2D, otherwise you're supposed to use + * @ref translationsRotationsScalings3DAsArray(). If the + * @ref SceneField::Translation field isn't present, the first returned + * value is a zero vector. If the @relativeref{SceneField,Rotation} + * field isn't present, the second value is an identity rotation. If + * the @relativeref{SceneField,Scaling} field isn't present, the third + * value is an identity scaling (@cpp 1.0f @ce in both dimensions). + * @see @ref is2D(), @ref translationsRotationsScalings2DInto(), + * @ref hasField(), @ref fieldType(SceneField) const, + * @ref translationRotationScaling2DFor() + */ + Containers::Array>> translationsRotationsScalings2DAsArray() const; + + /** + * @brief 2D transformations as float translation, rotation and scaling components into a pre-allocated view + * @m_since_latest + * + * Like @ref translationsRotationsScalings2DAsArray(), but puts the + * result into @p mappingDestination, @p translationDestination, + * @p rotationDestination and @p scalingDestination instead of + * allocating a new array. Expects that each view is either + * @cpp nullptr @ce or sized to contain exactly all data. If + * @p translationDestination, @p rotationDestination and + * @p scalingDestination are all @cpp nullptr @ce, the effect is the + * same as calling @ref mappingInto() with one of the + * @ref SceneField::Translation, @ref SceneField::Rotation and + * @ref SceneField::Scaling fields that's present. + * @see @ref fieldSize(SceneField) const + */ + void translationsRotationsScalings2DInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; + + /** + * @brief A subrange of 2D transformations as float translation, rotation and scaling components into a pre-allocated view + * @m_since_latest + * + * Compared to @ref translationsRotationsScalings2DInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the views, returning the count of items actually extracted. The + * @p offset is expected to not be larger than the field size, views + * that are not @cpp nullptr @ce are expected to have the same size. + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + std::size_t translationsRotationsScalings2DInto(std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; + + /** + * @brief 3D transformations as 4x4 float matrices + * @m_since_latest + * + * Convenience alternative to @ref mapping(SceneField) const together + * with @ref field(SceneField) const with + * @ref SceneField::Transformation as the argument, or, if not present, + * to a matrix created out of a subset of the + * @ref SceneField::Translation, @ref SceneField::Rotation and + * @ref SceneField::Scaling fields that's present. Converts the object + * mapping and the fields from arbitrary underlying types and returns + * them in a newly-allocated array. At least one of the fields is + * expected to exist and they are expected to have a type corresponding + * to 3D, otherwise you're supposed to use + * @ref transformations2DAsArray(). + * @see @ref is3D(), @ref transformations3DInto(), @ref hasField(), + * @ref fieldType(SceneField) const, @ref transformation3DFor() + */ + Containers::Array> transformations3DAsArray() const; + + /** + * @brief 3D transformations as 4x4 float matrices into a pre-allocated view + * @m_since_latest + * + * Like @ref transformations3DAsArray(), but puts the result into + * @p mappingDestination and @p fieldDestination instead of allocating + * a new array. Expects that the two views are either @cpp nullptr @ce + * or sized to contain exactly all data. If @p fieldDestination is + * @cpp nullptr @ce, the effect is the same as calling + * @ref mappingInto() with the first of the + * @ref SceneField::Transformation, @ref SceneField::Translation, + * @ref SceneField::Rotation and @ref SceneField::Scaling fields that's + * present. + * @see @ref fieldSize(SceneField) const + */ + void transformations3DInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& destination) const; + + /** + * @brief A subrange of 3D transformations as 4x4 float matrices into a pre-allocated view + * @m_since_latest + * + * Compared to @ref transformations3DInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the views, returning the count of items actually extracted. The + * @p offset is expected to not be larger than the field size, views + * that are not @cpp nullptr @ce are expected to have the same size. + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + std::size_t transformations3DInto(std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& destination) const; + + /** + * @brief 3D transformations as float translation, rotation and scaling components + * @m_since_latest + * + * Convenience alternative to @ref mapping(SceneField) const together + * with @ref field(SceneField) const with @ref SceneField::Translation, + * @ref SceneField::Rotation and @ref SceneField::Scaling as the + * arguments, as these are required to share the same object mapping. + * Converts the object mapping and the fields from arbitrary underlying + * types and returns them in a newly-allocated array. At least one of + * the fields is expected to exist and they are expected to have a type + * corresponding to 3D, otherwise you're supposed to use + * @ref translationsRotationsScalings2DAsArray(). If the + * @ref SceneField::Translation field isn't present, the first returned + * value is a zero vector. If the @relativeref{SceneField,Rotation} + * field isn't present, the second value is an identity rotation. If + * the @relativeref{SceneField,Scaling} field isn't present, the third + * value is an identity scaling (@cpp 1.0f @ce in all dimensions). + * @see @ref is3D(), @ref translationsRotationsScalings3DInto(), + * @ref hasField(), @ref fieldType(SceneField) const, + * @ref translationRotationScaling3DFor() + */ + Containers::Array>> translationsRotationsScalings3DAsArray() const; + + /** + * @brief 3D transformations as float translation, rotation and scaling components into a pre-allocated view + * @m_since_latest + * + * Like @ref translationsRotationsScalings3DAsArray(), but puts the + * result into @p mappingDestination, @p translationDestination, + * @p rotationDestination and @p scalingDestination instead of + * allocating a new array. Expects that each view is either + * @cpp nullptr @ce or sized to contain exactly all data. If + * @p translationDestination, @p rotationDestination and + * @p scalingDestination are all @cpp nullptr @ce, the effect is the + * same as calling @ref mappingInto() with one of the + * @ref SceneField::Translation, @ref SceneField::Rotation and + * @ref SceneField::Scaling fields that's present. + * @see @ref fieldSize(SceneField) const + */ + void translationsRotationsScalings3DInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; + + /** + * @brief A subrange of 3D transformations as float translation, rotation and scaling components into a pre-allocated view + * @m_since_latest + * + * Compared to @ref translationsRotationsScalings3DInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the views, returning the count of items actually extracted. The + * @p offset is expected to not be larger than the field size, views + * that are not @cpp nullptr @ce are expected to have the same size. + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + std::size_t translationsRotationsScalings3DInto(std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; + + /** + * @brief Mesh and material IDs as 32-bit integers + * @m_since_latest + * + * Convenience alternative to @ref mapping(SceneField) const together + * with @ref field(SceneField) const with @ref SceneField::Mesh and + * @ref SceneField::MeshMaterial as the argument, as the two are + * required to share the same object mapping. Converts the object + * mapping and the fields from arbitrary underlying types and returns + * them in a newly-allocated array. The @ref SceneField::Mesh field is + * expected to exist, if @ref SceneField::MeshMaterial isn't present, + * the second returned values are all @cpp -1 @ce. + * @see @ref meshesMaterialsInto(), @ref hasField(), + * @ref meshesMaterialsFor() + */ + Containers::Array>> meshesMaterialsAsArray() const; + + /** + * @brief Mesh and material IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref meshesMaterialsAsArray(), but puts the results into + * @p mappingDestination, @p meshDestination and + * @p meshMaterialDestination instead of allocating a new array. + * Expects that each view is either @cpp nullptr @ce or sized to + * contain exactly all data. If @p meshDestination and + * @p meshMaterialDestination are both @cpp nullptr @ce, the effect is + * the same as calling @ref mappingInto() with @ref SceneField::Mesh. + * @see @ref fieldSize(SceneField) const + */ + void meshesMaterialsInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& meshDestination, const Containers::StridedArrayView1D& meshMaterialDestination) const; + + /** + * @brief A subrange of mesh and material IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref meshesMaterialsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the views, returning the count of items actually extracted. The + * @p offset is expected to not be larger than the field size, views + * that are not @cpp nullptr @ce are expected to have the same size. + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + std::size_t meshesMaterialsInto(std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& meshDestination, const Containers::StridedArrayView1D& meshMaterialsDestination) const; + + /** + * @brief Light IDs as 32-bit integers + * @m_since_latest + * + * Convenience alternative to @ref mapping(SceneField) const together + * with @ref field(SceneField) const with @ref SceneField::Light as the + * argument. Converts the object mapping and the field from arbitrary + * underlying types and returns them in a newly-allocated array. The + * field is expected to exist. + * @see @ref lightsInto(), @ref hasField(), @ref lightsFor() + */ + Containers::Array> lightsAsArray() const; + + /** + * @brief Light IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref lightsAsArray(), but puts the result into + * @p mappingDestination and @p fieldDestination instead of allocating + * a new array. Expects that each view is either @cpp nullptr @ce or + * sized to contain exactly all data. If @p fieldDestination is + * @cpp nullptr @ce, the effect is the same as calling + * @ref lightsInto() with @ref SceneField::Light. + * @see @ref fieldSize(SceneField) const + */ + void lightsInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const; + + /** + * @brief A subrange of light IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref lightsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the views, returning the count of items actually extracted. The + * @p offset is expected to not be larger than the field size, views + * that are not @cpp nullptr @ce are expected to have the same size. + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + std::size_t lightsInto(std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const; + + /** + * @brief Camera IDs as 32-bit integers + * @m_since_latest + * + * Convenience alternative to @ref mapping(SceneField) const together + * with @ref field(SceneField) const with @ref SceneField::Camera as + * the argument. Converts the object mapping and the field from + * arbitrary underlying types and returns them in a newly-allocated + * array. The field is expected to exist. + * @see @ref camerasInto(), @ref hasField(), @ref camerasFor() + */ + Containers::Array> camerasAsArray() const; + + /** + * @brief Camera IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref camerasAsArray(), but puts the result into + * @p mappingDestination and @p fieldDestination instead of allocating + * a new array. Expects that each view is either @cpp nullptr @ce or + * sized to contain exactly all data. If @p fieldDestination is + * @cpp nullptr @ce, the effect is the same as calling + * @ref mappingInto() with @ref SceneField::Camera. + * @see @ref fieldSize(SceneField) const + */ + void camerasInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const; + + /** + * @brief A subrange of camera IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref camerasInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the views, returning the count of items actually extracted. The + * @p offset is expected to not be larger than the field size, views + * that are not @cpp nullptr @ce are expected to have the same size. + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + std::size_t camerasInto(std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const; + + /** + * @brief Skin IDs as 32-bit integers + * @m_since_latest + * + * Convenience alternative to @ref mapping(SceneField) const together + * with @ref field(SceneField) const with @ref SceneField::Skin as the + * argument. Converts the object mapping and the field from arbitrary + * underlying types and returns them in a newly-allocated array. The + * field is expected to exist. + * @see @ref skinsInto(), @ref hasField(), @ref skinsFor() + */ + Containers::Array> skinsAsArray() const; + + /** + * @brief Skin IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref skinsAsArray(), but puts the result into + * @p mappingDestination and @p fieldDestination instead of allocating a + * new array. Expects that each view is either @cpp nullptr @ce or + * sized to contain exactly all data. If @p fieldDestination is + * @cpp nullptr @ce, the effect is the same as calling + * @ref mappingInto() with @ref SceneField::Skin. + * @see @ref fieldSize(SceneField) const + */ + void skinsInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const; + + /** + * @brief A subrange of skin IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref skinsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the views, returning the count of items actually extracted. The + * @p offset is expected to not be larger than the field size, views + * that are not @cpp nullptr @ce are expected to have the same size. + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + std::size_t skinsInto(std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const; + + /** + * @brief Per-object importer state as `void` pointers + * @m_since_latest + * + * Convenience alternative to @ref mapping(SceneField) const together + * with @ref field(SceneField) const with @ref SceneField::ImporterState + * as the argument. Converts the object mapping and the field from + * arbitrary underlying types and returns them in a newly-allocated + * array. The field is expected to exist. + * + * This is different from @ref importerState(), which returns importer + * state for the scene itself, not particular objects. + * @see @ref importerStateInto(), @ref hasField(), @ref importerStateFor() + */ + Containers::Array> importerStateAsArray() const; + + /** + * @brief Per-object importer state as `void` pointers into a pre-allocated view + * @m_since_latest + * + * Like @ref importerStateAsArray(), but puts the result into + * @p mappingDestination and @p fieldDestination instead of allocating + * a new array. Expects that each view is either @cpp nullptr @ce or + * sized to contain exactly all data. If @p fieldDestination is + * @cpp nullptr @ce, the effect is the same as calling + * @ref mappingInto() with @ref SceneField::ImporterState. + * @see @ref fieldSize(SceneField) const + */ + void importerStateInto(const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const; + + /** + * @brief A subrange of per-object importer state as `void` pointers into a pre-allocated view + * @m_since_latest + * + * Compared to @ref importerStateInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the views, returning the count of items actually extracted. The + * @p offset is expected to not be larger than the field size, views + * that are not @cpp nullptr @ce are expected to have the same size. + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + std::size_t importerStateInto(std::size_t offset, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& fieldDestination) const; + + /** + * @brief Parent for given object + * @m_since_latest + * + * Looks up the @ref SceneField::Parent field for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the field from an arbitrary underlying type the + * same way as @ref parentsAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving parent + * info for many objects it's recommended to access the field data + * directly. + * + * If the @ref SceneField::Parent field is not present or if there's no + * parent for @p object, returns @ref Containers::NullOpt. If @p object + * is top-level, returns @cpp -1 @ce. + * + * The @p object is expected to be less than @ref mappingBound(). + * @see @ref childrenFor() + */ + Containers::Optional parentFor(UnsignedLong object) const; + + /** + * @brief Children for given object + * @m_since_latest + * + * Looks up @p object in the object mapping array for + * @ref SceneField::Parent equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const, + * converts the fields from an arbitrary underlying type the same way + * as @ref parentsAsArray(), returning a list of all object IDs that + * have it listed as the parent. See the lookup function documentation + * for operation complexity --- for retrieving parent/child info for + * many objects it's recommended to access the field data directly. + * + * If the @ref SceneField::Parent field doesn't exist or there are no + * objects which would have @p object listed as their parent, returns + * an empty array. Pass @cpp -1 @ce to get a list of top-level objects. + * + * The @p object is expected to be less than @ref mappingBound(). + * @see @ref parentFor() + */ + Containers::Array childrenFor(Long object) const; + + /** + * @brief 2D transformation for given object + * @m_since_latest + * + * Looks up the @ref SceneField::Transformation field for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * or combines it from a @ref SceneField::Translation, + * @relativeref{SceneField,Rotation} and + * @relativeref{SceneField,Scaling}, converting the fields from + * arbitrary underlying types the same way as + * @ref transformations2DAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving + * transformation info for many objects it's recommended to access the + * field data directly. + * + * If neither @ref SceneField::Transformation nor any of + * @ref SceneField::Translation, @relativeref{SceneField,Rotation} or + * @relativeref{SceneField,Scaling} is present, the fields represent a + * 3D transformation or there's no transformation for @p object, + * returns @ref Containers::NullOpt. + * + * The @p object is expected to be less than @ref mappingBound(). + * @see @ref translationRotationScaling2DFor() + */ + Containers::Optional transformation2DFor(UnsignedLong object) const; + + /** + * @brief 2D translation, rotation and scaling for given object + * @m_since_latest + * + * Looks up the @ref SceneField::Translation, + * @relativeref{SceneField,Rotation} and + * @relativeref{SceneField,Scaling} fields for @p object equivalently + * to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the fields from arbitrary underlying types the + * same way as @ref translationsRotationsScalings2DAsArray(). See the + * lookup function documentation for operation complexity --- for + * retrieving transformation info for many objects it's recommended to + * access the field data directly. + * + * If the @ref SceneField::Translation field isn't present, the first + * returned value is a zero vector. If the + * @relativeref{SceneField,Rotation} field isn't present, the second + * value is an identity rotation. If the @relativeref{SceneField,Scaling} + * field isn't present, the third value is an identity scaling + * (@cpp 1.0f @ce in both dimensions). If neither of those fields is + * present, if any of the fields represents a 3D transformation or if + * there's no transformation for @p object, returns + * @ref Containers::NullOpt. + * + * The @p object is expected to be less than @ref mappingBound(). + * @see @ref transformation2DFor() + */ + Containers::Optional> translationRotationScaling2DFor(UnsignedLong object) const; + + /** + * @brief 3D transformation for given object + * @m_since_latest + * + * Looks up the @ref SceneField::Transformation field for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * or combines it from a @ref SceneField::Translation, + * @relativeref{SceneField,Rotation} and + * @relativeref{SceneField,Scaling}, converting the fields from + * arbitrary underlying types the same way as + * @ref transformations3DAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving + * transformation info for many objects it's recommended to access the + * field data directly. + * + * If neither @ref SceneField::Transformation nor any of + * @ref SceneField::Translation, @relativeref{SceneField,Rotation} or + * @relativeref{SceneField,Scaling} is present, the fields represent a + * 2D transformation or there's no transformation for @p object, + * returns @ref Containers::NullOpt. + * + * The @p object is expected to be less than @ref mappingBound(). + * @see @ref translationRotationScaling3DFor() + */ + Containers::Optional transformation3DFor(UnsignedLong object) const; + + /** + * @brief 3D translation, rotation and scaling for given object + * @m_since_latest + * + * Looks up the @ref SceneField::Translation, + * @relativeref{SceneField,Rotation} and + * @relativeref{SceneField,Scaling} fields for @p object equivalently + * to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the fields from arbitrary underlying types the + * same way as @ref translationsRotationsScalings2DAsArray(). See the + * lookup function documentation for operation complexity --- for + * retrieving transformation info for many objects it's recommended to + * access the field data directly. + * + * If the @ref SceneField::Translation field isn't present, the first + * returned value is a zero vector. If the + * @relativeref{SceneField,Rotation} field isn't present, the second + * value is an identity rotation. If the @relativeref{SceneField,Scaling} + * field isn't present, the third value is an identity scaling + * (@cpp 1.0f @ce in all dimensions). If neither of those fields is + * present, if any of the fields represents a 2D transformation or if + * there's no transformation for @p object, returns + * @ref Containers::NullOpt. + * + * The @p object is expected to be less than @ref mappingBound(). + * @see @ref transformation3DFor() + */ + Containers::Optional> translationRotationScaling3DFor(UnsignedLong object) const; + + /** + * @brief Meshes and materials for given object + * @m_since_latest + * + * Looks up all @ref SceneField::Mesh and @ref SceneField::MeshMaterial + * @relativeref{SceneField,Scaling} fields for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the field from an arbitrary underlying type the + * same way as @ref meshesMaterialsAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving mesh and + * material info for many objects it's recommended to access the field + * data directly. + * + * If the @ref SceneField::MeshMaterial field is not present, the + * second returned value is always @cpp -1 @ce. If + * @ref SceneField::Mesh is not present or if there's no mesh for + * @p object, returns an empty array. + * + * The @p object is expected to be less than @ref mappingBound(). + */ + Containers::Array> meshesMaterialsFor(UnsignedLong object) const; + + /** + * @brief Lights for given object + * @m_since_latest + * + * Looks up all @ref SceneField::Light fields for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the field from an arbitrary underlying type the + * same way as @ref lightsAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving light info + * for many objects it's recommended to access the field data directly. + * + * If the @ref SceneField::Light field is not present or if there's no + * light for @p object, returns an empty array. + * + * The @p object is expected to be less than @ref mappingBound(). + */ + Containers::Array lightsFor(UnsignedLong object) const; + + /** + * @brief Cameras for given object + * @m_since_latest + * + * Looks up all @ref SceneField::Camera fields for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the field from an arbitrary underlying type the + * same way as @ref camerasAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving camera + * info for many objects it's recommended to access the field data + * directly. + * + * If the @ref SceneField::Camera field is not present or if there's no + * camera for @p object, returns an empty array. + * + * The @p object is expected to be less than @ref mappingBound(). + */ + Containers::Array camerasFor(UnsignedLong object) const; + + /** + * @brief Skins for given object + * @m_since_latest + * + * Looks up all @ref SceneField::Skin fields for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the field from an arbitrary underlying type the + * same way as @ref skinsAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving skin info + * for many objects it's recommended to access the field data directly. + * + * If the @ref SceneField::Skin field is not present or if there's no + * skin for @p object, returns an empty array. + * + * The @p object is expected to be less than @ref mappingBound(). + */ + Containers::Array skinsFor(UnsignedLong object) const; + + /** + * @brief Importer state for given object + * @m_since_latest + * + * Looks up the @ref SceneField::ImporterState field for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the field from an arbitrary underlying type the + * same way as @ref importerStateAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving importer + * state info for many objects it's recommended to access the field + * data directly. + * + * If the @ref SceneField::ImporterState field is not present or if + * there's no importer state for @p object, returns + * @ref Containers::NullOpt. + * + * The @p object is expected to be less than @ref mappingBound(). + */ + Containers::Optional importerStateFor(UnsignedLong object) const; + + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Two-dimensional root scene objects + * @m_deprecated_since_latest Use @ref childrenFor() with `-1` passed + * as the @p object argument. + */ + CORRADE_DEPRECATED("use childrenFor() instead") std::vector children2D() const; + + /** + * @brief Three-dimensional root scene objects + * @m_deprecated_since_latest Use @ref childrenFor() with `-1` passed + * as the @p object argument. + */ + CORRADE_DEPRECATED("use childrenFor() instead") std::vector children3D() const; + #endif + + /** + * @brief Release field data storage + * @m_since_latest + * + * Releases the ownership of the field data array and resets internal + * field-related state to default. The scene then behaves like if it + * has no fields (but it can still have non-empty data). Note that the + * returned array has a custom no-op deleter when the data are not + * owned by the scene, and while the returned array type is mutable, + * the actual memory might be not. Additionally, the returned + * @ref SceneFieldData instances may have different data pointers and + * sizes than what's returned by the @ref field() and + * @ref fieldData(UnsignedInt) const accessors as some of them might + * have @ref SceneFieldFlag::OffsetOnly --- use this function only if + * you *really* know what are you doing. + * @see @ref fieldData(), @ref SceneFieldData::flags() + */ + Containers::Array releaseFieldData(); + + /** + * @brief Release data storage + * @m_since_latest + * + * Releases the ownership of the data array and resets internal + * field-related state to default. The scene then behaves like it has + * no fields and no data. If you want to release field data as well, + * first call @ref releaseFieldData() and then this function. + * + * Note that the returned array has a custom no-op deleter when the + * data are not owned by the scene, and while the returned array type + * is mutable, the actual memory might be not. + * @see @ref data(), @ref dataFlags() + */ + Containers::Array releaseData(); + + /** + * @brief Importer-specific state + * + * Scene-specific importer state. For object-specific importer state + * look for the @ref SceneField::ImporterState field or access it via + * @ref importerStateAsArray(), @ref importerStateFor() and related + * convenience functions. See @ref AbstractImporter::importerState() + * for general information about importer state pointers. + */ + const void* importerState() const { return _importerState; } + + private: + /* For custom deleter checks. Not done in the constructors here because + the restriction is pointless when used outside of plugin + implementations. */ + friend AbstractImporter; + + /* Internal helper without the extra overhead from Optional, returns + ~UnsignedInt{} on failure */ + UnsignedInt findFieldIdInternal(SceneField name) const; + + /* Returns the offset at which `object` is for field at index `id`, or + the end offset if the object is not found. The returned offset can + be then passed to fieldData{Mapping,Field}ViewInternal(). */ + MAGNUM_TRADE_LOCAL std::size_t findFieldObjectOffsetInternal(const SceneFieldData& field, UnsignedLong object, std::size_t offset) const; + + /* Like objects() / field(), but returning just a 1D view, sliced from + offset to offset + size. The parameterless overloads are equal to + offset = 0 and size = field.size(). */ + MAGNUM_TRADE_LOCAL Containers::StridedArrayView1D fieldDataMappingViewInternal(const SceneFieldData& field, std::size_t offset, std::size_t size) const; + MAGNUM_TRADE_LOCAL Containers::StridedArrayView1D fieldDataMappingViewInternal(const SceneFieldData& field) const; + MAGNUM_TRADE_LOCAL Containers::StridedArrayView1D fieldDataFieldViewInternal(const SceneFieldData& field, std::size_t offset, std::size_t size) const; + MAGNUM_TRADE_LOCAL Containers::StridedArrayView1D fieldDataFieldViewInternal(const SceneFieldData& field) const; + + #ifndef CORRADE_NO_ASSERT + template bool checkFieldTypeCompatibility(const SceneFieldData& attribute, const char* prefix) const; + #endif + + MAGNUM_TRADE_LOCAL void mappingIntoInternal(UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL void parentsIntoInternal(UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL UnsignedInt findTransformFields(UnsignedInt& transformationFieldId, UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId) const; + MAGNUM_TRADE_LOCAL UnsignedInt findTranslationRotationScalingFields(UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId) const; + MAGNUM_TRADE_LOCAL void transformations2DIntoInternal(UnsignedInt transformationFieldId, UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL void translationsRotationsScalings2DIntoInternal(UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; + MAGNUM_TRADE_LOCAL void transformations3DIntoInternal(UnsignedInt transformationFieldId, UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL void translationsRotationsScalings3DIntoInternal(UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; + MAGNUM_TRADE_LOCAL void unsignedIndexFieldIntoInternal(const UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL void indexFieldIntoInternal(const UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL Containers::Array> unsignedIndexFieldAsArrayInternal(const UnsignedInt fieldId) const; + MAGNUM_TRADE_LOCAL void meshesMaterialsIntoInternal(UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& meshDestination, const Containers::StridedArrayView1D& meshMaterialDestination) const; + MAGNUM_TRADE_LOCAL void importerStateIntoInternal(const UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + + DataFlags _dataFlags; + SceneMappingType _mappingType; + UnsignedByte _dimensions; + /* 1/5 bytes free */ + UnsignedLong _mappingBound; + const void* _importerState; + Containers::Array _fields; + Containers::Array _data; +}; + +namespace Implementation { + /* Making this a struct because there can't be partial specializations for + a function (which we may need for pointers, matrix/vector subclasses + etc) */ + template struct SceneFieldTypeFor { + static_assert(sizeof(T) == 0, "unsupported field type"); + }; + #ifndef DOXYGEN_GENERATING_OUTPUT + #define _c(type_) template<> struct SceneFieldTypeFor { \ + constexpr static SceneFieldType type() { \ + return SceneFieldType::type_; \ + } \ + }; + /* Bool needs a special type */ + _c(Float) + _c(Half) + _c(Double) + _c(UnsignedByte) + _c(Byte) + _c(UnsignedShort) + _c(Short) + _c(UnsignedInt) + _c(Int) + _c(UnsignedLong) + _c(Long) + _c(Vector2) + _c(Vector2h) + _c(Vector2d) + _c(Vector2ub) + _c(Vector2b) + _c(Vector2us) + _c(Vector2s) + _c(Vector2ui) + _c(Vector2i) + _c(Vector3) + _c(Vector3h) + _c(Vector3d) + _c(Vector3ub) + _c(Vector3b) + _c(Vector3us) + _c(Vector3s) + _c(Vector3ui) + _c(Vector3i) + _c(Vector4) + _c(Vector4h) + _c(Vector4d) + _c(Vector4ub) + _c(Vector4b) + _c(Vector4us) + _c(Vector4s) + _c(Vector4ui) + _c(Vector4i) + _c(Matrix2x2) + _c(Matrix2x2h) + _c(Matrix2x2d) + _c(Matrix2x3) + _c(Matrix2x3h) + _c(Matrix2x3d) + _c(Matrix2x4) + _c(Matrix2x4h) + _c(Matrix2x4d) + _c(Matrix3x2) + _c(Matrix3x2h) + _c(Matrix3x2d) + _c(Matrix3x3) + _c(Matrix3x3h) + _c(Matrix3x3d) + _c(Matrix3x4) + _c(Matrix3x4h) + _c(Matrix3x4d) + _c(Matrix4x2) + _c(Matrix4x2h) + _c(Matrix4x2d) + _c(Matrix4x3) + _c(Matrix4x3h) + _c(Matrix4x3d) + _c(Matrix4x4) + _c(Matrix4x4h) + _c(Matrix4x4d) + _c(Range1D) + _c(Range1Dh) + _c(Range1Dd) + _c(Range1Di) + _c(Range2D) + _c(Range2Dh) + _c(Range2Dd) + _c(Range2Di) + _c(Range3D) + _c(Range3Dh) + _c(Range3Dd) + _c(Range3Di) + _c(Complex) + _c(Complexd) + _c(DualComplex) + _c(DualComplexd) + _c(Quaternion) + _c(Quaterniond) + _c(DualQuaternion) + _c(DualQuaterniond) + _c(Deg) + _c(Degh) + _c(Degd) + _c(Rad) + _c(Radh) + _c(Radd) + #undef _c + #endif + /** @todo this doesn't handle RectangleMatrix and Vector at the moment, do we need those? */ + template struct SceneFieldTypeFor>: SceneFieldTypeFor> {}; + template struct SceneFieldTypeFor>: SceneFieldTypeFor> {}; + template struct SceneFieldTypeFor>: SceneFieldTypeFor> {}; + template struct SceneFieldTypeFor>: SceneFieldTypeFor> {}; + template struct SceneFieldTypeFor { + constexpr static SceneFieldType type() { + return SceneFieldType::Pointer; + } + }; + template struct SceneFieldTypeFor { + constexpr static SceneFieldType type() { + return SceneFieldType::MutablePointer; + } + }; + + template constexpr SceneMappingType sceneMappingTypeFor() { + static_assert(sizeof(T) == 0, "unsupported mapping type"); + return {}; + } + #ifndef DOXYGEN_GENERATING_OUTPUT + #define _c(type) \ + template<> constexpr SceneMappingType sceneMappingTypeFor() { return SceneMappingType::type; } + _c(UnsignedByte) + _c(UnsignedShort) + _c(UnsignedInt) + _c(UnsignedLong) + #undef _c + #endif + + constexpr bool isSceneFieldTypeCompatibleWithField(SceneField name, SceneFieldType type) { + return + /* Named fields are restricted so we can decode them */ + (name == SceneField::Parent && + (type == SceneFieldType::Byte || + type == SceneFieldType::Short || + type == SceneFieldType::Int || + type == SceneFieldType::Long)) || + (name == SceneField::Transformation && + (type == SceneFieldType::Matrix3x3 || + type == SceneFieldType::Matrix3x3d || + type == SceneFieldType::Matrix4x4 || + type == SceneFieldType::Matrix4x4d || + type == SceneFieldType::Matrix3x2 || + type == SceneFieldType::Matrix3x2d || + type == SceneFieldType::Matrix4x3 || + type == SceneFieldType::Matrix4x3d || + type == SceneFieldType::DualComplex || + type == SceneFieldType::DualComplexd || + type == SceneFieldType::DualQuaternion || + type == SceneFieldType::DualQuaterniond)) || + ((name == SceneField::Translation || + name == SceneField::Scaling) && + (type == SceneFieldType::Vector2 || + type == SceneFieldType::Vector2d || + type == SceneFieldType::Vector3 || + type == SceneFieldType::Vector3d)) || + (name == SceneField::Rotation && + (type == SceneFieldType::Complex || + type == SceneFieldType::Complexd || + type == SceneFieldType::Quaternion || + type == SceneFieldType::Quaterniond)) || + ((name == SceneField::Mesh || + name == SceneField::Light || + name == SceneField::Camera || + name == SceneField::Skin) && + (type == SceneFieldType::UnsignedByte || + type == SceneFieldType::UnsignedShort || + type == SceneFieldType::UnsignedInt)) || + (name == SceneField::MeshMaterial && + (type == SceneFieldType::Byte || + type == SceneFieldType::Short || + type == SceneFieldType::Int)) || + (name == SceneField::ImporterState && + (type == SceneFieldType::Pointer || + type == SceneFieldType::MutablePointer)) || + /* Custom fields can be anything */ + isSceneFieldCustom(name); + } + + constexpr bool isSceneFieldArrayAllowed(SceneField name) { + return isSceneFieldCustom(name); + } +} + +constexpr SceneFieldData::SceneFieldData(const SceneField name, const SceneMappingType mappingType, const Containers::StridedArrayView1D& mappingData, const SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, const UnsignedShort fieldArraySize, const SceneFieldFlags flags) noexcept: + _size{(CORRADE_CONSTEXPR_ASSERT(mappingData.size() == fieldData.size(), + "Trade::SceneFieldData: expected mapping and field view to have the same size but got" << mappingData.size() << "and" << fieldData.size()), mappingData.size())}, + _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeCompatibleWithField(name, fieldType), + "Trade::SceneFieldData:" << fieldType << "is not a valid type for" << name), name)}, + _flags{(CORRADE_CONSTEXPR_ASSERT(!(flags & SceneFieldFlag::OffsetOnly), + "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::OffsetOnly for a view"), flags)}, + _mappingType{mappingType}, + _mappingStride{(CORRADE_CONSTEXPR_ASSERT(mappingData.stride() >= -32768 && mappingData.stride() <= 32767, + "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits, but got" << mappingData.stride()), Short(mappingData.stride()))}, + _mappingData{mappingData.data()}, + _fieldType{fieldType}, + _fieldStride{(CORRADE_CONSTEXPR_ASSERT(fieldData.stride() >= -32768 && fieldData.stride() <= 32767, + "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got" << fieldData.stride()), Short(fieldData.stride()))}, + _fieldArraySize{(CORRADE_CONSTEXPR_ASSERT(!fieldArraySize || Implementation::isSceneFieldArrayAllowed(name), + "Trade::SceneFieldData:" << name << "can't be an array field"), fieldArraySize)}, + _fieldData{fieldData.data()} {} + +template constexpr SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView1D& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{name, Implementation::sceneMappingTypeFor::type>(), mappingData, Implementation::SceneFieldTypeFor::type>::type(), fieldData, 0, flags} {} + +template constexpr SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView1D& mappingData, const Containers::StridedArrayView2D& fieldData, const SceneFieldFlags flags) noexcept: SceneFieldData{ + name, + Implementation::sceneMappingTypeFor::type>(), + mappingData, + Implementation::SceneFieldTypeFor::type>::type(), + Containers::StridedArrayView1D{{fieldData.data(), ~std::size_t{}}, fieldData.size()[0], fieldData.stride()[0]}, + /* Not using isContiguous<1>() as that's not constexpr */ + (CORRADE_CONSTEXPR_ASSERT(fieldData.stride()[1] == sizeof(U), "Trade::SceneFieldData: second field view dimension is not contiguous"), UnsignedShort(fieldData.size()[1])), + flags +} {} + +constexpr SceneFieldData::SceneFieldData(const SceneField name, const std::size_t size, const SceneMappingType mappingType, const std::size_t mappingOffset, const std::ptrdiff_t mappingStride, const SceneFieldType fieldType, const std::size_t fieldOffset, const std::ptrdiff_t fieldStride, const UnsignedShort fieldArraySize, const SceneFieldFlags flags) noexcept: + _size{size}, + _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeCompatibleWithField(name, fieldType), + "Trade::SceneFieldData:" << fieldType << "is not a valid type for" << name), name)}, + _flags{flags|SceneFieldFlag::OffsetOnly}, + _mappingType{mappingType}, + _mappingStride{(CORRADE_CONSTEXPR_ASSERT(mappingStride >= -32768 && mappingStride <= 32767, + "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits, but got" << mappingStride), Short(mappingStride))}, + _mappingData{mappingOffset}, + _fieldType{fieldType}, + _fieldStride{(CORRADE_CONSTEXPR_ASSERT(fieldStride >= -32768 && fieldStride <= 32767, + "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got" << fieldStride), Short(fieldStride))}, + _fieldArraySize{(CORRADE_CONSTEXPR_ASSERT(!fieldArraySize || Implementation::isSceneFieldArrayAllowed(name), + "Trade::SceneFieldData:" << name << "can't be an array field"), fieldArraySize)}, + _fieldData{fieldOffset} {} + +template Containers::StridedArrayView1D SceneData::mapping(const UnsignedInt fieldId) const { + Containers::StridedArrayView2D data = mapping(fieldId); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + CORRADE_ASSERT(Implementation::sceneMappingTypeFor() == _mappingType, + "Trade::SceneData::mapping(): mapping is" << _mappingType << "but requested" << Implementation::sceneMappingTypeFor(), {}); + return Containers::arrayCast<1, const T>(data); +} + +template Containers::StridedArrayView1D SceneData::mutableMapping(const UnsignedInt fieldId) { + Containers::StridedArrayView2D data = mutableMapping(fieldId); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + CORRADE_ASSERT(Implementation::sceneMappingTypeFor() == _mappingType, + "Trade::SceneData::mutableMapping(): mapping is" << _mappingType << "but requested" << Implementation::sceneMappingTypeFor(), {}); + return Containers::arrayCast<1, T>(data); +} + +template Containers::StridedArrayView1D SceneData::mapping(const SceneField fieldName) const { + Containers::StridedArrayView2D data = mapping(fieldName); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + CORRADE_ASSERT(Implementation::sceneMappingTypeFor() == _mappingType, + "Trade::SceneData::mapping(): mapping is" << _mappingType << "but requested" << Implementation::sceneMappingTypeFor(), {}); + return Containers::arrayCast<1, const T>(data); +} + +template Containers::StridedArrayView1D SceneData::mutableMapping(const SceneField fieldName) { + Containers::StridedArrayView2D data = mutableMapping(fieldName); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + CORRADE_ASSERT(Implementation::sceneMappingTypeFor() == _mappingType, + "Trade::SceneData::mutableMapping(): mapping is" << _mappingType << "but requested" << Implementation::sceneMappingTypeFor(), {}); + return Containers::arrayCast<1, T>(data); +} + +#ifndef CORRADE_NO_ASSERT +template bool SceneData::checkFieldTypeCompatibility(const SceneFieldData& field, const char* const prefix) const { + CORRADE_ASSERT(Implementation::SceneFieldTypeFor::type>::type() == field._fieldType, + prefix << field._name << "is" << field._fieldType << "but requested a type equivalent to" << Implementation::SceneFieldTypeFor::type>::type(), false); + if(field._fieldArraySize) CORRADE_ASSERT(std::is_array::value, + prefix << field._name << "is an array field, use T[] to access it", false); + else CORRADE_ASSERT(!std::is_array::value, + prefix << field._name << "is not an array field, can't use T[] to access it", false); + return true; +} +#endif + +template Containers::StridedArrayView1D SceneData::field(const UnsignedInt id) const { + Containers::StridedArrayView2D data = field(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[id], "Trade::SceneData::field():")) return {}; + #endif + return Containers::arrayCast<1, const T>(data); +} + +template Containers::StridedArrayView2D::type> SceneData::field(const UnsignedInt id) const { + Containers::StridedArrayView2D data = field(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[id], "Trade::SceneData::field():")) return {}; + #endif + return Containers::arrayCast<2, const typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D SceneData::mutableField(const UnsignedInt id) { + Containers::StridedArrayView2D data = mutableField(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[id], "Trade::SceneData::mutableField():")) return {}; + #endif + return Containers::arrayCast<1, T>(data); +} + +template Containers::StridedArrayView2D::type> SceneData::mutableField(const UnsignedInt id) { + Containers::StridedArrayView2D data = mutableField(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[id], "Trade::SceneData::mutableField():")) return {}; + #endif + return Containers::arrayCast<2, typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D SceneData::field(const SceneField name) const { + Containers::StridedArrayView2D data = field(name); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[findFieldIdInternal(name)], "Trade::SceneData::field():")) return {}; + #endif + return Containers::arrayCast<1, const T>(data); +} + +template Containers::StridedArrayView2D::type> SceneData::field(const SceneField name) const { + Containers::StridedArrayView2D data = field(name); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[findFieldIdInternal(name)], "Trade::SceneData::field():")) return {}; + #endif + return Containers::arrayCast<2, const typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D SceneData::mutableField(const SceneField name) { + Containers::StridedArrayView2D data = mutableField(name); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[findFieldIdInternal(name)], "Trade::SceneData::mutableField():")) return {}; + #endif + return Containers::arrayCast<1, T>(data); +} + +template Containers::StridedArrayView2D::type> SceneData::mutableField(const SceneField name) { + Containers::StridedArrayView2D data = mutableField(name); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[findFieldIdInternal(name)], "Trade::SceneData::mutableField():")) return {}; + #endif + return Containers::arrayCast<2, typename std::remove_extent::type>(data); +} + }} #endif diff --git a/src/Magnum/Trade/SkinData.cpp b/src/Magnum/Trade/SkinData.cpp index 775a197ac9..797cea200a 100644 --- a/src/Magnum/Trade/SkinData.cpp +++ b/src/Magnum/Trade/SkinData.cpp @@ -43,6 +43,8 @@ template SkinData::SkinData(DataFlags, const template SkinData::SkinData(SkinData&&) noexcept = default; +template SkinData::~SkinData() = default; + template SkinData& SkinData::operator=(SkinData&&) noexcept = default; template Containers::Array SkinData::releaseJointData() { diff --git a/src/Magnum/Trade/SkinData.h b/src/Magnum/Trade/SkinData.h index a2a8293442..c956191a6b 100644 --- a/src/Magnum/Trade/SkinData.h +++ b/src/Magnum/Trade/SkinData.h @@ -82,6 +82,8 @@ template class SkinData { /** @brief Move constructor */ SkinData(SkinData&& other) noexcept; + ~SkinData(); + /** @brief Copying is not allowed */ SkinData& operator=(const SkinData&) = delete; diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 671aec909d..ad0df45df7 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -34,6 +34,8 @@ #include "Magnum/PixelFormat.h" #include "Magnum/FileCallback.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/AnimationData.h" #include "Magnum/Trade/ArrayAllocator.h" @@ -41,18 +43,23 @@ #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" #include "Magnum/Trade/MeshData.h" -#include "Magnum/Trade/MeshObjectData2D.h" -#include "Magnum/Trade/MeshObjectData3D.h" #include "Magnum/Trade/PhongMaterialData.h" #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/SkinData.h" #include "Magnum/Trade/TextureData.h" #ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#include + #define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ +#define _MAGNUM_NO_DEPRECATED_OBJECTDATA /* So it doesn't yell here */ #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshObjectData2D.h" +#include "Magnum/Trade/MeshObjectData3D.h" #endif #include "configure.h" @@ -109,11 +116,35 @@ struct AbstractImporterTest: TestSuite::Tester { void defaultSceneOutOfRange(); void scene(); + void object(); + #ifdef MAGNUM_BUILD_DEPRECATED + void sceneDeprecatedFallback2D(); + void sceneDeprecatedFallback3D(); + void sceneDeprecatedFallbackParentless2D(); + void sceneDeprecatedFallbackParentless3D(); + void sceneDeprecatedFallbackTransformless2D(); + void sceneDeprecatedFallbackTransformless3D(); + void sceneDeprecatedFallbackMultiFunctionObjects2D(); + void sceneDeprecatedFallbackMultiFunctionObjects3D(); + void sceneDeprecatedFallbackObjectCountNoScenes(); + void sceneDeprecatedFallbackObjectCountAllSceneImportFailed(); + void sceneDeprecatedFallbackBoth2DAnd3DScene(); + #endif void sceneNameNotImplemented(); + void objectNameNotImplemented(); void sceneForNameOutOfRange(); + void objectForNameOutOfRange(); void sceneNameOutOfRange(); + void objectNameOutOfRange(); void sceneNotImplemented(); void sceneOutOfRange(); + void sceneNonOwningDeleters(); + void sceneCustomDataDeleter(); + void sceneCustomFieldDataDeleter(); + + void sceneFieldName(); + void sceneFieldNameNotImplemented(); + void sceneFieldNameNotCustom(); void animation(); void animationNameNotImplemented(); @@ -140,19 +171,35 @@ struct AbstractImporterTest: TestSuite::Tester { void cameraNotImplemented(); void cameraOutOfRange(); + #ifdef MAGNUM_BUILD_DEPRECATED void object2D(); - void object2DNameNotImplemented(); + void object2DCountNotImplemented(); + void object2DCountNoFile(); + void object2DForNameNotImplemented(); + void object2DForNameNoFile(); void object2DForNameOutOfRange(); + void object2DByNameNotFound(); + void object2DNameNotImplemented(); + void object2DNameNoFile(); void object2DNameOutOfRange(); void object2DNotImplemented(); + void object2DNoFile(); void object2DOutOfRange(); void object3D(); - void object3DNameNotImplemented(); + void object3DCountNotImplemented(); + void object3DCountNoFile(); + void object3DForNameNotImplemented(); + void object3DForNameNoFile(); void object3DForNameOutOfRange(); + void object3DByNameNotFound(); + void object3DNameNotImplemented(); + void object3DNameNoFile(); void object3DNameOutOfRange(); void object3DNotImplemented(); + void object3DNoFile(); void object3DOutOfRange(); + #endif void skin2D(); void skin2DNameNotImplemented(); @@ -303,6 +350,8 @@ constexpr struct { {"verify the message", true} }; +using namespace Math::Literals; + AbstractImporterTest::AbstractImporterTest() { addTests({&AbstractImporterTest::construct, &AbstractImporterTest::constructWithPluginManagerReference, @@ -354,11 +403,35 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::defaultSceneNotImplemented, &AbstractImporterTest::scene, + &AbstractImporterTest::object, + #ifdef MAGNUM_BUILD_DEPRECATED + &AbstractImporterTest::sceneDeprecatedFallback2D, + &AbstractImporterTest::sceneDeprecatedFallback3D, + &AbstractImporterTest::sceneDeprecatedFallbackParentless2D, + &AbstractImporterTest::sceneDeprecatedFallbackParentless3D, + &AbstractImporterTest::sceneDeprecatedFallbackTransformless2D, + &AbstractImporterTest::sceneDeprecatedFallbackTransformless3D, + &AbstractImporterTest::sceneDeprecatedFallbackMultiFunctionObjects2D, + &AbstractImporterTest::sceneDeprecatedFallbackMultiFunctionObjects3D, + &AbstractImporterTest::sceneDeprecatedFallbackObjectCountNoScenes, + &AbstractImporterTest::sceneDeprecatedFallbackObjectCountAllSceneImportFailed, + &AbstractImporterTest::sceneDeprecatedFallbackBoth2DAnd3DScene, + #endif &AbstractImporterTest::sceneForNameOutOfRange, + &AbstractImporterTest::objectForNameOutOfRange, &AbstractImporterTest::sceneNameNotImplemented, + &AbstractImporterTest::objectNameNotImplemented, &AbstractImporterTest::sceneNameOutOfRange, + &AbstractImporterTest::objectNameOutOfRange, &AbstractImporterTest::sceneNotImplemented, &AbstractImporterTest::sceneOutOfRange, + &AbstractImporterTest::sceneNonOwningDeleters, + &AbstractImporterTest::sceneCustomDataDeleter, + &AbstractImporterTest::sceneCustomFieldDataDeleter, + + &AbstractImporterTest::sceneFieldName, + &AbstractImporterTest::sceneFieldNameNotImplemented, + &AbstractImporterTest::sceneFieldNameNotCustom, &AbstractImporterTest::animation, &AbstractImporterTest::animationForNameOutOfRange, @@ -383,23 +456,45 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::cameraNameNotImplemented, &AbstractImporterTest::cameraNameOutOfRange, &AbstractImporterTest::cameraNotImplemented, - &AbstractImporterTest::cameraOutOfRange, + &AbstractImporterTest::cameraOutOfRange}); + + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AbstractImporterTest::object2D, + &AbstractImporterTest::object2DCountNotImplemented, + &AbstractImporterTest::object2DCountNoFile, + &AbstractImporterTest::object2DForNameNotImplemented, + &AbstractImporterTest::object2DForNameNoFile, + &AbstractImporterTest::object2DForNameOutOfRange}); + + addInstancedTests({&AbstractImporterTest::object2DByNameNotFound}, + Containers::arraySize(ThingByNameData)); - &AbstractImporterTest::object2D, - &AbstractImporterTest::object2DForNameOutOfRange, - &AbstractImporterTest::object2DNameNotImplemented, + addTests({&AbstractImporterTest::object2DNameNotImplemented, + &AbstractImporterTest::object2DNameNoFile, &AbstractImporterTest::object2DNameOutOfRange, &AbstractImporterTest::object2DNotImplemented, + &AbstractImporterTest::object2DNoFile, &AbstractImporterTest::object2DOutOfRange, &AbstractImporterTest::object3D, - &AbstractImporterTest::object3DForNameOutOfRange, - &AbstractImporterTest::object3DNameNotImplemented, + &AbstractImporterTest::object3DCountNotImplemented, + &AbstractImporterTest::object3DCountNoFile, + &AbstractImporterTest::object3DForNameNotImplemented, + &AbstractImporterTest::object3DForNameNoFile, + &AbstractImporterTest::object3DForNameOutOfRange}); + + addInstancedTests({&AbstractImporterTest::object3DByNameNotFound}, + Containers::arraySize(ThingByNameData)); + + addTests({&AbstractImporterTest::object3DNameNotImplemented, + &AbstractImporterTest::object3DNameNoFile, &AbstractImporterTest::object3DNameOutOfRange, &AbstractImporterTest::object3DNotImplemented, - &AbstractImporterTest::object3DOutOfRange, + &AbstractImporterTest::object3DNoFile, + &AbstractImporterTest::object3DOutOfRange}); + #endif - &AbstractImporterTest::skin2D, + addTests({&AbstractImporterTest::skin2D, &AbstractImporterTest::skin2DForNameOutOfRange, &AbstractImporterTest::skin2DNameNotImplemented, &AbstractImporterTest::skin2DNameOutOfRange, @@ -1202,13 +1297,11 @@ void AbstractImporterTest::thingCountNotImplemented() { } importer; CORRADE_COMPARE(importer.sceneCount(), 0); + CORRADE_COMPARE(importer.objectCount(), 0); CORRADE_COMPARE(importer.animationCount(), 0); CORRADE_COMPARE(importer.lightCount(), 0); CORRADE_COMPARE(importer.cameraCount(), 0); - CORRADE_COMPARE(importer.object2DCount(), 0); - CORRADE_COMPARE(importer.object3DCount(), 0); - CORRADE_COMPARE(importer.skin2DCount(), 0); CORRADE_COMPARE(importer.skin3DCount(), 0); @@ -1236,13 +1329,11 @@ void AbstractImporterTest::thingCountNoFile() { Error redirectError{&out}; importer.sceneCount(); + importer.objectCount(); importer.animationCount(); importer.lightCount(); importer.cameraCount(); - importer.object2DCount(); - importer.object3DCount(); - importer.skin2DCount(); importer.skin3DCount(); @@ -1260,13 +1351,11 @@ void AbstractImporterTest::thingCountNoFile() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::sceneCount(): no file opened\n" + "Trade::AbstractImporter::objectCount(): no file opened\n" "Trade::AbstractImporter::animationCount(): no file opened\n" "Trade::AbstractImporter::lightCount(): no file opened\n" "Trade::AbstractImporter::cameraCount(): no file opened\n" - "Trade::AbstractImporter::object2DCount(): no file opened\n" - "Trade::AbstractImporter::object3DCount(): no file opened\n" - "Trade::AbstractImporter::skin2DCount(): no file opened\n" "Trade::AbstractImporter::skin3DCount(): no file opened\n" @@ -1291,13 +1380,11 @@ void AbstractImporterTest::thingForNameNotImplemented() { } importer; CORRADE_COMPARE(importer.sceneForName(""), -1); + CORRADE_COMPARE(importer.objectForName(""), -1); CORRADE_COMPARE(importer.animationForName(""), -1); CORRADE_COMPARE(importer.lightForName(""), -1); CORRADE_COMPARE(importer.cameraForName(""), -1); - CORRADE_COMPARE(importer.object2DForName(""), -1); - CORRADE_COMPARE(importer.object3DForName(""), -1); - CORRADE_COMPARE(importer.skin2DForName(""), -1); CORRADE_COMPARE(importer.skin3DForName(""), -1); @@ -1325,13 +1412,11 @@ void AbstractImporterTest::thingForNameNoFile() { Error redirectError{&out}; importer.sceneForName(""); + importer.objectForName(""); importer.animationForName(""); importer.lightForName(""); importer.cameraForName(""); - importer.object2DForName(""); - importer.object3DForName(""); - importer.skin2DForName(""); importer.skin3DForName(""); @@ -1345,13 +1430,11 @@ void AbstractImporterTest::thingForNameNoFile() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::sceneForName(): no file opened\n" + "Trade::AbstractImporter::objectForName(): no file opened\n" "Trade::AbstractImporter::animationForName(): no file opened\n" "Trade::AbstractImporter::lightForName(): no file opened\n" "Trade::AbstractImporter::cameraForName(): no file opened\n" - "Trade::AbstractImporter::object2DForName(): no file opened\n" - "Trade::AbstractImporter::object3DForName(): no file opened\n" - "Trade::AbstractImporter::skin2DForName(): no file opened\n" "Trade::AbstractImporter::skin3DForName(): no file opened\n" @@ -1378,19 +1461,16 @@ void AbstractImporterTest::thingByNameNotFound() { UnsignedInt doLightCount() const override { return 3; } UnsignedInt doCameraCount() const override { return 4; } - UnsignedInt doObject2DCount() const override { return 5; } - UnsignedInt doObject3DCount() const override { return 6; } - - UnsignedInt doSkin2DCount() const override { return 7; } - UnsignedInt doSkin3DCount() const override { return 8; } + UnsignedInt doSkin2DCount() const override { return 5; } + UnsignedInt doSkin3DCount() const override { return 6; } - UnsignedInt doMeshCount() const override { return 9; } - UnsignedInt doMaterialCount() const override { return 10; } - UnsignedInt doTextureCount() const override { return 11; } + UnsignedInt doMeshCount() const override { return 7; } + UnsignedInt doMaterialCount() const override { return 8; } + UnsignedInt doTextureCount() const override { return 9; } - UnsignedInt doImage1DCount() const override { return 12; } - UnsignedInt doImage2DCount() const override { return 13; } - UnsignedInt doImage3DCount() const override { return 14; } + UnsignedInt doImage1DCount() const override { return 10; } + UnsignedInt doImage2DCount() const override { return 11; } + UnsignedInt doImage3DCount() const override { return 12; } } importer; std::ostringstream out; @@ -1403,9 +1483,6 @@ void AbstractImporterTest::thingByNameNotFound() { CORRADE_VERIFY(!importer.light("foobar")); CORRADE_VERIFY(!importer.camera("foobar")); - CORRADE_VERIFY(!importer.object2D("foobar")); - CORRADE_VERIFY(!importer.object3D("foobar")); - CORRADE_VERIFY(!importer.skin2D("foobar")); CORRADE_VERIFY(!importer.skin3D("foobar")); @@ -1425,19 +1502,16 @@ void AbstractImporterTest::thingByNameNotFound() { "Trade::AbstractImporter::light(): light foobar not found among 3 entries\n" "Trade::AbstractImporter::camera(): camera foobar not found among 4 entries\n" - "Trade::AbstractImporter::object2D(): object foobar not found among 5 entries\n" - "Trade::AbstractImporter::object3D(): object foobar not found among 6 entries\n" + "Trade::AbstractImporter::skin2D(): skin foobar not found among 5 entries\n" + "Trade::AbstractImporter::skin3D(): skin foobar not found among 6 entries\n" - "Trade::AbstractImporter::skin2D(): skin foobar not found among 7 entries\n" - "Trade::AbstractImporter::skin3D(): skin foobar not found among 8 entries\n" + "Trade::AbstractImporter::mesh(): mesh foobar not found among 7 entries\n" + "Trade::AbstractImporter::material(): material foobar not found among 8 entries\n" + "Trade::AbstractImporter::texture(): texture foobar not found among 9 entries\n" - "Trade::AbstractImporter::mesh(): mesh foobar not found among 9 entries\n" - "Trade::AbstractImporter::material(): material foobar not found among 10 entries\n" - "Trade::AbstractImporter::texture(): texture foobar not found among 11 entries\n" - - "Trade::AbstractImporter::image1D(): image foobar not found among 12 entries\n" - "Trade::AbstractImporter::image2D(): image foobar not found among 13 entries\n" - "Trade::AbstractImporter::image3D(): image foobar not found among 14 entries\n"); + "Trade::AbstractImporter::image1D(): image foobar not found among 10 entries\n" + "Trade::AbstractImporter::image2D(): image foobar not found among 11 entries\n" + "Trade::AbstractImporter::image3D(): image foobar not found among 12 entries\n"); } } @@ -1460,9 +1534,6 @@ void AbstractImporterTest::thingNameNoFile() { importer.lightName(42); importer.cameraName(42); - importer.object2DName(42); - importer.object3DName(42); - importer.skin2DName(42); importer.skin3DName(42); @@ -1480,9 +1551,6 @@ void AbstractImporterTest::thingNameNoFile() { "Trade::AbstractImporter::lightName(): no file opened\n" "Trade::AbstractImporter::cameraName(): no file opened\n" - "Trade::AbstractImporter::object2DName(): no file opened\n" - "Trade::AbstractImporter::object3DName(): no file opened\n" - "Trade::AbstractImporter::skin2DName(): no file opened\n" "Trade::AbstractImporter::skin3DName(): no file opened\n" @@ -1519,11 +1587,6 @@ void AbstractImporterTest::thingNoFile() { importer.camera(42); importer.camera("foo"); - importer.object2D(42); - importer.object2D("foo"); - importer.object3D(42); - importer.object3D("foo"); - importer.skin2D(42); importer.skin2D("foo"); importer.skin3D(42); @@ -1556,11 +1619,6 @@ void AbstractImporterTest::thingNoFile() { "Trade::AbstractImporter::camera(): no file opened\n" "Trade::AbstractImporter::camera(): no file opened\n" - "Trade::AbstractImporter::object2D(): no file opened\n" - "Trade::AbstractImporter::object2D(): no file opened\n" - "Trade::AbstractImporter::object3D(): no file opened\n" - "Trade::AbstractImporter::object3D(): no file opened\n" - "Trade::AbstractImporter::skin2D(): no file opened\n" "Trade::AbstractImporter::skin2D(): no file opened\n" "Trade::AbstractImporter::skin3D(): no file opened\n" @@ -1644,8 +1702,9 @@ void AbstractImporterTest::scene() { return {}; } Containers::Optional doScene(UnsignedInt id) override { - if(id == 7) return SceneData{{}, {}, &state}; - return SceneData{{}, {}}; + if(id == 7) + return SceneData{SceneMappingType::UnsignedByte, 0, nullptr, {}, &state}; + return SceneData{SceneMappingType::UnsignedByte, 0, nullptr, {}}; } } importer; @@ -1664,39 +1723,29 @@ void AbstractImporterTest::scene() { } } -void AbstractImporterTest::sceneForNameOutOfRange() { - #ifdef CORRADE_NO_ASSERT - CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); - #endif - - struct: AbstractImporter { - ImporterFeatures doFeatures() const override { return {}; } - bool doIsOpened() const override { return true; } - void doClose() override {} - - UnsignedInt doSceneCount() const override { return 8; } - Int doSceneForName(const std::string&) override { return 8; } - } importer; - - std::ostringstream out; - Error redirectError{&out}; - importer.sceneForName(""); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::sceneForName(): implementation-returned index 8 out of range for 8 entries\n"); -} - -void AbstractImporterTest::sceneNameNotImplemented() { +void AbstractImporterTest::object() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } bool doIsOpened() const override { return true; } void doClose() override {} - UnsignedInt doSceneCount() const override { return 8; } + UnsignedLong doObjectCount() const override { return 8; } + Long doObjectForName(const std::string& name) override { + if(name == "eighth") return 7; + return -1; + } + std::string doObjectName(UnsignedLong id) override { + if(id == 7) return "eighth"; + return {}; + } } importer; - CORRADE_COMPARE(importer.sceneName(7), ""); + CORRADE_COMPARE(importer.objectCount(), 8); + CORRADE_COMPARE(importer.objectForName("eighth"), 7); + CORRADE_COMPARE(importer.objectName(7), "eighth"); } -void AbstractImporterTest::sceneNameOutOfRange() { +void AbstractImporterTest::sceneForNameOutOfRange() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -1707,16 +1756,16 @@ void AbstractImporterTest::sceneNameOutOfRange() { void doClose() override {} UnsignedInt doSceneCount() const override { return 8; } + Int doSceneForName(const std::string&) override { return 8; } } importer; std::ostringstream out; Error redirectError{&out}; - - importer.sceneName(8); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::sceneName(): index 8 out of range for 8 entries\n"); + importer.sceneForName(""); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::sceneForName(): implementation-returned index 8 out of range for 8 entries\n"); } -void AbstractImporterTest::sceneNotImplemented() { +void AbstractImporterTest::objectForNameOutOfRange() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif @@ -1726,141 +1775,1932 @@ void AbstractImporterTest::sceneNotImplemented() { bool doIsOpened() const override { return true; } void doClose() override {} - UnsignedInt doSceneCount() const override { return 8; } + UnsignedLong doObjectCount() const override { return 8; } + Long doObjectForName(const std::string&) override { return 8; } } importer; std::ostringstream out; Error redirectError{&out}; - - importer.scene(7); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::scene(): not implemented\n"); + importer.objectForName(""); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::objectForName(): implementation-returned index 8 out of range for 8 entries\n"); } -void AbstractImporterTest::sceneOutOfRange() { - #ifdef CORRADE_NO_ASSERT - CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); - #endif +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractImporterTest::sceneDeprecatedFallback2D() { + /* Need to test the following combinations: + + - few objects in the root + - an object with one child, with more than one, with none + - an object with a mesh and a material + - an object with a mesh and without a material + - an object with a mesh and a skin + - an object with a skin but no mesh + - an object with a camera + - an object with TRS transformation + - an object with TRS transformation and a mesh + - an object with nothing except parent / transformation + */ + + struct Transform { + UnsignedInt object; + Int parent; + Matrix3 transformation; + }; + struct Trs { + UnsignedInt object; + Vector2 translation; + Complex rotation; + Vector2 scaling; + }; + struct Mesh { + UnsignedInt object; + UnsignedShort mesh; + Short meshMaterial; + }; + struct Index { + UnsignedInt object; + UnsignedInt id; + }; + struct ImporterState { + UnsignedInt object; + const void* importerState; + }; + Containers::StridedArrayView1D transformations; + Containers::StridedArrayView1D trs; + Containers::StridedArrayView1D meshes; + Containers::StridedArrayView1D cameras; + Containers::StridedArrayView1D skins; + Containers::StridedArrayView1D importerState; + Containers::Array data = Containers::ArrayTuple{ + {NoInit, 6, transformations}, + {NoInit, 2, trs}, + {NoInit, 2, meshes}, + {NoInit, 1, cameras}, + {NoInit, 2, skins}, + {NoInit, 3, importerState} + }; - struct: AbstractImporter { - ImporterFeatures doFeatures() const override { return {}; } - bool doIsOpened() const override { return true; } - void doClose() override {} + int a, b, c; - UnsignedInt doSceneCount() const override { return 8; } - } importer; + /* Object 3 is in the root, has a camera attached, TRS and children 5 + 4. + Because of the TRS, the actual transformation gets ignored. Has importer + state. */ + transformations[0] = {3, -1, Matrix3::rotation(75.0_degf)}; + trs[0] = {3, {0.0f, 3.0f}, Complex::rotation(15.0_degf), Vector2{1.0f}}; + cameras[0] = {3, 15}; + importerState[0] = {3, &a}; - std::ostringstream out; - Error redirectError{&out}; + /* Object 5 is a child of object 3, has a skin (which gets ignored by the + legacy interface) */ + transformations[1] = {5, 3, Matrix3::rotation(-15.0_degf)}; + skins[0] = {5, 226}; - importer.scene(8); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::scene(): index 8 out of range for 8 entries\n"); -} + /* Object 1 is a child of object 2 */ + transformations[2] = {1, 2, Matrix3::translation({1.0f, 0.5f})*Matrix3::rotation(15.0_degf)}; + + /* Object 2 is in the root, has object 1 as a child but nothing else */ + transformations[3] = {2, -1, {}}; + + /* Object 0 is in the root, has a mesh without a material and no children */ + transformations[4] = {0, -1, Matrix3::rotation(30.0_degf)}; + meshes[0] = {0, 33, -1}; + + /* Object 4 has TRS also, a mesh with a material and a skin and is a child + of object 3. The transformation gets ignored again. Has importer + state. */ + transformations[5] = {4, 3, Matrix3::translation(Vector2::xAxis(5.0f))}; + trs[1] = {4, {}, {}, {1.5f, -0.5f}}; + meshes[1] = {4, 27, 46}; + skins[1] = {4, 72}; + importerState[1] = {4, &b}; + + /* Object 6 has neither a transformation nor a parent, only an importer + state. It should get ignored. */ + importerState[2] = {6, &c}; + + struct Importer: AbstractImporter { + explicit Importer(SceneData&& data): _data{std::move(data)} {} -void AbstractImporterTest::animation() { - struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } bool doIsOpened() const override { return true; } void doClose() override {} - UnsignedInt doAnimationCount() const override { return 8; } - Int doAnimationForName(const std::string& name) override { - if(name == "eighth") return 7; + UnsignedInt doSceneCount() const override { return 3; } + UnsignedLong doObjectCount() const override { return 7; } + Long doObjectForName(const std::string& name) override { + if(name == "sixth") return 5; return -1; } - std::string doAnimationName(UnsignedInt id) override { - if(id == 7) return "eighth"; + std::string doObjectName(UnsignedLong id) override { + if(id == 5) return "sixth"; return {}; } - Containers::Optional doAnimation(UnsignedInt id) override { - /* Verify that initializer list is converted to an array with - the default deleter and not something disallowed */ - if(id == 7) return AnimationData{nullptr, { - AnimationTrackData{AnimationTrackType::Vector3, - AnimationTrackTargetType::Scaling3D, 0, {}} - }, &state}; - return AnimationData{{}, {}}; + Containers::Optional doScene(UnsignedInt id) override { + /* This one has seven objects, but no fields for them so it should + get skipped */ + if(id == 0) + return SceneData{SceneMappingType::UnsignedByte, 7, nullptr, {}}; + /* This one has no objects, so it should get skipped as well + without even querying any fieldFor() API (as those would + assert) */ + if(id == 1) + return SceneData{SceneMappingType::UnsignedShort, 0, nullptr, {}}; + /* This one is the one */ + if(id == 2) + return SceneData{SceneMappingType::UnsignedInt, 7, {}, _data.data(), sceneFieldDataNonOwningArray(_data.fieldData())}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); } - } importer; - CORRADE_COMPARE(importer.animationCount(), 8); - CORRADE_COMPARE(importer.animationForName("eighth"), 7); - CORRADE_COMPARE(importer.animationName(7), "eighth"); + private: + SceneData _data; + } importer{SceneData{SceneMappingType::UnsignedInt, 7, std::move(data), { + SceneFieldData{SceneField::Parent, + transformations.slice(&Transform::object), + transformations.slice(&Transform::parent)}, + SceneFieldData{SceneField::Transformation, + transformations.slice(&Transform::object), + transformations.slice(&Transform::transformation)}, + SceneFieldData{SceneField::Translation, + trs.slice(&Trs::object), + trs.slice(&Trs::translation)}, + SceneFieldData{SceneField::Rotation, + trs.slice(&Trs::object), + trs.slice(&Trs::rotation)}, + SceneFieldData{SceneField::Scaling, + trs.slice(&Trs::object), + trs.slice(&Trs::scaling)}, + SceneFieldData{SceneField::Mesh, + meshes.slice(&Mesh::object), + meshes.slice(&Mesh::mesh)}, + SceneFieldData{SceneField::MeshMaterial, + meshes.slice(&Mesh::object), + meshes.slice(&Mesh::meshMaterial)}, + SceneFieldData{SceneField::Camera, + cameras.slice(&Index::object), + cameras.slice(&Index::id)}, + SceneFieldData{SceneField::Skin, + skins.slice(&Index::object), + skins.slice(&Index::id)}, + SceneFieldData{SceneField::ImporterState, + importerState.slice(&ImporterState::object), importerState.slice(&ImporterState::importerState)} + }}}; + + CORRADE_COMPARE(importer.sceneCount(), 3); + + Containers::Optional scene = importer.scene(2); + CORRADE_VERIFY(scene); + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE_AS(scene->children2D(), + (std::vector{3, 2, 0}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene->children3D(), + (std::vector{}), + TestSuite::Compare::Container); + + CORRADE_COMPARE(importer.object2DCount(), 7); + CORRADE_COMPARE(importer.object2DForName("sixth"), 5); + CORRADE_COMPARE(importer.object2DName(5), "sixth"); + + CORRADE_COMPARE(importer.object3DCount(), 0); + CORRADE_COMPARE(importer.object3DForName("sixth"), -1); { - auto data = importer.animation(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + Containers::Pointer o = importer.object2D(0); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Mesh); + CORRADE_COMPARE(o->instance(), 33); + CORRADE_COMPARE(o->flags(), ObjectFlags2D{}); + CORRADE_COMPARE(o->transformation(), Matrix3::rotation(30.0_degf)); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + MeshObjectData2D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), -1); + CORRADE_COMPARE(mo.skin(), -1); } { - auto data = importer.animation("eighth"); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + Containers::Pointer o = importer.object2D(1); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlags2D{}); + CORRADE_COMPARE(o->transformation(), Matrix3::translation({1.0f, 0.5f})*Matrix3::rotation(15.0_degf)); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object2D(2); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlags2D{}); + CORRADE_COMPARE(o->transformation(), Matrix3{}); + CORRADE_COMPARE_AS(o->children(), + std::vector{1}, + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object2D(3); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), &a); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Camera); + CORRADE_COMPARE(o->instance(), 15); + CORRADE_COMPARE(o->flags(), ObjectFlag2D::HasTranslationRotationScaling); + CORRADE_COMPARE(o->transformation(), Matrix3::translation({0.0f, 3.0f})*Matrix3::rotation(15.0_degf)); + CORRADE_COMPARE(o->translation(), (Vector2{0.0f, 3.0f})); + CORRADE_COMPARE(o->rotation(), Complex::rotation(15.0_degf)); + CORRADE_COMPARE(o->scaling(), (Vector2{1.0f})); + CORRADE_COMPARE_AS(o->children(), + (std::vector{5, 4}), + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object2D(4); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), &b); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Mesh); + CORRADE_COMPARE(o->instance(), 27); + CORRADE_COMPARE(o->flags(), ObjectFlag2D::HasTranslationRotationScaling); + CORRADE_COMPARE(o->transformation(), Matrix3::scaling({1.5f, -0.5f})); + CORRADE_COMPARE(o->translation(), Vector2{}); + CORRADE_COMPARE(o->rotation(), Complex{}); + CORRADE_COMPARE(o->scaling(), (Vector2{1.5, -0.5f})); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + MeshObjectData2D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 46); + CORRADE_COMPARE(mo.skin(), 72); + } { + Containers::Pointer o = importer.object2D("sixth"); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlags2D{}); + CORRADE_COMPARE(o->transformation(), Matrix3::rotation(-15.0_degf)); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + } { + /* This one is not contained in any parent hierarchy, so it fails to + import */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer.object2D(6)); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object2D(): object 6 not found in any 2D scene hierarchy\n"); } + CORRADE_IGNORE_DEPRECATED_POP } -void AbstractImporterTest::animationForNameOutOfRange() { - #ifdef CORRADE_NO_ASSERT - CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); - #endif +void AbstractImporterTest::sceneDeprecatedFallback3D() { + /* Need to test the following combinations: + + - few objects in the root + - an object with one child, with more than one, with none + - an object with a mesh and a material + - an object with a mesh and without a material + - an object with a mesh and a skin + - an object with a skin but no mesh + - an object with a camera + - an object with a light + - an object with TRS transformation + - an object with TRS transformation and a mesh + - an object with nothing except parent / transformation + */ + + struct Transform { + UnsignedInt object; + Int parent; + Matrix4 transformation; + }; + struct Trs { + UnsignedInt object; + Vector3 translation; + Quaternion rotation; + Vector3 scaling; + }; + struct Mesh { + UnsignedInt object; + UnsignedShort mesh; + Short meshMaterial; + }; + struct Index { + UnsignedInt object; + UnsignedInt id; + }; + struct ImporterState { + UnsignedInt object; + const void* importerState; + }; + Containers::StridedArrayView1D transformations; + Containers::StridedArrayView1D trs; + Containers::StridedArrayView1D meshes; + Containers::StridedArrayView1D cameras; + Containers::StridedArrayView1D lights; + Containers::StridedArrayView1D skins; + Containers::StridedArrayView1D importerState; + Containers::Array data = Containers::ArrayTuple{ + {NoInit, 6, transformations}, + {NoInit, 2, trs}, + {NoInit, 2, meshes}, + {NoInit, 1, cameras}, + {NoInit, 1, lights}, + {NoInit, 2, skins}, + {NoInit, 3, importerState} + }; + + int a, b, c; + + /* Object 3 is in the root, has a camera attached, TRS and children 5 + 4. + Because of the TRS, the actual transformation gets ignored. Has importer + state. */ + transformations[0] = {3, -1, Matrix4::rotationX(75.0_degf)}; + trs[0] = {3, {0.0f, 0.0f, 3.0f}, Quaternion::rotation(15.0_degf, Vector3::xAxis()), Vector3{1.0f}}; + cameras[0] = {3, 15}; + importerState[0] = {3, &a}; + + /* Object 5 is a child of object 3, has a skin (which gets ignored by the + legacy interface) */ + transformations[1] = {5, 3, Matrix4::rotationY(-15.0_degf)}; + skins[0] = {5, 226}; + + /* Object 1 is a child of object 2, has a light. */ + transformations[2] = {1, 2, Matrix4::translation({1.0f, 0.0f, 1.0f})*Matrix4::rotationZ(15.0_degf)}; + lights[0] = {1, 113}; + + /* Object 2 is in the root, has object 1 as a child but nothing else */ + transformations[3] = {2, -1, {}}; + + /* Object 0 is in the root, has a mesh without a material and no children */ + transformations[4] = {0, -1, Matrix4::rotationX(30.0_degf)}; + meshes[0] = {0, 33, -1}; + + /* Object 4 has TRS also, a mesh with a material and a skin and is a child + of object 3. The transformation gets ignored again. Has importer + state. */ + transformations[5] = {4, 3, Matrix4::translation(Vector3::xAxis(5.0f))}; + trs[1] = {4, {}, {}, {1.5f, 3.0f, -0.5f}}; + meshes[1] = {4, 27, 46}; + skins[1] = {4, 72}; + importerState[1] = {4, &b}; + + /* Object 6 has neither a transformation nor a parent, only an importer + state. It should get ignored. */ + importerState[2] = {6, &c}; + + struct Importer: AbstractImporter { + explicit Importer(SceneData&& data): _data{std::move(data)} {} - struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } bool doIsOpened() const override { return true; } void doClose() override {} - UnsignedInt doAnimationCount() const override { return 8; } - Int doAnimationForName(const std::string&) override { return 8; } - } importer; + UnsignedInt doSceneCount() const override { return 3; } + UnsignedLong doObjectCount() const override { return 7; } + Long doObjectForName(const std::string& name) override { + if(name == "sixth") return 5; + return -1; + } + std::string doObjectName(UnsignedLong id) override { + if(id == 5) return "sixth"; + return {}; + } + Containers::Optional doScene(UnsignedInt id) override { + /* This one has seven objects, but no fields for them so it should + get skipped */ + if(id == 0) + return SceneData{SceneMappingType::UnsignedByte, 7, nullptr, {}}; + /* This one has no objects, so it should get skipped as well + without even querying any fieldFor() API (as those would + assert) */ + if(id == 1) + return SceneData{SceneMappingType::UnsignedShort, 0, nullptr, {}}; + /* This one is the one */ + if(id == 2) + return SceneData{SceneMappingType::UnsignedInt, 7, {}, _data.data(), sceneFieldDataNonOwningArray(_data.fieldData())}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } - std::ostringstream out; - Error redirectError{&out}; - importer.animationForName(""); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animationForName(): implementation-returned index 8 out of range for 8 entries\n"); -} + private: + SceneData _data; + } importer{SceneData{SceneMappingType::UnsignedInt, 7, std::move(data), { + SceneFieldData{SceneField::Parent, + transformations.slice(&Transform::object), + transformations.slice(&Transform::parent)}, + SceneFieldData{SceneField::Transformation, + transformations.slice(&Transform::object), + transformations.slice(&Transform::transformation)}, + SceneFieldData{SceneField::Translation, + trs.slice(&Trs::object), + trs.slice(&Trs::translation)}, + SceneFieldData{SceneField::Rotation, + trs.slice(&Trs::object), + trs.slice(&Trs::rotation)}, + SceneFieldData{SceneField::Scaling, + trs.slice(&Trs::object), + trs.slice(&Trs::scaling)}, + SceneFieldData{SceneField::Mesh, + meshes.slice(&Mesh::object), + meshes.slice(&Mesh::mesh)}, + SceneFieldData{SceneField::MeshMaterial, + meshes.slice(&Mesh::object), + meshes.slice(&Mesh::meshMaterial)}, + SceneFieldData{SceneField::Camera, + cameras.slice(&Index::object), + cameras.slice(&Index::id)}, + SceneFieldData{SceneField::Light, + lights.slice(&Index::object), + lights.slice(&Index::id)}, + SceneFieldData{SceneField::Skin, + skins.slice(&Index::object), + skins.slice(&Index::id)}, + SceneFieldData{SceneField::ImporterState, + importerState.slice(&ImporterState::object), importerState.slice(&ImporterState::importerState)} + }}}; + + CORRADE_COMPARE(importer.sceneCount(), 3); + + Containers::Optional scene = importer.scene(2); + CORRADE_VERIFY(scene); -void AbstractImporterTest::animationNameNotImplemented() { - struct: AbstractImporter { - ImporterFeatures doFeatures() const override { return {}; } - bool doIsOpened() const override { return true; } - void doClose() override {} + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE_AS(scene->children2D(), + std::vector{}, + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene->children3D(), + (std::vector{3, 2, 0}), + TestSuite::Compare::Container); - UnsignedInt doAnimationCount() const override { return 8; } - } importer; + CORRADE_COMPARE(importer.object2DCount(), 0); + CORRADE_COMPARE(importer.object2DForName("sixth"), -1); - CORRADE_COMPARE(importer.animationName(7), ""); + CORRADE_COMPARE(importer.object3DCount(), 7); + CORRADE_COMPARE(importer.object3DForName("sixth"), 5); + CORRADE_COMPARE(importer.object3DName(5), "sixth"); + + { + Containers::Pointer o = importer.object3D(0); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(o->instance(), 33); + CORRADE_COMPARE(o->flags(), ObjectFlags3D{}); + CORRADE_COMPARE(o->transformation(), Matrix4::rotationX(30.0_degf)); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + MeshObjectData3D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), -1); + CORRADE_COMPARE(mo.skin(), -1); + } { + Containers::Pointer o = importer.object3D(1); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Light); + CORRADE_COMPARE(o->instance(), 113); + CORRADE_COMPARE(o->flags(), ObjectFlags3D{}); + CORRADE_COMPARE(o->transformation(), Matrix4::translation({1.0f, 0.0f, 1.0f})*Matrix4::rotationZ(15.0_degf)); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object3D(2); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlags3D{}); + CORRADE_COMPARE(o->transformation(), Matrix4{}); + CORRADE_COMPARE_AS(o->children(), + std::vector{1}, + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object3D(3); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), &a); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Camera); + CORRADE_COMPARE(o->instance(), 15); + CORRADE_COMPARE(o->flags(), ObjectFlag3D::HasTranslationRotationScaling); + CORRADE_COMPARE(o->transformation(), Matrix4::translation({0.0f, 0.0f, 3.0f})*Matrix4::rotationX(15.0_degf)); + CORRADE_COMPARE(o->translation(), (Vector3{0.0f, 0.0f, 3.0f})); + CORRADE_COMPARE(o->rotation(), Quaternion::rotation(15.0_degf, Vector3::xAxis())); + CORRADE_COMPARE(o->scaling(), (Vector3{1.0f})); + CORRADE_COMPARE_AS(o->children(), + (std::vector{5, 4}), + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object3D(4); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), &b); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(o->instance(), 27); + CORRADE_COMPARE(o->flags(), ObjectFlag3D::HasTranslationRotationScaling); + CORRADE_COMPARE(o->transformation(), Matrix4::scaling({1.5f, 3.0f, -0.5f})); + CORRADE_COMPARE(o->translation(), Vector3{}); + CORRADE_COMPARE(o->rotation(), Quaternion{}); + CORRADE_COMPARE(o->scaling(), (Vector3{1.5, 3.0f, -0.5f})); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + MeshObjectData3D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 46); + CORRADE_COMPARE(mo.skin(), 72); + } { + Containers::Pointer o = importer.object3D("sixth"); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlags3D{}); + CORRADE_COMPARE(o->transformation(), Matrix4::rotationY(-15.0_degf)); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + } { + /* This one is not contained in any parent hierarchy, so it fails to + import */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer.object3D(6)); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object3D(): object 6 not found in any 3D scene hierarchy\n"); + } + CORRADE_IGNORE_DEPRECATED_POP } -void AbstractImporterTest::animationNameOutOfRange() { - #ifdef CORRADE_NO_ASSERT - CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); - #endif +void AbstractImporterTest::sceneDeprecatedFallbackParentless2D() { + /* As the Parent field is currently used to distinguish which objects + belong to which scene, its absence means the objects are advertised, but + aren't listed as children of any scene, and retrieving them will fail */ + /** @todo adapt when there's a dedicated way to distinguish which objects + belong to which scene */ + + struct Field { + UnsignedInt object; + Matrix3 transformation; + } fields[]{ + {5, Matrix3{}}, + {2, Matrix3{}}, + }; + + Containers::StridedArrayView1D view = fields; + + struct Importer: AbstractImporter { + explicit Importer(SceneData&& data): _data{std::move(data)} {} - struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } bool doIsOpened() const override { return true; } void doClose() override {} - UnsignedInt doAnimationCount() const override { return 8; } - } importer; + UnsignedInt doSceneCount() const override { return 1; } + UnsignedLong doObjectCount() const override { return 6; } + Containers::Optional doScene(UnsignedInt) override { + return SceneData{SceneMappingType::UnsignedInt, 6, {}, _data.data(), sceneFieldDataNonOwningArray(_data.fieldData())}; + } + + private: + SceneData _data; + } importer{SceneData{SceneMappingType::UnsignedInt, 6, {}, fields, { + SceneFieldData{SceneField::Transformation, + view.slice(&Field::object), + view.slice(&Field::transformation)} + }}}; + + CORRADE_COMPARE(importer.sceneCount(), 1); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object2DCount(), 6); + CORRADE_COMPARE(importer.object3DCount(), 0); + CORRADE_IGNORE_DEPRECATED_POP + + Containers::Optional scene = importer.scene(0); + CORRADE_VERIFY(scene); + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE_AS(scene->children2D(), + std::vector{}, + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene->children3D(), + (std::vector{}), + TestSuite::Compare::Container); + CORRADE_IGNORE_DEPRECATED_POP std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_VERIFY(!importer.object2D(0)); + CORRADE_VERIFY(!importer.object2D(1)); + CORRADE_VERIFY(!importer.object2D(2)); + CORRADE_VERIFY(!importer.object2D(3)); + CORRADE_VERIFY(!importer.object2D(4)); + CORRADE_VERIFY(!importer.object2D(5)); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::object2D(): object 0 not found in any 2D scene hierarchy\n" + "Trade::AbstractImporter::object2D(): object 1 not found in any 2D scene hierarchy\n" + "Trade::AbstractImporter::object2D(): object 2 not found in any 2D scene hierarchy\n" + "Trade::AbstractImporter::object2D(): object 3 not found in any 2D scene hierarchy\n" + "Trade::AbstractImporter::object2D(): object 4 not found in any 2D scene hierarchy\n" + "Trade::AbstractImporter::object2D(): object 5 not found in any 2D scene hierarchy\n"); +} + +void AbstractImporterTest::sceneDeprecatedFallbackParentless3D() { + /* As the Parent field is currently used to distinguish which objects + belong to which scene, its absence means the objects are advertised, but + aren't listed as children of any scene, and retrieving them will fail */ + /** @todo adapt when there's a dedicated way to distinguish which objects + belong to which scene */ + + struct Field { + UnsignedInt object; + Matrix4 transformation; + } fields[]{ + {5, Matrix4{}}, + {2, Matrix4{}}, + }; - importer.animationName(8); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animationName(): index 8 out of range for 8 entries\n"); -} + Containers::StridedArrayView1D view = fields; -void AbstractImporterTest::animationNotImplemented() { - #ifdef CORRADE_NO_ASSERT - CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); - #endif + struct Importer: AbstractImporter { + explicit Importer(SceneData&& data): _data{std::move(data)} {} - struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } bool doIsOpened() const override { return true; } void doClose() override {} - UnsignedInt doAnimationCount() const override { return 8; } - } importer; + UnsignedInt doSceneCount() const override { return 1; } + UnsignedLong doObjectCount() const override { return 6; } + Containers::Optional doScene(UnsignedInt) override { + return SceneData{SceneMappingType::UnsignedInt, 6, {}, _data.data(), sceneFieldDataNonOwningArray(_data.fieldData())}; + } + + private: + SceneData _data; + } importer{SceneData{SceneMappingType::UnsignedInt, 6, {}, fields, { + SceneFieldData{SceneField::Transformation, + view.slice(&Field::object), + view.slice(&Field::transformation)} + }}}; + + CORRADE_COMPARE(importer.sceneCount(), 1); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object2DCount(), 0); + CORRADE_COMPARE(importer.object3DCount(), 6); + CORRADE_IGNORE_DEPRECATED_POP + + Containers::Optional scene = importer.scene(0); + CORRADE_VERIFY(scene); + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE_AS(scene->children2D(), + std::vector{}, + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene->children3D(), + (std::vector{}), + TestSuite::Compare::Container); + CORRADE_IGNORE_DEPRECATED_POP + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_VERIFY(!importer.object3D(0)); + CORRADE_VERIFY(!importer.object3D(1)); + CORRADE_VERIFY(!importer.object3D(2)); + CORRADE_VERIFY(!importer.object3D(3)); + CORRADE_VERIFY(!importer.object3D(4)); + CORRADE_VERIFY(!importer.object3D(5)); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::object3D(): object 0 not found in any 3D scene hierarchy\n" + "Trade::AbstractImporter::object3D(): object 1 not found in any 3D scene hierarchy\n" + "Trade::AbstractImporter::object3D(): object 2 not found in any 3D scene hierarchy\n" + "Trade::AbstractImporter::object3D(): object 3 not found in any 3D scene hierarchy\n" + "Trade::AbstractImporter::object3D(): object 4 not found in any 3D scene hierarchy\n" + "Trade::AbstractImporter::object3D(): object 5 not found in any 3D scene hierarchy\n"); +} + +void AbstractImporterTest::sceneDeprecatedFallbackTransformless2D() { + /* If no transformation field is present, for backwards compatibility we + assume the objects are 3D -- the only plugin that has a 2D scene is + PrimitiveImporter and it has the transformation field. */ + + struct Field { + UnsignedInt object; + Int parent; + } fields[]{ + {5, -1}, + {2, 5}, + {3, 5}, + {1, -1} + }; + + Containers::StridedArrayView1D view = fields; + + struct Importer: AbstractImporter { + explicit Importer(SceneData&& data): _data{std::move(data)} {} + + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 1; } + UnsignedLong doObjectCount() const override { return 6; } + Containers::Optional doScene(UnsignedInt) override { + return SceneData{SceneMappingType::UnsignedInt, 6, {}, _data.data(), sceneFieldDataNonOwningArray(_data.fieldData())}; + } + + private: + SceneData _data; + } importer{SceneData{SceneMappingType::UnsignedInt, 6, {}, fields, { + SceneFieldData{SceneField::Parent, + view.slice(&Field::object), + view.slice(&Field::parent)}, + /* Required in order to have the scene recognized as 2D */ + SceneFieldData{SceneField::Transformation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Matrix3x3, nullptr} + }}}; + + CORRADE_COMPARE(importer.sceneCount(), 1); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object2DCount(), 6); + CORRADE_COMPARE(importer.object3DCount(), 0); + CORRADE_IGNORE_DEPRECATED_POP + + Containers::Optional scene = importer.scene(0); + CORRADE_VERIFY(scene); + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE_AS(scene->children2D(), + (std::vector{5, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene->children3D(), + std::vector{}, + TestSuite::Compare::Container); + + /* If we have neither a matrix nor a TRS, having an identity TRS is better + as it's more flexible compared to a matrix */ + { + Containers::Pointer o = importer.object2D(5); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlag2D::HasTranslationRotationScaling); + CORRADE_COMPARE(o->transformation(), Matrix3{}); + CORRADE_COMPARE_AS(o->children(), + (std::vector{2, 3}), + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object2D(2); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlag2D::HasTranslationRotationScaling); + CORRADE_COMPARE(o->transformation(), Matrix3{}); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object2D(3); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlag2D::HasTranslationRotationScaling); + CORRADE_COMPARE(o->transformation(), Matrix3{}); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object2D(1); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlag2D::HasTranslationRotationScaling); + CORRADE_COMPARE(o->transformation(), Matrix3{}); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + } + CORRADE_IGNORE_DEPRECATED_POP +} +void AbstractImporterTest::sceneDeprecatedFallbackTransformless3D() { + /* If no transformation field is present, for backwards compatibility we + assume the objects are 3D -- the only plugin that has a 2D scene is + PrimitiveImporter and it has the transformation field. */ + + struct Field { + UnsignedInt object; + Int parent; + } fields[]{ + {5, -1}, + {2, 5}, + {3, 5}, + {1, -1} + }; + + Containers::StridedArrayView1D view = fields; + + struct Importer: AbstractImporter { + explicit Importer(SceneData&& data): _data{std::move(data)} {} + + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 1; } + UnsignedLong doObjectCount() const override { return 6; } + Containers::Optional doScene(UnsignedInt) override { + return SceneData{SceneMappingType::UnsignedInt, 6, {}, _data.data(), sceneFieldDataNonOwningArray(_data.fieldData())}; + } + + private: + SceneData _data; + } importer{SceneData{SceneMappingType::UnsignedInt, 6, {}, fields, { + SceneFieldData{SceneField::Parent, + view.slice(&Field::object), + view.slice(&Field::parent)}, + /* Required in order to have the scene recognized as 3D */ + SceneFieldData{SceneField::Transformation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Matrix4x4, nullptr} + }}}; + + CORRADE_COMPARE(importer.sceneCount(), 1); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object2DCount(), 0); + CORRADE_COMPARE(importer.object3DCount(), 6); + CORRADE_IGNORE_DEPRECATED_POP + + Containers::Optional scene = importer.scene(0); + CORRADE_VERIFY(scene); + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE_AS(scene->children2D(), + std::vector{}, + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene->children3D(), + (std::vector{5, 1}), + TestSuite::Compare::Container); + + /* If we have neither a matrix nor a TRS, having an identity TRS is better + as it's more flexible compared to a matrix */ + { + Containers::Pointer o = importer.object3D(5); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlag3D::HasTranslationRotationScaling); + CORRADE_COMPARE(o->transformation(), Matrix4{}); + CORRADE_COMPARE_AS(o->children(), + (std::vector{2, 3}), + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object3D(2); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlag3D::HasTranslationRotationScaling); + CORRADE_COMPARE(o->transformation(), Matrix4{}); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object3D(3); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlag3D::HasTranslationRotationScaling); + CORRADE_COMPARE(o->transformation(), Matrix4{}); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object3D(1); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->importerState(), nullptr); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Empty); + CORRADE_COMPARE(o->instance(), -1); + CORRADE_COMPARE(o->flags(), ObjectFlag3D::HasTranslationRotationScaling); + CORRADE_COMPARE(o->transformation(), Matrix4{}); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + } + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::sceneDeprecatedFallbackMultiFunctionObjects2D() { + /* Mostly just a copy of SceneToolsTest::convertToSingleFunctionObjects() + except that here we can't use the convenience combining tool so it's + done by hand */ + + struct Parent { + UnsignedInt object; + Int parent; + }; + struct Mesh { + UnsignedInt object; + UnsignedInt mesh; + Int meshMaterial; + }; + struct Camera { + UnsignedInt object; + UnsignedInt camera; + }; + struct Skin { + UnsignedInt object; + UnsignedInt skin; + }; + Containers::StridedArrayView1D parents; + Containers::StridedArrayView1D meshes; + Containers::StridedArrayView1D cameras; + Containers::StridedArrayView1D skins; + Containers::Array dataData = Containers::ArrayTuple{ + {NoInit, 5, parents}, + {NoInit, 7, meshes}, + {NoInit, 2, cameras}, + {NoInit, 2, skins}, + }; + Utility::copy({{15, -1}, {21, -1}, {22, 21}, {23, 22}, {1, -1}}, parents); + Utility::copy({ + {15, 6, 4}, + {23, 1, 0}, + {23, 2, 3}, + {23, 4, 2}, + {1, 7, 2}, + {15, 3, 1}, + {21, 5, -1} + }, meshes); + Utility::copy({{22, 1}, {1, 5}}, cameras); + Utility::copy({{15, 9}, {21, 10}}, skins); + + /* Second scene that also has a duplicate, to verify the newly added object + IDs don't conflict with each other. A potential downside is that + multi-primitive nodes shared by multiple scenes get duplicated, but + that's a smaller problem than two unrelated nodes sharing the same ID + (and thus having a wrong name, etc). */ + Containers::StridedArrayView1D parentsSecondary; + Containers::StridedArrayView1D meshesSecondary; + Containers::Array dataDataSecondary = Containers::ArrayTuple{ + {NoInit, 1, parentsSecondary}, + {NoInit, 2, meshesSecondary} + }; + Utility::copy({{30, -1}}, parentsSecondary); + Utility::copy({ + {30, 6, 2}, + {30, 1, -1} + }, meshesSecondary); + + SceneData data{SceneMappingType::UnsignedInt, 32, std::move(dataData), { + SceneFieldData{SceneField::Parent, parents.slice(&Parent::object), parents.slice(&Parent::parent)}, + SceneFieldData{SceneField::Mesh, meshes.slice(&Mesh::object), meshes.slice(&Mesh::mesh)}, + SceneFieldData{SceneField::MeshMaterial, meshes.slice(&Mesh::object), meshes.slice(&Mesh::meshMaterial)}, + SceneFieldData{SceneField::Camera, cameras.slice(&Camera::object), cameras.slice(&Camera::camera)}, + SceneFieldData{SceneField::Skin, skins.slice(&Skin::object), skins.slice(&Skin::skin)}, + /* Just to disambiguate this as a 2D scene */ + SceneFieldData{SceneField::Transformation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Matrix3x3, nullptr}, + }}; + SceneData dataSecondary{SceneMappingType::UnsignedInt, 31, std::move(dataDataSecondary), { + SceneFieldData{SceneField::Parent, parentsSecondary.slice(&Parent::object), parentsSecondary.slice(&Parent::parent)}, + SceneFieldData{SceneField::Mesh, meshesSecondary.slice(&Mesh::object), meshesSecondary.slice(&Mesh::mesh)}, + SceneFieldData{SceneField::MeshMaterial, meshesSecondary.slice(&Mesh::object), meshesSecondary.slice(&Mesh::meshMaterial)}, + /* Just to disambiguate this as a 2D scene */ + SceneFieldData{SceneField::Transformation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Matrix3x3, nullptr}, + }}; + + struct Importer: AbstractImporter { + explicit Importer(SceneData&& data, SceneData&& dataSecondary): _data{std::move(data)}, _dataSecondary{std::move(dataSecondary)} {} + + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 4; } + UnsignedLong doObjectCount() const override { return 63; } + std::string doObjectName(UnsignedLong id) override { + if(id == 1) return "object 1"; + if(id == 15) return "object 15"; + if(id == 23) return "object 23"; + if(id == 30) return "object 30 from secondary scene"; + if(id == 62) return "last"; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + Containers::Optional doScene(UnsignedInt id) override { + /* This scene should get skipped when querying names as it's not + 2D */ + if(id == 0) + return SceneData{SceneMappingType::UnsignedByte, 32, nullptr, {}}; + /* This scene should get skipped when querying names as it has too + little objects */ + if(id == 1) + return SceneData{SceneMappingType::UnsignedByte, 32, nullptr, { + SceneFieldData{SceneField::Transformation, SceneMappingType::UnsignedByte, nullptr, SceneFieldType::Matrix3x3, nullptr} + }}; + if(id == 2) + return SceneData{SceneMappingType::UnsignedInt, 32, {}, _data.data(), sceneFieldDataNonOwningArray(_data.fieldData())}; + /* A secondary scene, which should have non-overlapping IDs for the + newly added objects */ + if(id == 3) + return SceneData{SceneMappingType::UnsignedInt, 31, {}, _dataSecondary.data(), sceneFieldDataNonOwningArray(_dataSecondary.fieldData())}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + private: + SceneData _data, _dataSecondary; + } importer{std::move(data), std::move(dataSecondary)}; + + CORRADE_COMPARE(importer.sceneCount(), 4); + + CORRADE_IGNORE_DEPRECATED_PUSH + /* Total object count reported by the importer plus four new added for the + first and one for the second scene */ + CORRADE_COMPARE(importer.object2DCount(), 63 + 4 + 1); + CORRADE_COMPARE(importer.object3DCount(), 0); + + /* Object name should return parent names for the additional objects */ + CORRADE_COMPARE(importer.object2DName(62), "last"); + CORRADE_COMPARE(importer.object2DName(63), "object 23"); + CORRADE_COMPARE(importer.object2DName(64), "object 23"); + CORRADE_COMPARE(importer.object2DName(65), "object 15"); + CORRADE_COMPARE(importer.object2DName(66), "object 1"); + CORRADE_COMPARE(importer.object2DName(67), "object 30 from secondary scene"); + CORRADE_IGNORE_DEPRECATED_POP + + Containers::Optional scene = importer.scene(2); + CORRADE_VERIFY(scene); + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE_AS(scene->children2D(), + (std::vector{15, 21, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene->children3D(), + std::vector{}, + TestSuite::Compare::Container); + + /* Only 9 objects should exist in total, go in order. Usually the object + IDs will be contiguous so no such mess as this happens. */ + { + Containers::Pointer o = importer.object2D(1); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Mesh); + CORRADE_COMPARE(o->instance(), 7); + CORRADE_COMPARE_AS(o->children(), + std::vector{66}, + TestSuite::Compare::Container); + MeshObjectData2D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 2); + CORRADE_COMPARE(mo.skin(), -1); + } { + Containers::Pointer o = importer.object2D(15); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Mesh); + CORRADE_COMPARE(o->instance(), 6); + CORRADE_COMPARE_AS(o->children(), + std::vector{65}, + TestSuite::Compare::Container); + MeshObjectData2D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 4); + CORRADE_COMPARE(mo.skin(), 9); + } { + Containers::Pointer o = importer.object2D(21); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Mesh); + CORRADE_COMPARE(o->instance(), 5); + CORRADE_COMPARE_AS(o->children(), + std::vector{22}, + TestSuite::Compare::Container); + MeshObjectData2D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), -1); + CORRADE_COMPARE(mo.skin(), 10); + } { + Containers::Pointer o = importer.object2D(22); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Camera); + CORRADE_COMPARE(o->instance(), 1); + CORRADE_COMPARE_AS(o->children(), + std::vector{23}, + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object2D(23); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Mesh); + CORRADE_COMPARE(o->instance(), 1); + CORRADE_COMPARE_AS(o->children(), + (std::vector{63, 64}), + TestSuite::Compare::Container); + MeshObjectData2D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 0); + CORRADE_COMPARE(mo.skin(), -1); + } { + Containers::Pointer o = importer.object2D(63); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Mesh); + CORRADE_COMPARE(o->instance(), 2); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + MeshObjectData2D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 3); + CORRADE_COMPARE(mo.skin(), -1); + } { + Containers::Pointer o = importer.object2D(64); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Mesh); + CORRADE_COMPARE(o->instance(), 4); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + MeshObjectData2D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 2); + CORRADE_COMPARE(mo.skin(), -1); + } { + Containers::Pointer o = importer.object2D(65); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Mesh); + CORRADE_COMPARE(o->instance(), 3); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + MeshObjectData2D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 1); + CORRADE_COMPARE(mo.skin(), 9); + } { + Containers::Pointer o = importer.object2D(66); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Camera); + CORRADE_COMPARE(o->instance(), 5); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + } + CORRADE_IGNORE_DEPRECATED_POP + + Containers::Optional sceneSecondary = importer.scene(3); + CORRADE_VERIFY(sceneSecondary); + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE_AS(sceneSecondary->children2D(), + std::vector{30}, + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(sceneSecondary->children3D(), + (std::vector{}), + TestSuite::Compare::Container); + + /* One additional duplicated object here */ + { + Containers::Pointer o = importer.object2D(30); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Mesh); + CORRADE_COMPARE(o->instance(), 6); + CORRADE_COMPARE_AS(o->children(), + std::vector{67}, + TestSuite::Compare::Container); + MeshObjectData2D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 2); + } { + Containers::Pointer o = importer.object2D(67); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType2D::Mesh); + CORRADE_COMPARE(o->instance(), 1); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + MeshObjectData2D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), -1); + } + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::sceneDeprecatedFallbackMultiFunctionObjects3D() { + /* Mostly just a copy of SceneToolsTest::convertToSingleFunctionObjects() + except that here we can't use the convenience combining tool so it's + done by hand */ + + struct Parent { + UnsignedInt object; + Int parent; + }; + struct Mesh { + UnsignedInt object; + UnsignedInt mesh; + Int meshMaterial; + }; + struct Camera { + UnsignedInt object; + UnsignedInt camera; + }; + struct Skin { + UnsignedInt object; + UnsignedInt skin; + }; + Containers::StridedArrayView1D parents; + Containers::StridedArrayView1D meshes; + Containers::StridedArrayView1D cameras; + Containers::StridedArrayView1D skins; + Containers::Array dataData = Containers::ArrayTuple{ + {NoInit, 5, parents}, + {NoInit, 7, meshes}, + {NoInit, 2, cameras}, + {NoInit, 2, skins}, + }; + Utility::copy({{15, -1}, {21, -1}, {22, 21}, {23, 22}, {1, -1}}, parents); + Utility::copy({ + {15, 6, 4}, + {23, 1, 0}, + {23, 2, 3}, + {23, 4, 2}, + {1, 7, 2}, + {15, 3, 1}, + {21, 5, -1} + }, meshes); + Utility::copy({{22, 1}, {1, 5}}, cameras); + Utility::copy({{15, 9}, {21, 10}}, skins); + + /* Second scene that also has a duplicate, to verify the newly added object + IDs don't conflict with each other. A potential downside is that + multi-primitive nodes shared by multiple scenes get duplicated, but + that's a smaller problem than two unrelated nodes sharing the same ID + (and thus having a wrong name, etc). */ + Containers::StridedArrayView1D parentsSecondary; + Containers::StridedArrayView1D meshesSecondary; + Containers::Array dataDataSecondary = Containers::ArrayTuple{ + {NoInit, 1, parentsSecondary}, + {NoInit, 2, meshesSecondary} + }; + Utility::copy({{30, -1}}, parentsSecondary); + Utility::copy({ + {30, 6, 2}, + {30, 1, -1} + }, meshesSecondary); + + SceneData data{SceneMappingType::UnsignedInt, 32, std::move(dataData), { + SceneFieldData{SceneField::Parent, parents.slice(&Parent::object), parents.slice(&Parent::parent)}, + SceneFieldData{SceneField::Mesh, meshes.slice(&Mesh::object), meshes.slice(&Mesh::mesh)}, + SceneFieldData{SceneField::MeshMaterial, meshes.slice(&Mesh::object), meshes.slice(&Mesh::meshMaterial)}, + SceneFieldData{SceneField::Camera, cameras.slice(&Camera::object), cameras.slice(&Camera::camera)}, + SceneFieldData{SceneField::Skin, skins.slice(&Skin::object), skins.slice(&Skin::skin)}, + /* Just to disambiguate this as a 3D scene */ + SceneFieldData{SceneField::Transformation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Matrix4x4, nullptr}, + }}; + SceneData dataSecondary{SceneMappingType::UnsignedInt, 31, std::move(dataDataSecondary), { + SceneFieldData{SceneField::Parent, parentsSecondary.slice(&Parent::object), parentsSecondary.slice(&Parent::parent)}, + SceneFieldData{SceneField::Mesh, meshesSecondary.slice(&Mesh::object), meshesSecondary.slice(&Mesh::mesh)}, + SceneFieldData{SceneField::MeshMaterial, meshesSecondary.slice(&Mesh::object), meshesSecondary.slice(&Mesh::meshMaterial)}, + /* Just to disambiguate this as a 3D scene */ + SceneFieldData{SceneField::Transformation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Matrix4x4, nullptr}, + }}; + + struct Importer: AbstractImporter { + explicit Importer(SceneData&& data, SceneData&& dataSecondary): _data{std::move(data)}, _dataSecondary{std::move(dataSecondary)} {} + + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 4; } + UnsignedLong doObjectCount() const override { return 63; } + std::string doObjectName(UnsignedLong id) override { + if(id == 1) return "object 1"; + if(id == 15) return "object 15"; + if(id == 23) return "object 23"; + if(id == 30) return "object 30 from secondary scene"; + if(id == 62) return "last"; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + Containers::Optional doScene(UnsignedInt id) override { + /* This scene should get skipped when querying names as it's not + 2D */ + if(id == 0) + return SceneData{SceneMappingType::UnsignedByte, 32, nullptr, {}}; + /* This scene should get skipped when querying names as it has too + little objects */ + if(id == 1) + return SceneData{SceneMappingType::UnsignedByte, 32, nullptr, { + SceneFieldData{SceneField::Transformation, SceneMappingType::UnsignedByte, nullptr, SceneFieldType::Matrix4x4, nullptr} + }}; + if(id == 2) + return SceneData{SceneMappingType::UnsignedInt, 32, {}, _data.data(), sceneFieldDataNonOwningArray(_data.fieldData())}; + /* A secondary scene, which should have non-overlapping IDs for the + newly added objects */ + if(id == 3) + return SceneData{SceneMappingType::UnsignedInt, 31, {}, _dataSecondary.data(), sceneFieldDataNonOwningArray(_dataSecondary.fieldData())}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + + private: + SceneData _data, _dataSecondary; + } importer{std::move(data), std::move(dataSecondary)}; + + CORRADE_COMPARE(importer.sceneCount(), 4); + + CORRADE_IGNORE_DEPRECATED_PUSH + /* Total object count reported by the importer plus four new added for the + first and one for the second scene */ + CORRADE_COMPARE(importer.object2DCount(), 0); + CORRADE_COMPARE(importer.object3DCount(), 63 + 4 + 1); + + /* Object name should return parent names for the additional objects */ + CORRADE_COMPARE(importer.object3DName(62), "last"); + CORRADE_COMPARE(importer.object3DName(63), "object 23"); + CORRADE_COMPARE(importer.object3DName(64), "object 23"); + CORRADE_COMPARE(importer.object3DName(65), "object 15"); + CORRADE_COMPARE(importer.object3DName(66), "object 1"); + CORRADE_COMPARE(importer.object3DName(67), "object 30 from secondary scene"); + CORRADE_IGNORE_DEPRECATED_POP + + Containers::Optional scene = importer.scene(2); + CORRADE_VERIFY(scene); + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE_AS(scene->children2D(), + std::vector{}, + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene->children3D(), + (std::vector{15, 21, 1}), + TestSuite::Compare::Container); + + /* Only 9 objects should exist in total, go in order. Usually the object + IDs will be contiguous so no such mess as this happens. */ + { + Containers::Pointer o = importer.object3D(1); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(o->instance(), 7); + CORRADE_COMPARE_AS(o->children(), + std::vector{66}, + TestSuite::Compare::Container); + MeshObjectData3D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 2); + CORRADE_COMPARE(mo.skin(), -1); + } { + Containers::Pointer o = importer.object3D(15); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(o->instance(), 6); + CORRADE_COMPARE_AS(o->children(), + std::vector{65}, + TestSuite::Compare::Container); + MeshObjectData3D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 4); + CORRADE_COMPARE(mo.skin(), 9); + } { + Containers::Pointer o = importer.object3D(21); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(o->instance(), 5); + CORRADE_COMPARE_AS(o->children(), + std::vector{22}, + TestSuite::Compare::Container); + MeshObjectData3D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), -1); + CORRADE_COMPARE(mo.skin(), 10); + } { + Containers::Pointer o = importer.object3D(22); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Camera); + CORRADE_COMPARE(o->instance(), 1); + CORRADE_COMPARE_AS(o->children(), + std::vector{23}, + TestSuite::Compare::Container); + } { + Containers::Pointer o = importer.object3D(23); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(o->instance(), 1); + CORRADE_COMPARE_AS(o->children(), + (std::vector{63, 64}), + TestSuite::Compare::Container); + MeshObjectData3D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 0); + CORRADE_COMPARE(mo.skin(), -1); + } { + Containers::Pointer o = importer.object3D(63); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(o->instance(), 2); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + MeshObjectData3D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 3); + CORRADE_COMPARE(mo.skin(), -1); + } { + Containers::Pointer o = importer.object3D(64); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(o->instance(), 4); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + MeshObjectData3D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 2); + CORRADE_COMPARE(mo.skin(), -1); + } { + Containers::Pointer o = importer.object3D(65); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(o->instance(), 3); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + MeshObjectData3D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 1); + CORRADE_COMPARE(mo.skin(), 9); + } { + Containers::Pointer o = importer.object3D(66); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Camera); + CORRADE_COMPARE(o->instance(), 5); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + } + CORRADE_IGNORE_DEPRECATED_POP + + Containers::Optional sceneSecondary = importer.scene(3); + CORRADE_VERIFY(sceneSecondary); + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE_AS(sceneSecondary->children2D(), + std::vector{}, + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(sceneSecondary->children3D(), + (std::vector{30}), + TestSuite::Compare::Container); + + /* One additional duplicated object here */ + { + Containers::Pointer o = importer.object3D(30); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(o->instance(), 6); + CORRADE_COMPARE_AS(o->children(), + std::vector{67}, + TestSuite::Compare::Container); + MeshObjectData3D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), 2); + } { + Containers::Pointer o = importer.object3D(67); + CORRADE_VERIFY(o); + CORRADE_COMPARE(o->instanceType(), ObjectInstanceType3D::Mesh); + CORRADE_COMPARE(o->instance(), 1); + CORRADE_COMPARE_AS(o->children(), + std::vector{}, + TestSuite::Compare::Container); + MeshObjectData3D& mo = static_cast(*o); + CORRADE_COMPARE(mo.material(), -1); + } + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::sceneDeprecatedFallbackObjectCountNoScenes() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 0; } + UnsignedLong doObjectCount() const override { return 27; } + } importer; + + /* There's no scenes to get data or hierarchy from, so there are no + 2D/3D objects reported even though objectCount() says 27 */ + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object2DCount(), 0); + CORRADE_COMPARE(importer.object3DCount(), 0); + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::sceneDeprecatedFallbackObjectCountAllSceneImportFailed() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 1; } + UnsignedLong doObjectCount() const override { return 27; } + Containers::Optional doScene(UnsignedInt) override { + return {}; + } + } importer; + + /* There's a scene but it failed to import, assume it was 3D and proxy the + objectCount() */ + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object2DCount(), 0); + CORRADE_COMPARE(importer.object3DCount(), 27); + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::sceneDeprecatedFallbackBoth2DAnd3DScene() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 2; } + UnsignedLong doObjectCount() const override { return 7; } + Long doObjectForName(const std::string& name) override { + if(name == "sixth") return 5; + return -1; + } + std::string doObjectName(UnsignedLong id) override { + if(id == 5) return "sixth"; + return {}; + } + Containers::Optional doScene(UnsignedInt id) override { + if(id == 0) return SceneData{SceneMappingType::UnsignedInt, 7, nullptr, { + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Translation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Vector2, nullptr}, + }}; + if(id == 1) return SceneData{SceneMappingType::UnsignedInt, 7, nullptr, { + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Translation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Vector3, nullptr}, + }}; + CORRADE_INTERNAL_ASSERT_UNREACHABLE(); + } + } importer; + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object2DCount(), 7); + CORRADE_COMPARE(importer.object3DCount(), 7); + CORRADE_IGNORE_DEPRECATED_POP + + { + CORRADE_EXPECT_FAIL("No check for whether given object is 2D or 3D is done, so the names are reported for both 2D and 3D objects."); + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object2DForName("sixth"), -1); + CORRADE_COMPARE(importer.object2DName(5), ""); + CORRADE_COMPARE(importer.object3DForName("sixth"), -1); + CORRADE_COMPARE(importer.object3DName(5), ""); + CORRADE_IGNORE_DEPRECATED_POP + } { + /* Just to be sure, verify that the names get really reported for both + instead of some other weird shit happening */ + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object2DForName("sixth"), 5); + CORRADE_COMPARE(importer.object2DName(5), "sixth"); + CORRADE_COMPARE(importer.object3DForName("sixth"), 5); + CORRADE_COMPARE(importer.object3DName(5), "sixth"); + CORRADE_IGNORE_DEPRECATED_POP + } +} +#endif + +void AbstractImporterTest::sceneNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 8; } + } importer; + + CORRADE_COMPARE(importer.sceneName(7), ""); +} + +void AbstractImporterTest::objectNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedLong doObjectCount() const override { return 8; } + } importer; + + CORRADE_COMPARE(importer.objectName(7), ""); +} + +void AbstractImporterTest::sceneNameOutOfRange() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.sceneName(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::sceneName(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::objectNameOutOfRange() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedLong doObjectCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.objectName(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::objectName(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::sceneNotImplemented() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.scene(7); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::scene(): not implemented\n"); +} + +void AbstractImporterTest::sceneOutOfRange() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.scene(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::scene(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::sceneNonOwningDeleters() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 1; } + Containers::Optional doScene(UnsignedInt) override { + return SceneData{SceneMappingType::UnsignedInt, 0, + Containers::Array{data, 1, Implementation::nonOwnedArrayDeleter}, + sceneFieldDataNonOwningArray(fields)}; + } + + char data[1]; + SceneFieldData fields[1]{ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr} + }; + } importer; + + auto data = importer.scene(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(static_cast(data->data()), importer.data); + CORRADE_COMPARE(static_cast(data->fieldData()), importer.fields); +} + +void AbstractImporterTest::sceneCustomDataDeleter() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 1; } + Int doSceneForName(const std::string&) override { return 0; } + Containers::Optional doScene(UnsignedInt) override { + return SceneData{SceneMappingType::UnsignedInt, 0, + Containers::Array{data, 1, [](char*, std::size_t) {}}, + {}}; + } + + char data[1]; + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.scene(0); + importer.scene(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::scene(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::scene(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractImporterTest::sceneCustomFieldDataDeleter() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doSceneCount() const override { return 1; } + Int doSceneForName(const std::string&) override { return 0; } + Containers::Optional doScene(UnsignedInt) override { + return SceneData{SceneMappingType::UnsignedInt, 0, nullptr, Containers::Array{&parents, 1, [](SceneFieldData*, std::size_t) {}}}; + } + + SceneFieldData parents{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}; + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.scene(0); + importer.scene(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::scene(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::scene(): implementation is not allowed to use a custom Array deleter\n" + ); +} + +void AbstractImporterTest::sceneFieldName() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + SceneField doSceneFieldForName(const std::string& name) override { + if(name == "OctreeCell") return sceneFieldCustom(100037); + return SceneField{}; + } + + std::string doSceneFieldName(UnsignedInt id) override { + if(id == 100037) return "OctreeCell"; + return ""; + } + } importer; + + CORRADE_COMPARE(importer.sceneFieldForName("OctreeCell"), sceneFieldCustom(100037)); + CORRADE_COMPARE(importer.sceneFieldName(sceneFieldCustom(100037)), "OctreeCell"); +} + +void AbstractImporterTest::sceneFieldNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + CORRADE_COMPARE(importer.sceneFieldForName(""), SceneField{}); + CORRADE_COMPARE(importer.sceneFieldName(sceneFieldCustom(100037)), ""); +} + +void AbstractImporterTest::sceneFieldNameNotCustom() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + SceneField doSceneFieldForName(const std::string&) override { + return SceneField::Translation; + } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.sceneFieldForName("OctreeCell"); + importer.sceneFieldName(SceneField::Translation); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::sceneFieldForName(): implementation-returned Trade::SceneField::Translation is neither custom nor invalid\n" + "Trade::AbstractImporter::sceneFieldName(): Trade::SceneField::Translation is not custom\n"); +} + +void AbstractImporterTest::animation() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doAnimationCount() const override { return 8; } + Int doAnimationForName(const std::string& name) override { + if(name == "eighth") return 7; + return -1; + } + std::string doAnimationName(UnsignedInt id) override { + if(id == 7) return "eighth"; + return {}; + } + Containers::Optional doAnimation(UnsignedInt id) override { + /* Verify that initializer list is converted to an array with + the default deleter and not something disallowed */ + if(id == 7) return AnimationData{nullptr, { + AnimationTrackData{AnimationTrackType::Vector3, + AnimationTrackTargetType::Scaling3D, 0, {}} + }, &state}; + return AnimationData{{}, {}}; + } + } importer; + + CORRADE_COMPARE(importer.animationCount(), 8); + CORRADE_COMPARE(importer.animationForName("eighth"), 7); + CORRADE_COMPARE(importer.animationName(7), "eighth"); + + { + auto data = importer.animation(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.animation("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } +} + +void AbstractImporterTest::animationForNameOutOfRange() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doAnimationCount() const override { return 8; } + Int doAnimationForName(const std::string&) override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.animationForName(""); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animationForName(): implementation-returned index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::animationNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doAnimationCount() const override { return 8; } + } importer; + + CORRADE_COMPARE(importer.animationName(7), ""); +} + +void AbstractImporterTest::animationNameOutOfRange() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doAnimationCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.animationName(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animationName(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::animationNotImplemented() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doAnimationCount() const override { return 8; } + } importer; std::ostringstream out; Error redirectError{&out}; @@ -2200,80 +4040,175 @@ void AbstractImporterTest::cameraNameOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::cameraName(): index 8 out of range for 8 entries\n"); } -void AbstractImporterTest::cameraNotImplemented() { +void AbstractImporterTest::cameraNotImplemented() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doCameraCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.camera(7); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::camera(): not implemented\n"); +} + +void AbstractImporterTest::cameraOutOfRange() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doCameraCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.camera(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::camera(): index 8 out of range for 8 entries\n"); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractImporterTest::object2D() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doObject2DCount() const override { return 8; } + Int doObject2DForName(const std::string& name) override { + if(name == "eighth") return 7; + return -1; + } + std::string doObject2DName(UnsignedInt id) override { + if(id == 7) return "eighth"; + return {}; + } + CORRADE_IGNORE_DEPRECATED_PUSH + Containers::Pointer doObject2D(UnsignedInt id) override { + if(id == 7) return Containers::pointer(new ObjectData2D{{}, {}, &state}); + return {}; + } + CORRADE_IGNORE_DEPRECATED_POP + } importer; + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object2DCount(), 8); + CORRADE_COMPARE(importer.object2DForName("eighth"), 7); + CORRADE_COMPARE(importer.object2DName(7), "eighth"); + + { + auto data = importer.object2D(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.object2D("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::object2DCountNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object2DCount(), 0); + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::object2DCountNoFile() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } - bool doIsOpened() const override { return true; } + bool doIsOpened() const override { return false; } void doClose() override {} - - UnsignedInt doCameraCount() const override { return 8; } } importer; std::ostringstream out; Error redirectError{&out}; - importer.camera(7); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::camera(): not implemented\n"); + CORRADE_IGNORE_DEPRECATED_PUSH + importer.object2DCount(); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object2DCount(): no file opened\n"); } -void AbstractImporterTest::cameraOutOfRange() { +void AbstractImporterTest::object2DForNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object2DForName(""), -1); + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::object2DForNameNoFile() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } - bool doIsOpened() const override { return true; } + bool doIsOpened() const override { return false; } void doClose() override {} - - UnsignedInt doCameraCount() const override { return 8; } } importer; std::ostringstream out; Error redirectError{&out}; - importer.camera(8); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::camera(): index 8 out of range for 8 entries\n"); + CORRADE_IGNORE_DEPRECATED_PUSH + importer.object2DForName(""); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object2DForName(): no file opened\n"); } -void AbstractImporterTest::object2D() { +void AbstractImporterTest::object2DByNameNotFound() { + auto&& data = ThingByNameData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } bool doIsOpened() const override { return true; } void doClose() override {} - UnsignedInt doObject2DCount() const override { return 8; } - Int doObject2DForName(const std::string& name) override { - if(name == "eighth") return 7; - return -1; - } - std::string doObject2DName(UnsignedInt id) override { - if(id == 7) return "eighth"; - return {}; - } - Containers::Pointer doObject2D(UnsignedInt id) override { - if(id == 7) return Containers::pointer(new ObjectData2D{{}, {}, &state}); - return {}; - } + UnsignedInt doObject2DCount() const override { return 5; } } importer; - CORRADE_COMPARE(importer.object2DCount(), 8); - CORRADE_COMPARE(importer.object2DForName("eighth"), 7); - CORRADE_COMPARE(importer.object2DName(7), "eighth"); - + std::ostringstream out; { - auto data = importer.object2D(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); - } { - auto data = importer.object2D("eighth"); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + Containers::Optional redirectError; + if(data.checkMessage) redirectError.emplace(&out); + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_VERIFY(!importer.object2D("foobar")); + CORRADE_IGNORE_DEPRECATED_POP } + + if(data.checkMessage) CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::object2D(): object foobar not found among 5 entries\n"); } void AbstractImporterTest::object2DForNameOutOfRange() { @@ -2292,7 +4227,9 @@ void AbstractImporterTest::object2DForNameOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.object2DForName(""); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object2DForName(): implementation-returned index 8 out of range for 8 entries\n"); } @@ -2305,7 +4242,29 @@ void AbstractImporterTest::object2DNameNotImplemented() { UnsignedInt doObject2DCount() const override { return 8; } } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.object2DName(7), ""); + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::object2DNameNoFile() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_IGNORE_DEPRECATED_PUSH + importer.object2DName(42); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object2DName(): no file opened\n"); } void AbstractImporterTest::object2DNameOutOfRange() { @@ -2324,7 +4283,9 @@ void AbstractImporterTest::object2DNameOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.object2DName(8); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object2DName(): index 8 out of range for 8 entries\n"); } @@ -2338,14 +4299,44 @@ void AbstractImporterTest::object2DNotImplemented() { bool doIsOpened() const override { return true; } void doClose() override {} + UnsignedInt doSceneCount() const override { return 1; } UnsignedInt doObject2DCount() const override { return 8; } } importer; std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.object2D(7); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object2D(): not implemented\n"); + CORRADE_IGNORE_DEPRECATED_POP + /* It delegates to scene(), but since the assert is graceful and returns a + null optional, it errors out immediately after */ + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::scene(): not implemented\n" + "Trade::AbstractImporter::object2D(): object 7 not found in any 2D scene hierarchy\n"); +} + +void AbstractImporterTest::object2DNoFile() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_IGNORE_DEPRECATED_PUSH + importer.object2D(42); + importer.object2D("foo"); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::object2D(): no file opened\n" + "Trade::AbstractImporter::object2D(): no file opened\n"); } void AbstractImporterTest::object2DOutOfRange() { @@ -2364,7 +4355,9 @@ void AbstractImporterTest::object2DOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.object2D(8); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object2D(): index 8 out of range for 8 entries\n"); } @@ -2383,12 +4376,15 @@ void AbstractImporterTest::object3D() { if(id == 7) return "eighth"; return {}; } + CORRADE_IGNORE_DEPRECATED_PUSH Containers::Pointer doObject3D(UnsignedInt id) override { if(id == 7) return Containers::pointer(new ObjectData3D{{}, {}, &state}); return {}; } + CORRADE_IGNORE_DEPRECATED_POP } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.object3DCount(), 8); CORRADE_COMPARE(importer.object3DForName("eighth"), 7); CORRADE_COMPARE(importer.object3DName(7), "eighth"); @@ -2402,6 +4398,97 @@ void AbstractImporterTest::object3D() { CORRADE_VERIFY(data); CORRADE_COMPARE(data->importerState(), &state); } + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::object3DCountNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object3DCount(), 0); + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::object3DCountNoFile() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_IGNORE_DEPRECATED_PUSH + importer.object3DCount(); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object3DCount(): no file opened\n"); +} + +void AbstractImporterTest::object3DForNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE(importer.object3DForName(""), -1); + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::object3DForNameNoFile() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_IGNORE_DEPRECATED_PUSH + importer.object2DForName(""); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object2DForName(): no file opened\n"); +} + +void AbstractImporterTest::object3DByNameNotFound() { + auto&& data = ThingByNameData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doObject3DCount() const override { return 6; } + } importer; + + std::ostringstream out; + { + Containers::Optional redirectError; + if(data.checkMessage) redirectError.emplace(&out); + + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_VERIFY(!importer.object3D("foobar")); + CORRADE_IGNORE_DEPRECATED_POP + } + + if(data.checkMessage) CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::object3D(): object foobar not found among 6 entries\n"); } void AbstractImporterTest::object3DForNameOutOfRange() { @@ -2420,7 +4507,9 @@ void AbstractImporterTest::object3DForNameOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.object3DForName(""); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object3DForName(): implementation-returned index 8 out of range for 8 entries\n"); } @@ -2433,7 +4522,29 @@ void AbstractImporterTest::object3DNameNotImplemented() { UnsignedInt doObject3DCount() const override { return 8; } } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.object3DName(7), ""); + CORRADE_IGNORE_DEPRECATED_POP +} + +void AbstractImporterTest::object3DNameNoFile() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_IGNORE_DEPRECATED_PUSH + importer.object3DName(42); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object3DName(): no file opened\n"); } void AbstractImporterTest::object3DNameOutOfRange() { @@ -2452,7 +4563,9 @@ void AbstractImporterTest::object3DNameOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.object3DName(8); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object3DName(): index 8 out of range for 8 entries\n"); } @@ -2466,14 +4579,44 @@ void AbstractImporterTest::object3DNotImplemented() { bool doIsOpened() const override { return true; } void doClose() override {} + UnsignedInt doSceneCount() const override { return 1; } UnsignedInt doObject3DCount() const override { return 8; } } importer; std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.object3D(7); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object3D(): not implemented\n"); + CORRADE_IGNORE_DEPRECATED_POP + /* It delegates to scene(), but since the assert is graceful and returns a + null optional, it errors out immediately after */ + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::scene(): not implemented\n" + "Trade::AbstractImporter::object3D(): object 7 not found in any 3D scene hierarchy\n"); +} + +void AbstractImporterTest::object3DNoFile() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + CORRADE_IGNORE_DEPRECATED_PUSH + importer.object3D(42); + importer.object3D("foo"); + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::object3D(): no file opened\n" + "Trade::AbstractImporter::object3D(): no file opened\n"); } void AbstractImporterTest::object3DOutOfRange() { @@ -2492,9 +4635,12 @@ void AbstractImporterTest::object3DOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.object3D(8); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object3D(): index 8 out of range for 8 entries\n"); } +#endif void AbstractImporterTest::skin2D() { struct: AbstractImporter { diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index f90dba8b22..521be6d740 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -54,13 +54,12 @@ corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestL corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeMeshDataTest MeshDataTest.cpp LIBRARIES MagnumTradeTestLib) -corrade_add_test(TradeObjectData2DTest ObjectData2DTest.cpp LIBRARIES MagnumTradeTestLib) -corrade_add_test(TradeObjectData3DTest ObjectData3DTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradePbrClearCoatMaterialDataTest PbrClearCoatMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradePbrMetallicRoughnessMate___Test PbrMetallicRoughnessMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradePbrSpecularGlossinessMat___Test PbrSpecularGlossinessMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradePhongMaterialDataTest PhongMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) -corrade_add_test(TradeSceneDataTest SceneDataTest.cpp LIBRARIES MagnumTrade) +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) @@ -68,6 +67,7 @@ set_property(TARGET TradeAnimationDataTest TradeMaterialDataTest TradeMeshDataTest + TradeSceneDataTest APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") set_target_properties( @@ -80,22 +80,25 @@ set_target_properties( TradeImageDataTest TradeLightDataTest TradeMaterialDataTest - TradeObjectData2DTest - TradeObjectData3DTest TradePbrClearCoatMaterialDataTest TradePbrMetallicRoughnessMate___Test TradePbrSpecularGlossinessMat___Test TradePhongMaterialDataTest TradeSceneDataTest + TradeSceneToolsTest TradeTextureDataTest PROPERTIES FOLDER "Magnum/Trade/Test") if(MAGNUM_BUILD_DEPRECATED) corrade_add_test(TradeMeshData2DTest MeshData2DTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeMeshData3DTest MeshData3DTest.cpp LIBRARIES MagnumTrade) + corrade_add_test(TradeObjectData2DTest ObjectData2DTest.cpp LIBRARIES MagnumTradeTestLib) + corrade_add_test(TradeObjectData3DTest ObjectData3DTest.cpp LIBRARIES MagnumTradeTestLib) set_target_properties( TradeMeshData2DTest TradeMeshData3DTest + TradeObjectData2DTest + TradeObjectData3DTest PROPERTIES FOLDER "Magnum/Trade/Test") endif() diff --git a/src/Magnum/Trade/Test/ObjectData2DTest.cpp b/src/Magnum/Trade/Test/ObjectData2DTest.cpp index a4ad55b573..48e0390153 100644 --- a/src/Magnum/Trade/Test/ObjectData2DTest.cpp +++ b/src/Magnum/Trade/Test/ObjectData2DTest.cpp @@ -23,6 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +/* There's no better way to disable file deprecation warnings */ +#define _MAGNUM_NO_DEPRECATED_OBJECTDATA + #include #include #include @@ -51,6 +54,8 @@ class ObjectData2DTest: public TestSuite::Tester { void debugFlags(); }; +CORRADE_IGNORE_DEPRECATED_PUSH + ObjectData2DTest::ObjectData2DTest() { addTests({&ObjectData2DTest::constructEmpty, &ObjectData2DTest::constructEmptyTransformations, @@ -264,6 +269,8 @@ void ObjectData2DTest::debugFlags() { CORRADE_COMPARE(o.str(), "Trade::ObjectFlag2D::HasTranslationRotationScaling Trade::ObjectFlags2D{}\n"); } +CORRADE_IGNORE_DEPRECATED_POP + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::ObjectData2DTest) diff --git a/src/Magnum/Trade/Test/ObjectData3DTest.cpp b/src/Magnum/Trade/Test/ObjectData3DTest.cpp index 2b39a4eaaf..3fe3a6e84d 100644 --- a/src/Magnum/Trade/Test/ObjectData3DTest.cpp +++ b/src/Magnum/Trade/Test/ObjectData3DTest.cpp @@ -23,6 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +/* There's no better way to disable file deprecation warnings */ +#define _MAGNUM_NO_DEPRECATED_OBJECTDATA + #include #include #include @@ -70,6 +73,8 @@ ObjectData3DTest::ObjectData3DTest() { &ObjectData3DTest::debugFlags}); } +CORRADE_IGNORE_DEPRECATED_PUSH + using namespace Math::Literals; void ObjectData3DTest::constructEmpty() { @@ -278,6 +283,8 @@ void ObjectData3DTest::debugFlags() { CORRADE_COMPARE(o.str(), "Trade::ObjectFlag3D::HasTranslationRotationScaling Trade::ObjectFlags3D{}\n"); } +CORRADE_IGNORE_DEPRECATED_POP + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::ObjectData3DTest) diff --git a/src/Magnum/Trade/Test/SceneDataTest.cpp b/src/Magnum/Trade/Test/SceneDataTest.cpp index 26e81c9b7b..395e033307 100644 --- a/src/Magnum/Trade/Test/SceneDataTest.cpp +++ b/src/Magnum/Trade/Test/SceneDataTest.cpp @@ -23,61 +23,5571 @@ DEALINGS IN THE SOFTWARE. */ +#include +#include +#include +#include +#include #include +#include +#include +#include #include "Magnum/Magnum.h" +#include "Magnum/Math/Half.h" +#include "Magnum/Math/DualComplex.h" +#include "Magnum/Math/DualQuaternion.h" +#include "Magnum/Math/Range.h" #include "Magnum/Trade/SceneData.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#endif + namespace Magnum { namespace Trade { namespace Test { namespace { struct SceneDataTest: TestSuite::Tester { explicit SceneDataTest(); + void mappingTypeSizeAlignment(); + void mappingTypeSizeAlignmentInvalid(); + void debugMappingType(); + + void customFieldName(); + void customFieldNameTooLarge(); + void customFieldNameNotCustom(); + void debugFieldName(); + + void fieldTypeSizeAlignment(); + void fieldTypeSizeAlignmentInvalid(); + void debugFieldType(); + + void debugFieldFlag(); + void debugFieldFlags(); + void debugFieldFlagsSupersets(); + + void constructField(); + void constructFieldDefault(); + void constructFieldCustom(); + void constructField2D(); + void constructFieldTypeErased(); + void constructFieldNonOwningArray(); + void constructFieldOffsetOnly(); + void constructFieldArray(); + void constructFieldArray2D(); + void constructFieldArrayTypeErased(); + void constructFieldArrayOffsetOnly(); + + void constructFieldWrongType(); + void constructFieldInconsistentViewSize(); + void constructFieldTooLargeMappingStride(); + void constructFieldTooLargeFieldStride(); + void constructFieldOffsetOnlyNotAllowed(); + void constructFieldWrongDataAccess(); + void constructField2DWrongSize(); + void constructField2DNonContiguous(); + void constructFieldArrayNonContiguous(); + void constructFieldArrayNotAllowed(); + void constructFieldArray2DWrongSize(); + void constructFieldArray2DNonContiguous(); + void construct(); + void constructZeroFields(); + void constructZeroObjects(); + void constructSpecialStrides(); + void constructNotOwned(); + #ifdef MAGNUM_BUILD_DEPRECATED + void constructDeprecated(); + void constructDeprecatedBoth2DAnd3D(); + #endif + + void constructDuplicateField(); + void constructDuplicateCustomField(); + void constructInconsistentMappingType(); + void constructMappingDataNotContained(); + void constructFieldDataNotContained(); + void constructMappingTypeTooSmall(); + void constructNotOwnedFlagOwned(); + void constructMismatchedTRSViews(); + template void constructMismatchedTRSDimensionality(); + void constructMismatchedMeshMaterialView(); + void constructAmbiguousSkinDimensions(); + void constructCopy(); void constructMove(); + + void findFieldId(); + template void findFieldObjectOffset(); + void findFieldObjectOffsetInvalidOffset(); + void fieldObjectOffsetNotFound(); + + template void mappingAsArrayByIndex(); + template void mappingAsArrayByName(); + void mappingIntoArrayByIndex(); + void mappingIntoArrayByName(); + void mappingIntoArrayInvalidSizeOrOffset(); + + template void parentsAsArray(); + void parentsIntoArray(); + void parentsIntoArrayInvalidSizeOrOffset(); + template void transformations2DAsArray(); + template void transformations2DAsArrayTRS(); + void transformations2DAsArrayBut3DType(); + void transformations2DIntoArray(); + void transformations2DTRSIntoArray(); + void transformations2DIntoArrayTRS(); + void transformations2DIntoArrayInvalidSizeOrOffset(); + void transformations2DIntoArrayInvalidSizeOrOffsetTRS(); + template void transformations3DAsArray(); + template void transformations3DAsArrayTRS(); + void transformations3DAsArrayBut2DType(); + void transformations3DIntoArray(); + void transformations3DTRSIntoArray(); + void transformations3DIntoArrayTRS(); + void transformations3DIntoArrayInvalidSizeOrOffset(); + void transformations3DIntoArrayInvalidSizeOrOffsetTRS(); + template void meshesMaterialsAsArray(); + void meshesMaterialsIntoArray(); + void meshesMaterialsIntoArrayInvalidSizeOrOffset(); + template void lightsAsArray(); + void lightsIntoArray(); + void lightsIntoArrayInvalidSizeOrOffset(); + template void camerasAsArray(); + void camerasIntoArray(); + void camerasIntoArrayInvalidSizeOrOffset(); + template void skinsAsArray(); + void skinsIntoArray(); + void skinsIntoArrayInvalidSizeOrOffset(); + template void importerStateAsArray(); + void importerStateIntoArray(); + void importerStateIntoArrayInvalidSizeOrOffset(); + + void mutableAccessNotAllowed(); + + void mappingNotFound(); + void mappingWrongType(); + + void fieldNotFound(); + void fieldWrongType(); + void fieldWrongPointerType(); + void fieldWrongArrayAccess(); + + void parentFor(); + void parentForTrivialParent(); + void childrenFor(); + void childrenForTrivialParent(); + void transformation2DFor(); + void transformation2DForTRS(); + void transformation2DForBut3DType(); + void transformation3DFor(); + void transformation3DForTRS(); + void transformation3DForBut2DType(); + void meshesMaterialsFor(); + void lightsFor(); + void camerasFor(); + void skinsFor(); + void importerStateFor(); + + #ifdef MAGNUM_BUILD_DEPRECATED + void childrenDeprecated(); + #endif + + void fieldForFieldMissing(); + void findFieldObjectOffsetInvalidObject(); + + void releaseFieldData(); + void releaseData(); +}; + +const struct { + const char* name; + DataFlags dataFlags; +} NotOwnedData[]{ + {"", {}}, + {"mutable", DataFlag::Mutable} +}; + +const struct { + const char* name; + SceneFieldFlags flags; + UnsignedInt mapping[5]; + UnsignedInt object; + UnsignedInt offset; + Containers::Optional expected; +} FindFieldObjectOffsetData[]{ + {"", {}, + {4, 2, 1, 0, 2}, 2, 0, 1}, + {"not found", {}, + {4, 2, 1, 0, 2}, 3, 0, Containers::NullOpt}, + {"offset", {}, + {4, 2, 1, 0, 2}, 2, 2, 4}, + {"offset, not found", {}, + {4, 2, 1, 0, 2}, 2, 5, Containers::NullOpt}, + + {"ordered", SceneFieldFlag::OrderedMapping, + {1, 3, 4, 4, 5}, 4, 0, 2}, + {"ordered, not found", SceneFieldFlag::OrderedMapping, + /* It *is* there but the binary search expects an ordered range and thus + should not even see it */ + {1, 3, 4, 4, 2}, 2, 0, Containers::NullOpt}, + {"ordered, not found, too small", SceneFieldFlag::OrderedMapping, + {1, 3, 4, 4, 5}, 0, 0, Containers::NullOpt}, + {"ordered, not found, too large", SceneFieldFlag::OrderedMapping, + {1, 3, 4, 4, 5}, 6, 0, Containers::NullOpt}, + {"ordered, offset", SceneFieldFlag::OrderedMapping, + {1, 3, 4, 4, 5}, 4, 3, 3}, + {"ordered, offset, not found", SceneFieldFlag::OrderedMapping, + {1, 3, 4, 4, 5}, 4, 4, Containers::NullOpt}, + + {"implicit", SceneFieldFlag::ImplicitMapping, + /* Not there but the assumption is that the ID matches the offset */ + {5, 5, 5, 5, 5}, 3, 0, 3}, + {"implicit, not found", SceneFieldFlag::ImplicitMapping, + /* Is there but the assumption is that the ID matches the offset, which + is out of bounds */ + {5, 5, 5, 5, 5}, 5, 0, Containers::NullOpt}, + {"implicit, offset", SceneFieldFlag::ImplicitMapping, + /* Not there but the assumption is that the ID matches the offset; + verifying that the offset is properly accounted for */ + {5, 5, 5, 5, 5}, 3, 3, 3}, + {"implicit, offset, not found, less than offset", SceneFieldFlag::ImplicitMapping, + /* Cerifying that the offset is properly accounted for -- it's never + found if offset > id */ + {5, 5, 5, 5, 5}, 3, 4, Containers::NullOpt}, + {"implicit, offset, not found, out of bounds", SceneFieldFlag::ImplicitMapping, + {5, 5, 5, 5, 5}, 5, 4, Containers::NullOpt} +}; + +const struct { + const char* name; + std::size_t offset; + std::size_t size; + std::size_t expectedSize; +} IntoArrayOffsetData[]{ + {"whole", 0, 3, 3}, + {"one element in the middle", 1, 1, 1}, + {"suffix to a larger array", 2, 10, 1}, + {"offset at the end", 3, 10, 0} +}; + +const struct { + const char* name; + std::size_t offset; + std::size_t size; + std::size_t expectedSize; + bool mapping, field; +} IntoArrayOffset1Data[]{ + {"whole", 0, 3, 3, true, true}, + {"one element in the middle", 1, 1, 1, true, true}, + {"suffix to a larger array", 2, 10, 1, true, true}, + {"offset at the end", 3, 10, 0, true, true}, + {"only mapping", 0, 3, 3, true, false}, + {"only field", 0, 3, 3, false, true}, + {"neither", 0, 3, 0, false, false} +}; + +const struct { + const char* name; + std::size_t offset; + std::size_t size; + std::size_t expectedSize; + bool mapping, field1, field2; +} IntoArrayOffset2Data[]{ + {"whole", 0, 3, 3, true, true, true}, + {"one element in the middle", 1, 1, 1, true, true, true}, + {"suffix to a larger array", 2, 10, 1, true, true, true}, + {"offset at the end", 3, 10, 0, true, true, true}, + {"only mapping", 0, 3, 3, true, false, false}, + {"only fields", 0, 3, 3, false, true, true}, + {"only first field", 0, 3, 3, false, true, false}, + {"only second field", 0, 3, 3, false, false, true}, + {"none", 0, 3, 0, false, false, false} +}; + +const struct { + const char* name; + std::size_t offset; + std::size_t size; + std::size_t expectedSize; + bool mapping, field1, field2, field3; +} IntoArrayOffset3Data[]{ + {"whole", 0, 3, 3, true, true, true, true}, + {"one element in the middle", 1, 1, 1, true, true, true, true}, + {"suffix to a larger array", 2, 10, 1, true, true, true, true}, + {"offset at the end", 3, 10, 0, true, true, true, true}, + {"only mapping", 0, 3, 3, true, false, false, true}, + {"only fields", 0, 3, 3, false, true, true, true}, + {"only first field", 0, 3, 3, false, true, false, false}, + {"only second field", 0, 3, 3, false, false, true, false}, + {"only third field", 0, 3, 3, false, false, false, true}, + {"none", 0, 3, 0, false, false, false, false} +}; + +#ifdef MAGNUM_BUILD_DEPRECATED +const struct { + const char* name; + bool is2D; + bool is3D; + bool skipParent; +} ChildrenDeprecatedData[]{ + {"2D", true, false, false}, + {"2D with no parent", true, false, true}, + {"3D", false, true, false}, + {"3D with no parent", false, true, true}, + {"neither", false, false, false}, + {"neither with no parent", false, false, true} }; +#endif SceneDataTest::SceneDataTest() { - addTests({&SceneDataTest::construct, + addTests({&SceneDataTest::mappingTypeSizeAlignment, + &SceneDataTest::mappingTypeSizeAlignmentInvalid, + &SceneDataTest::debugMappingType, + + &SceneDataTest::customFieldName, + &SceneDataTest::customFieldNameTooLarge, + &SceneDataTest::customFieldNameNotCustom, + &SceneDataTest::debugFieldName, + + &SceneDataTest::fieldTypeSizeAlignment, + &SceneDataTest::fieldTypeSizeAlignmentInvalid, + &SceneDataTest::debugFieldType, + + &SceneDataTest::debugFieldFlag, + &SceneDataTest::debugFieldFlags, + &SceneDataTest::debugFieldFlagsSupersets, + + &SceneDataTest::constructField, + &SceneDataTest::constructFieldDefault, + &SceneDataTest::constructFieldCustom, + &SceneDataTest::constructField2D, + &SceneDataTest::constructFieldTypeErased, + &SceneDataTest::constructFieldNonOwningArray, + &SceneDataTest::constructFieldOffsetOnly, + &SceneDataTest::constructFieldArray, + &SceneDataTest::constructFieldArray2D, + &SceneDataTest::constructFieldArrayTypeErased, + &SceneDataTest::constructFieldArrayOffsetOnly, + + &SceneDataTest::constructFieldWrongType, + &SceneDataTest::constructFieldInconsistentViewSize, + &SceneDataTest::constructFieldTooLargeMappingStride, + &SceneDataTest::constructFieldTooLargeFieldStride, + &SceneDataTest::constructFieldOffsetOnlyNotAllowed, + &SceneDataTest::constructFieldWrongDataAccess, + &SceneDataTest::constructField2DWrongSize, + &SceneDataTest::constructField2DNonContiguous, + &SceneDataTest::constructFieldArrayNonContiguous, + &SceneDataTest::constructFieldArrayNotAllowed, + &SceneDataTest::constructFieldArray2DWrongSize, + &SceneDataTest::constructFieldArray2DNonContiguous, + + &SceneDataTest::construct, + &SceneDataTest::constructZeroFields, + &SceneDataTest::constructZeroObjects, + &SceneDataTest::constructSpecialStrides}); + + addInstancedTests({&SceneDataTest::constructNotOwned}, + Containers::arraySize(NotOwnedData)); + + #ifdef MAGNUM_BUILD_DEPRECATED + addInstancedTests({&SceneDataTest::constructDeprecated}, + Containers::arraySize(ChildrenDeprecatedData)); + + addTests({&SceneDataTest::constructDeprecatedBoth2DAnd3D}); + #endif + + addTests({&SceneDataTest::constructDuplicateField, + &SceneDataTest::constructDuplicateCustomField, + &SceneDataTest::constructInconsistentMappingType, + &SceneDataTest::constructMappingDataNotContained, + &SceneDataTest::constructFieldDataNotContained, + &SceneDataTest::constructMappingTypeTooSmall, + &SceneDataTest::constructNotOwnedFlagOwned, + &SceneDataTest::constructMismatchedTRSViews, + &SceneDataTest::constructMismatchedTRSDimensionality, + &SceneDataTest::constructMismatchedTRSDimensionality, + &SceneDataTest::constructMismatchedMeshMaterialView, + &SceneDataTest::constructAmbiguousSkinDimensions, + &SceneDataTest::constructCopy, - &SceneDataTest::constructMove}); + &SceneDataTest::constructMove, + + &SceneDataTest::findFieldId}); + + addInstancedTests({ + &SceneDataTest::findFieldObjectOffset, + &SceneDataTest::findFieldObjectOffset, + &SceneDataTest::findFieldObjectOffset, + &SceneDataTest::findFieldObjectOffset + }, Containers::arraySize(FindFieldObjectOffsetData)); + + addTests({&SceneDataTest::findFieldObjectOffsetInvalidOffset, + &SceneDataTest::fieldObjectOffsetNotFound, + + &SceneDataTest::mappingAsArrayByIndex, + &SceneDataTest::mappingAsArrayByIndex, + &SceneDataTest::mappingAsArrayByIndex, + &SceneDataTest::mappingAsArrayByIndex, + &SceneDataTest::mappingAsArrayByName, + &SceneDataTest::mappingAsArrayByName, + &SceneDataTest::mappingAsArrayByName, + &SceneDataTest::mappingAsArrayByName}); + + addInstancedTests({&SceneDataTest::mappingIntoArrayByIndex, + &SceneDataTest::mappingIntoArrayByName}, + Containers::arraySize(IntoArrayOffsetData)); + + addTests({&SceneDataTest::mappingIntoArrayInvalidSizeOrOffset, + + &SceneDataTest::parentsAsArray, + &SceneDataTest::parentsAsArray, + &SceneDataTest::parentsAsArray, + &SceneDataTest::parentsAsArray}); + + addInstancedTests({&SceneDataTest::parentsIntoArray}, + Containers::arraySize(IntoArrayOffset1Data)); + + addTests({&SceneDataTest::parentsIntoArrayInvalidSizeOrOffset, + &SceneDataTest::transformations2DAsArray, + &SceneDataTest::transformations2DAsArray, + &SceneDataTest::transformations2DAsArray, + &SceneDataTest::transformations2DAsArray, + &SceneDataTest::transformations2DAsArray, + &SceneDataTest::transformations2DAsArray, + &SceneDataTest::transformations2DAsArrayTRS, + &SceneDataTest::transformations2DAsArrayTRS, + &SceneDataTest::transformations2DAsArrayBut3DType}); + + addInstancedTests({&SceneDataTest::transformations2DIntoArray, + &SceneDataTest::transformations2DTRSIntoArray}, + Containers::arraySize(IntoArrayOffset1Data)); + + addInstancedTests({&SceneDataTest::transformations2DIntoArrayTRS}, + Containers::arraySize(IntoArrayOffset3Data)); + + addTests({&SceneDataTest::transformations2DIntoArrayInvalidSizeOrOffset, + &SceneDataTest::transformations2DIntoArrayInvalidSizeOrOffsetTRS, + &SceneDataTest::transformations3DAsArray, + &SceneDataTest::transformations3DAsArray, + &SceneDataTest::transformations3DAsArray, + &SceneDataTest::transformations3DAsArray, + &SceneDataTest::transformations3DAsArray, + &SceneDataTest::transformations3DAsArray, + &SceneDataTest::transformations3DAsArrayTRS, + &SceneDataTest::transformations3DAsArrayTRS, + &SceneDataTest::transformations3DAsArrayBut2DType}); + + addInstancedTests({&SceneDataTest::transformations3DIntoArray, + &SceneDataTest::transformations3DTRSIntoArray}, + Containers::arraySize(IntoArrayOffset1Data)); + + addInstancedTests({&SceneDataTest::transformations3DIntoArrayTRS}, + Containers::arraySize(IntoArrayOffset3Data)); + + addTests({&SceneDataTest::transformations3DIntoArrayInvalidSizeOrOffset, + &SceneDataTest::transformations3DIntoArrayInvalidSizeOrOffsetTRS, + &SceneDataTest::meshesMaterialsAsArray, + &SceneDataTest::meshesMaterialsAsArray, + &SceneDataTest::meshesMaterialsAsArray}); + + addInstancedTests({&SceneDataTest::meshesMaterialsIntoArray}, + Containers::arraySize(IntoArrayOffset2Data)); + + addTests({&SceneDataTest::meshesMaterialsIntoArrayInvalidSizeOrOffset, + &SceneDataTest::lightsAsArray, + &SceneDataTest::lightsAsArray, + &SceneDataTest::lightsAsArray}); + + addInstancedTests({&SceneDataTest::lightsIntoArray}, + Containers::arraySize(IntoArrayOffset1Data)); + + addTests({&SceneDataTest::lightsIntoArrayInvalidSizeOrOffset, + &SceneDataTest::camerasAsArray, + &SceneDataTest::camerasAsArray, + &SceneDataTest::camerasAsArray}); + + addInstancedTests({&SceneDataTest::camerasIntoArray}, + Containers::arraySize(IntoArrayOffset1Data)); + + addTests({&SceneDataTest::camerasIntoArrayInvalidSizeOrOffset, + &SceneDataTest::skinsAsArray, + &SceneDataTest::skinsAsArray, + &SceneDataTest::skinsAsArray}); + + addInstancedTests({&SceneDataTest::skinsIntoArray}, + Containers::arraySize(IntoArrayOffset1Data)); + + addTests({&SceneDataTest::skinsIntoArrayInvalidSizeOrOffset, + &SceneDataTest::importerStateAsArray, + &SceneDataTest::importerStateAsArray}); + + addInstancedTests({&SceneDataTest::importerStateIntoArray}, + Containers::arraySize(IntoArrayOffset1Data)); + + addTests({&SceneDataTest::importerStateIntoArrayInvalidSizeOrOffset, + + &SceneDataTest::mutableAccessNotAllowed, + + &SceneDataTest::mappingNotFound, + &SceneDataTest::mappingWrongType, + + &SceneDataTest::fieldNotFound, + &SceneDataTest::fieldWrongType, + &SceneDataTest::fieldWrongPointerType, + &SceneDataTest::fieldWrongArrayAccess, + + &SceneDataTest::parentFor, + &SceneDataTest::parentForTrivialParent, + &SceneDataTest::childrenFor, + &SceneDataTest::childrenForTrivialParent, + &SceneDataTest::transformation2DFor, + &SceneDataTest::transformation2DForTRS, + &SceneDataTest::transformation2DForBut3DType, + &SceneDataTest::transformation3DFor, + &SceneDataTest::transformation3DForTRS, + &SceneDataTest::transformation3DForBut2DType, + &SceneDataTest::meshesMaterialsFor, + &SceneDataTest::lightsFor, + &SceneDataTest::camerasFor, + &SceneDataTest::skinsFor, + &SceneDataTest::importerStateFor}); + + #ifdef MAGNUM_BUILD_DEPRECATED + addInstancedTests({&SceneDataTest::childrenDeprecated}, + Containers::arraySize(ChildrenDeprecatedData)); + #endif + + addTests({&SceneDataTest::fieldForFieldMissing, + &SceneDataTest::findFieldObjectOffsetInvalidObject, + + &SceneDataTest::releaseFieldData, + &SceneDataTest::releaseData}); +} + +using namespace Math::Literals; + +void SceneDataTest::mappingTypeSizeAlignment() { + CORRADE_COMPARE(sceneMappingTypeSize(SceneMappingType::UnsignedByte), 1); + CORRADE_COMPARE(sceneMappingTypeAlignment(SceneMappingType::UnsignedByte), 1); + CORRADE_COMPARE(sceneMappingTypeSize(SceneMappingType::UnsignedShort), 2); + CORRADE_COMPARE(sceneMappingTypeAlignment(SceneMappingType::UnsignedShort), 2); + CORRADE_COMPARE(sceneMappingTypeSize(SceneMappingType::UnsignedInt), 4); + CORRADE_COMPARE(sceneMappingTypeAlignment(SceneMappingType::UnsignedInt), 4); + CORRADE_COMPARE(sceneMappingTypeSize(SceneMappingType::UnsignedLong), 8); + CORRADE_COMPARE(sceneMappingTypeAlignment(SceneMappingType::UnsignedLong), 8); +} + +void SceneDataTest::mappingTypeSizeAlignmentInvalid() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + sceneMappingTypeSize(SceneMappingType{}); + sceneMappingTypeAlignment(SceneMappingType{}); + sceneMappingTypeSize(SceneMappingType(0x73)); + sceneMappingTypeAlignment(SceneMappingType(0x73)); + + CORRADE_COMPARE(out.str(), + "Trade::sceneMappingTypeSize(): invalid type Trade::SceneMappingType(0x0)\n" + "Trade::sceneMappingTypeAlignment(): invalid type Trade::SceneMappingType(0x0)\n" + "Trade::sceneMappingTypeSize(): invalid type Trade::SceneMappingType(0x73)\n" + "Trade::sceneMappingTypeAlignment(): invalid type Trade::SceneMappingType(0x73)\n"); +} + +void SceneDataTest::debugMappingType() { + std::ostringstream out; + Debug{&out} << SceneMappingType::UnsignedLong << SceneMappingType(0x73); + CORRADE_COMPARE(out.str(), "Trade::SceneMappingType::UnsignedLong Trade::SceneMappingType(0x73)\n"); +} + +void SceneDataTest::customFieldName() { + CORRADE_VERIFY(!isSceneFieldCustom(SceneField::Rotation)); + CORRADE_VERIFY(!isSceneFieldCustom(SceneField(0x0fffffffu))); + CORRADE_VERIFY(isSceneFieldCustom(SceneField::Custom)); + CORRADE_VERIFY(isSceneFieldCustom(SceneField(0x80000000u))); + + CORRADE_COMPARE(UnsignedInt(sceneFieldCustom(0)), 0x80000000u); + CORRADE_COMPARE(UnsignedInt(sceneFieldCustom(0xabcd)), 0x8000abcdu); + CORRADE_COMPARE(UnsignedInt(sceneFieldCustom(0x7fffffff)), 0xffffffffu); + + CORRADE_COMPARE(sceneFieldCustom(SceneField::Custom), 0); + CORRADE_COMPARE(sceneFieldCustom(SceneField(0x8000abcdu)), 0xabcd); + CORRADE_COMPARE(sceneFieldCustom(SceneField(0xffffffffu)), 0x7fffffffu); + + constexpr bool is = isSceneFieldCustom(SceneField(0x8000abcdu)); + CORRADE_VERIFY(is); + constexpr SceneField a = sceneFieldCustom(0xabcd); + CORRADE_COMPARE(UnsignedInt(a), 0x8000abcdu); + constexpr UnsignedInt b = sceneFieldCustom(a); + CORRADE_COMPARE(b, 0xabcd); +} + +void SceneDataTest::customFieldNameTooLarge() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + sceneFieldCustom(1u << 31); + CORRADE_COMPARE(out.str(), "Trade::sceneFieldCustom(): index 2147483648 too large\n"); +} + +void SceneDataTest::customFieldNameNotCustom() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + sceneFieldCustom(SceneField::Transformation); + CORRADE_COMPARE(out.str(), "Trade::sceneFieldCustom(): Trade::SceneField::Transformation is not custom\n"); +} + +void SceneDataTest::debugFieldName() { + std::ostringstream out; + Debug{&out} << SceneField::Transformation << sceneFieldCustom(73) << SceneField(0xdeadda7); + CORRADE_COMPARE(out.str(), "Trade::SceneField::Transformation Trade::SceneField::Custom(73) Trade::SceneField(0xdeadda7)\n"); +} + +void SceneDataTest::fieldTypeSizeAlignment() { + /* Test at least one of every size */ + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Byte), sizeof(Byte)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Degh), sizeof(Degh)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Vector3ub), sizeof(Vector3ub)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Range1Dh), sizeof(Range1Dh)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Vector3s), sizeof(Vector3s)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Long), sizeof(Long)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix3x2h), sizeof(Matrix3x2h)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix4x2h), sizeof(Matrix4x2h)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix3x3h), sizeof(Matrix3x3h)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Range3Di), sizeof(Range3Di)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::DualQuaternion), sizeof(DualQuaternion)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix3x3), sizeof(Matrix3x3)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix3x2d), sizeof(Matrix3x2d)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::DualQuaterniond), sizeof(DualQuaterniond)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix3x3d), sizeof(Matrix3x3d)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix3x4d), sizeof(Matrix3x4d)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix4x4d), sizeof(Matrix4x4d)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Pointer), sizeof(const void*)); + + /* Test at least one of every alignment */ + CORRADE_COMPARE(sceneFieldTypeAlignment(SceneFieldType::Vector3ub), alignof(UnsignedByte)); + CORRADE_COMPARE(sceneFieldTypeAlignment(SceneFieldType::Matrix3x3h), alignof(Half)); + CORRADE_COMPARE(sceneFieldTypeAlignment(SceneFieldType::Range3Di), alignof(UnsignedInt)); + CORRADE_COMPARE(sceneFieldTypeAlignment(SceneFieldType::DualComplexd), alignof(Double)); + CORRADE_COMPARE(sceneFieldTypeAlignment(SceneFieldType::Pointer), alignof(const void*)); +} + +void SceneDataTest::fieldTypeSizeAlignmentInvalid() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + sceneFieldTypeSize(SceneFieldType{}); + sceneFieldTypeAlignment(SceneFieldType{}); + sceneFieldTypeSize(SceneFieldType(0xdead)); + sceneFieldTypeAlignment(SceneFieldType(0xdead)); + + CORRADE_COMPARE(out.str(), + "Trade::sceneFieldTypeSize(): invalid type Trade::SceneFieldType(0x0)\n" + "Trade::sceneFieldTypeAlignment(): invalid type Trade::SceneFieldType(0x0)\n" + "Trade::sceneFieldTypeSize(): invalid type Trade::SceneFieldType(0xdead)\n" + "Trade::sceneFieldTypeAlignment(): invalid type Trade::SceneFieldType(0xdead)\n"); +} + +void SceneDataTest::debugFieldType() { + std::ostringstream out; + Debug{&out} << SceneFieldType::Matrix3x4h << SceneFieldType(0xdead); + CORRADE_COMPARE(out.str(), "Trade::SceneFieldType::Matrix3x4h Trade::SceneFieldType(0xdead)\n"); +} + +void SceneDataTest::debugFieldFlag() { + std::ostringstream out; + + Debug(&out) << SceneFieldFlag::OffsetOnly << SceneFieldFlag(0xbe); + CORRADE_COMPARE(out.str(), "Trade::SceneFieldFlag::OffsetOnly Trade::SceneFieldFlag(0xbe)\n"); +} + +void SceneDataTest::debugFieldFlags() { + std::ostringstream out; + + Debug{&out} << (SceneFieldFlag::OffsetOnly|SceneFieldFlag(0xe0)) << SceneFieldFlags{}; + CORRADE_COMPARE(out.str(), "Trade::SceneFieldFlag::OffsetOnly|Trade::SceneFieldFlag(0xe0) Trade::SceneFieldFlags{}\n"); +} + +void SceneDataTest::debugFieldFlagsSupersets() { + /* ImplicitMapping is a superset of OrderedMapping, so only one should be + printed */ + { + std::ostringstream out; + Debug{&out} << (SceneFieldFlag::ImplicitMapping|SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(out.str(), "Trade::SceneFieldFlag::ImplicitMapping\n"); + } +} + +const UnsignedShort RotationMapping2D[3] { + 17, + 35, + 98 +}; +constexpr Complexd RotationField2D[3] { + Complexd{Constantsd::sqrtHalf(), Constantsd::sqrtHalf()}, /* 45° */ + Complexd{1.0, 0.0}, /* 0° */ + Complexd{0.0, 1.0}, /* 90° */ +}; + +void SceneDataTest::constructField() { + const UnsignedShort rotationMappingData[3]{}; + const Complexd rotationFieldData[3]; + + SceneFieldData rotations{SceneField::Rotation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData), SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(rotations.flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(rotations.name(), SceneField::Rotation); + CORRADE_COMPARE(rotations.size(), 3); + CORRADE_COMPARE(rotations.mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(rotations.mappingData().size(), 3); + CORRADE_COMPARE(rotations.mappingData().stride(), sizeof(UnsignedShort)); + CORRADE_VERIFY(rotations.mappingData().data() == rotationMappingData); + CORRADE_COMPARE(rotations.fieldType(), SceneFieldType::Complexd); + CORRADE_COMPARE(rotations.fieldArraySize(), 0); + CORRADE_COMPARE(rotations.fieldData().size(), 3); + CORRADE_COMPARE(rotations.fieldData().stride(), sizeof(Complexd)); + CORRADE_VERIFY(rotations.fieldData().data() == rotationFieldData); + + /* This is allowed too for simplicity, the parameter has to be large enough + tho */ + char someArray[3*sizeof(Complexd)]; + CORRADE_COMPARE(rotations.fieldData(someArray).size(), 3); + CORRADE_COMPARE(rotations.fieldData(someArray).stride(), sizeof(Complexd)); + CORRADE_VERIFY(rotations.fieldData(someArray).data() == rotationFieldData); + CORRADE_COMPARE(rotations.mappingData(someArray).size(), 3); + CORRADE_COMPARE(rotations.mappingData(someArray).stride(), sizeof(UnsignedShort)); + CORRADE_VERIFY(rotations.mappingData(someArray).data() == rotationMappingData); + + #ifndef CORRADE_MSVC2015_COMPATIBILITY /* Won't bother anymore */ + constexpr SceneFieldData crotations{SceneField::Rotation, Containers::arrayView(RotationMapping2D), Containers::arrayView(RotationField2D), SceneFieldFlag::ImplicitMapping}; + constexpr SceneField name = crotations.name(); + constexpr SceneFieldFlags flags = crotations.flags(); + constexpr SceneMappingType mappingType = crotations.mappingType(); + constexpr Containers::StridedArrayView1D mappingData = crotations.mappingData(); + constexpr SceneFieldType fieldType = crotations.fieldType(); + constexpr UnsignedShort fieldArraySize = crotations.fieldArraySize(); + constexpr Containers::StridedArrayView1D fieldData = crotations.fieldData(); + CORRADE_COMPARE(name, SceneField::Rotation); + CORRADE_COMPARE(flags, SceneFieldFlag::ImplicitMapping); + CORRADE_COMPARE(mappingType, SceneMappingType::UnsignedShort); + CORRADE_COMPARE(mappingData.size(), 3); + CORRADE_COMPARE(mappingData.stride(), sizeof(UnsignedShort)); + CORRADE_COMPARE(mappingData.data(), RotationMapping2D); + CORRADE_COMPARE(fieldType, SceneFieldType::Complexd); + CORRADE_COMPARE(fieldArraySize, 0); + CORRADE_COMPARE(fieldData.size(), 3); + CORRADE_COMPARE(fieldData.stride(), sizeof(Complexd)); + CORRADE_COMPARE(fieldData.data(), RotationField2D); + #endif +} + +void SceneDataTest::constructFieldDefault() { + SceneFieldData data; + CORRADE_COMPARE(data.name(), SceneField{}); + CORRADE_COMPARE(data.fieldType(), SceneFieldType{}); + CORRADE_COMPARE(data.mappingType(), SceneMappingType{}); + + constexpr SceneFieldData cdata; + CORRADE_COMPARE(cdata.name(), SceneField{}); + CORRADE_COMPARE(cdata.fieldType(), SceneFieldType{}); + CORRADE_COMPARE(cdata.mappingType(), SceneMappingType{}); +} + +void SceneDataTest::constructFieldCustom() { + /* Verifying it doesn't hit any assertion about disallowed type for given + attribute */ + + const UnsignedByte rangeMappingData[3]{}; + const Range2Dh rangeFieldData[3]; + SceneFieldData ranges{sceneFieldCustom(13), Containers::arrayView(rangeMappingData), Containers::arrayView(rangeFieldData)}; + CORRADE_COMPARE(ranges.name(), sceneFieldCustom(13)); + CORRADE_COMPARE(ranges.mappingType(), SceneMappingType::UnsignedByte); + CORRADE_VERIFY(ranges.mappingData().data() == rangeMappingData); + CORRADE_COMPARE(ranges.fieldType(), SceneFieldType::Range2Dh); + CORRADE_VERIFY(ranges.fieldData().data() == rangeFieldData); +} + +void SceneDataTest::constructField2D() { + char rotationMappingData[6*sizeof(UnsignedShort)]; + char rotationFieldData[6*sizeof(Complexd)]; + auto rotationMappingView = Containers::StridedArrayView2D{rotationMappingData, {6, sizeof(UnsignedShort)}}.every(2); + auto rotationFieldView = Containers::StridedArrayView2D{rotationFieldData, {6, sizeof(Complexd)}}.every(2); + + SceneFieldData rotations{SceneField::Rotation, rotationMappingView, SceneFieldType::Complexd, rotationFieldView, SceneFieldFlag::ImplicitMapping}; + CORRADE_COMPARE(rotations.flags(), SceneFieldFlag::ImplicitMapping); + CORRADE_COMPARE(rotations.name(), SceneField::Rotation); + CORRADE_COMPARE(rotations.size(), 3); + CORRADE_COMPARE(rotations.mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(rotations.mappingData().size(), 3); + CORRADE_COMPARE(rotations.mappingData().stride(), 2*sizeof(UnsignedShort)); + CORRADE_COMPARE(rotations.mappingData().data(), rotationMappingView.data()); + CORRADE_COMPARE(rotations.fieldType(), SceneFieldType::Complexd); + CORRADE_COMPARE(rotations.fieldArraySize(), 0); + CORRADE_COMPARE(rotations.fieldData().size(), 3); + CORRADE_COMPARE(rotations.fieldData().stride(), 2*sizeof(Complexd)); + CORRADE_COMPARE(rotations.fieldData().data(), rotationFieldView.data()); +} + +void SceneDataTest::constructFieldTypeErased() { + const UnsignedLong scalingMappingData[3]{}; + const Vector3 scalingFieldData[3]; + SceneFieldData scalings{SceneField::Scaling, SceneMappingType::UnsignedLong, Containers::arrayCast(Containers::stridedArrayView(scalingMappingData)), SceneFieldType::Vector3, Containers::arrayCast(Containers::stridedArrayView(scalingFieldData)), SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(scalings.flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(scalings.name(), SceneField::Scaling); + CORRADE_COMPARE(scalings.size(), 3); + CORRADE_COMPARE(scalings.mappingType(), SceneMappingType::UnsignedLong); + CORRADE_COMPARE(scalings.mappingData().size(), 3); + CORRADE_COMPARE(scalings.mappingData().stride(), sizeof(UnsignedLong)); + CORRADE_COMPARE(scalings.mappingData().data(), scalingMappingData); + CORRADE_COMPARE(scalings.fieldType(), SceneFieldType::Vector3); + CORRADE_COMPARE(scalings.fieldArraySize(), 0); + CORRADE_COMPARE(scalings.fieldData().size(), 3); + CORRADE_COMPARE(scalings.fieldData().stride(), sizeof(Vector3)); + CORRADE_COMPARE(scalings.fieldData().data(), scalingFieldData); +} + +void SceneDataTest::constructFieldNonOwningArray() { + const SceneFieldData data[3]; + Containers::Array array = sceneFieldDataNonOwningArray(data); + CORRADE_COMPARE(array.size(), 3); + CORRADE_COMPARE(static_cast(array.data()), data); +} + +void SceneDataTest::constructFieldOffsetOnly() { + struct Data { + Byte parent; + UnsignedShort object; + Vector2 translation; + } data[] { + {0, 2, {2.0f, 3.0f}}, + {0, 15, {67.0f, -1.1f}} + }; + + SceneFieldData a{SceneField::Translation, 2, SceneMappingType::UnsignedShort, offsetof(Data, object), sizeof(Data), SceneFieldType::Vector2, offsetof(Data, translation), sizeof(Data), SceneFieldFlag::ImplicitMapping}; + CORRADE_COMPARE(a.flags(), SceneFieldFlag::OffsetOnly|SceneFieldFlag::ImplicitMapping); + CORRADE_COMPARE(a.name(), SceneField::Translation); + CORRADE_COMPARE(a.size(), 2); + CORRADE_COMPARE(a.mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(a.mappingData(data).size(), 2); + CORRADE_COMPARE(a.mappingData(data).stride(), sizeof(Data)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.mappingData(data)), + Containers::arrayView({2, 15}), + TestSuite::Compare::Container); + CORRADE_COMPARE(a.fieldType(), SceneFieldType::Vector2); + CORRADE_COMPARE(a.fieldArraySize(), 0); + CORRADE_COMPARE(a.fieldData(data).size(), 2); + CORRADE_COMPARE(a.fieldData(data).stride(), sizeof(Data)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.fieldData(data)), + Containers::arrayView({{2.0f, 3.0f}, {67.0f, -1.1f}}), + TestSuite::Compare::Container); +} + +constexpr UnsignedByte ArrayOffsetMappingData[3]{}; +constexpr Int ArrayOffsetFieldData[3*4]{}; + +void SceneDataTest::constructFieldArray() { + UnsignedByte offsetMappingData[3]; + Int offsetFieldData[3*4]; + SceneFieldData data{sceneFieldCustom(34), Containers::arrayView(offsetMappingData), Containers::StridedArrayView2D{offsetFieldData, {3, 4}}, SceneFieldFlag::ImplicitMapping}; + CORRADE_COMPARE(data.flags(), SceneFieldFlag::ImplicitMapping); + CORRADE_COMPARE(data.name(), sceneFieldCustom(34)); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.mappingType(), SceneMappingType::UnsignedByte); + CORRADE_COMPARE(data.mappingData().size(), 3); + CORRADE_COMPARE(data.mappingData().stride(), sizeof(UnsignedByte)); + CORRADE_VERIFY(data.mappingData().data() == offsetMappingData); + CORRADE_COMPARE(data.fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(data.fieldArraySize(), 4); + CORRADE_COMPARE(data.fieldData().size(), 3); + CORRADE_COMPARE(data.fieldData().stride(), 4*sizeof(Int)); + CORRADE_VERIFY(data.fieldData().data() == offsetFieldData); + + constexpr SceneFieldData cdata{sceneFieldCustom(34), Containers::arrayView(ArrayOffsetMappingData), Containers::StridedArrayView2D{ArrayOffsetFieldData, {3, 4}}, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(cdata.flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(cdata.name(), sceneFieldCustom(34)); + CORRADE_COMPARE(cdata.size(), 3); + CORRADE_COMPARE(cdata.mappingType(), SceneMappingType::UnsignedByte); + CORRADE_COMPARE(cdata.mappingData().size(), 3); + CORRADE_COMPARE(cdata.mappingData().stride(), sizeof(UnsignedByte)); + CORRADE_VERIFY(cdata.mappingData().data() == ArrayOffsetMappingData); + CORRADE_COMPARE(cdata.fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(cdata.fieldArraySize(), 4); + CORRADE_COMPARE(cdata.fieldData().size(), 3); + CORRADE_COMPARE(cdata.fieldData().stride(), 4*sizeof(Int)); + CORRADE_VERIFY(cdata.fieldData().data() == ArrayOffsetFieldData); +} + +void SceneDataTest::constructFieldArray2D() { + char offsetMappingData[3*sizeof(UnsignedByte)]; + char offsetFieldData[3*4*sizeof(Int)]; + SceneFieldData data{sceneFieldCustom(34), Containers::StridedArrayView2D{offsetMappingData, {3, sizeof(UnsignedByte)}}, SceneFieldType::Int, Containers::StridedArrayView2D{offsetFieldData, {3, 4*sizeof(Int)}}, 4, SceneFieldFlag::ImplicitMapping}; + CORRADE_COMPARE(data.flags(), SceneFieldFlag::ImplicitMapping); + CORRADE_COMPARE(data.name(), sceneFieldCustom(34)); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.mappingType(), SceneMappingType::UnsignedByte); + CORRADE_COMPARE(data.mappingData().size(), 3); + CORRADE_COMPARE(data.mappingData().stride(), sizeof(UnsignedByte)); + CORRADE_VERIFY(data.mappingData().data() == offsetMappingData); + CORRADE_COMPARE(data.fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(data.fieldArraySize(), 4); + CORRADE_COMPARE(data.fieldData().size(), 3); + CORRADE_COMPARE(data.fieldData().stride(), 4*sizeof(Int)); + CORRADE_VERIFY(data.fieldData().data() == offsetFieldData); +} + +void SceneDataTest::constructFieldArrayTypeErased() { + UnsignedByte offsetMappingData[3]; + Int offsetFieldData[3*4]; + Containers::StridedArrayView1D offset{offsetFieldData, 3, 4*sizeof(Int)}; + SceneFieldData data{sceneFieldCustom(34), SceneMappingType::UnsignedByte, Containers::arrayCast(Containers::stridedArrayView(offsetMappingData)), SceneFieldType::Int, Containers::arrayCast(offset), 4, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(data.flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(data.name(), sceneFieldCustom(34)); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(data.mappingType(), SceneMappingType::UnsignedByte); + CORRADE_COMPARE(data.mappingData().size(), 3); + CORRADE_COMPARE(data.mappingData().stride(), sizeof(UnsignedByte)); + CORRADE_VERIFY(data.mappingData().data() == offsetMappingData); + CORRADE_COMPARE(data.fieldArraySize(), 4); + CORRADE_COMPARE(data.fieldData().size(), 3); + CORRADE_COMPARE(data.fieldData().stride(), 4*sizeof(Int)); + CORRADE_VERIFY(data.fieldData().data() == offsetFieldData); +} + +void SceneDataTest::constructFieldArrayOffsetOnly() { + struct Data { + Byte parent; + UnsignedByte object; + Int offset[4]; + }; + + SceneFieldData data{sceneFieldCustom(34), 3, SceneMappingType::UnsignedByte, offsetof(Data, object), sizeof(Data), SceneFieldType::Int, offsetof(Data, offset), sizeof(Data), 4, SceneFieldFlag::ImplicitMapping}; + CORRADE_COMPARE(data.flags(), SceneFieldFlag::OffsetOnly|SceneFieldFlag::ImplicitMapping); + CORRADE_COMPARE(data.name(), sceneFieldCustom(34)); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.mappingType(), SceneMappingType::UnsignedByte); + CORRADE_COMPARE(data.fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(data.fieldArraySize(), 4); + + Data actual[3]; + CORRADE_COMPARE(data.fieldData(actual).size(), 3); + CORRADE_COMPARE(data.fieldData(actual).stride(), sizeof(Data)); + CORRADE_VERIFY(data.fieldData(actual).data() == &actual[0].offset); + CORRADE_COMPARE(data.mappingData(actual).size(), 3); + CORRADE_COMPARE(data.mappingData(actual).stride(), sizeof(Data)); + CORRADE_VERIFY(data.mappingData(actual).data() == &actual[0].object); + + constexpr SceneFieldData cdata{sceneFieldCustom(34), 3, SceneMappingType::UnsignedByte, offsetof(Data, object), sizeof(Data), SceneFieldType::Int, offsetof(Data, offset), sizeof(Data), 4, SceneFieldFlag::OrderedMapping}; + CORRADE_COMPARE(cdata.flags(), SceneFieldFlag::OffsetOnly|SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(cdata.name(), sceneFieldCustom(34)); + CORRADE_COMPARE(cdata.size(), 3); + CORRADE_COMPARE(cdata.mappingType(), SceneMappingType::UnsignedByte); + CORRADE_COMPARE(cdata.fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(cdata.fieldArraySize(), 4); +} + +void SceneDataTest::constructFieldInconsistentViewSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + const UnsignedShort rotationMappingData[3]{}; + const Complexd rotationFieldData[2]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Rotation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData)}; + CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: expected mapping and field view to have the same size but got 3 and 2\n"); +} + +void SceneDataTest::constructFieldWrongType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + const UnsignedShort rotationMappingData[3]{}; + const Quaternion rotationFieldData[3]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Transformation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData)}; + SceneFieldData{SceneField::Transformation, 3, SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), SceneFieldType::Quaternion, 0, sizeof(Quaternion)}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: Trade::SceneFieldType::Quaternion is not a valid type for Trade::SceneField::Transformation\n" + "Trade::SceneFieldData: Trade::SceneFieldType::Quaternion is not a valid type for Trade::SceneField::Transformation\n"); +} + +void SceneDataTest::constructFieldTooLargeMappingStride() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + UnsignedInt enough[2]; + char toomuch[2*(32768 + sizeof(UnsignedInt))]; + + /* These should be fine */ + SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32767}, SceneFieldType::UnsignedInt, enough}; + SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}.flipped<0>(), SceneFieldType::UnsignedInt, enough}; + SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 0, 32767, SceneFieldType::UnsignedInt, 0, 4}; + SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 65536, -32768, SceneFieldType::UnsignedInt, 0, 4}; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}, SceneFieldType::UnsignedInt, enough}; + SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32769}.flipped<0>(), SceneFieldType::UnsignedInt, enough}; + SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 0, 32768, SceneFieldType::UnsignedInt, 0, 4}; + SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 65538, -32769, SceneFieldType::UnsignedInt, 0, 4}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits, but got 32768\n" + "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits, but got -32769\n" + "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits, but got 32768\n" + "Trade::SceneFieldData: expected mapping view stride to fit into 16 bits, but got -32769\n"); +} + +void SceneDataTest::constructFieldTooLargeFieldStride() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + UnsignedInt enough[2]; + char toomuch[2*(32768 + sizeof(UnsignedInt))]; + + /* These should be fine */ + SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, enough, SceneFieldType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32767}}; + SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, enough, SceneFieldType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}.flipped<0>()}; + SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 0, 32767}; + SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 65536, -32768}; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, enough, SceneFieldType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}}; + SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, enough, SceneFieldType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32769}.flipped<0>()}; + SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 0, 32768}; + SceneFieldData{SceneField::Mesh, 2, SceneMappingType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 65538, -32769}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got 32768\n" + "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got -32769\n" + "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got 32768\n" + "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got -32769\n"); +} + +void SceneDataTest::constructFieldOffsetOnlyNotAllowed() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + const UnsignedShort rotationMappingData[3]{}; + const Quaternion rotationFieldData[3]; + + /* This one is fine */ + SceneFieldData{SceneField::Rotation, 3, SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), SceneFieldType::Quaternion, 0, sizeof(Quaternion), SceneFieldFlag::OffsetOnly}; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Rotation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData), SceneFieldFlag::OffsetOnly}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: can't pass Trade::SceneFieldFlag::OffsetOnly for a view\n"); +} + +void SceneDataTest::constructFieldWrongDataAccess() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + const UnsignedShort rotationMappingData[3]{}; + const Quaternion rotationFieldData[3]; + SceneFieldData a{SceneField::Rotation, Containers::arrayView(rotationMappingData), Containers::arrayView(rotationFieldData)}; + SceneFieldData b{SceneField::Rotation, 3, SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), SceneFieldType::Quaternion, 0, sizeof(Quaternion)}; + CORRADE_COMPARE(a.flags(), SceneFieldFlags{}); + CORRADE_COMPARE(b.flags(), SceneFieldFlag::OffsetOnly); + + a.mappingData(rotationMappingData); /* This is fine, no asserts */ + a.fieldData(rotationFieldData); + + std::ostringstream out; + Error redirectError{&out}; + b.mappingData(); + b.fieldData(); + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData::mappingData(): the field is offset-only, supply a data array\n" + "Trade::SceneFieldData::fieldData(): the field is offset-only, supply a data array\n"); +} + +void SceneDataTest::constructField2DWrongSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + char rotationMappingData[5*sizeof(UnsignedInt)]; + char rotationFieldData[5*sizeof(Complex)]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Rotation, + Containers::StridedArrayView2D{rotationMappingData, {4, 5}}.every(2), + SceneFieldType::Complex, + Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}.every(2)}; + SceneFieldData{SceneField::Translation, + Containers::StridedArrayView2D{rotationMappingData, {4, sizeof(UnsignedInt)}}.every(2), + SceneFieldType::Vector3, + Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}.every(2)}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: expected second mapping view dimension size 1, 2, 4 or 8 but got 5\n" + "Trade::SceneFieldData: second field view dimension size 8 doesn't match Trade::SceneFieldType::Vector3\n"); +} + +void SceneDataTest::constructField2DNonContiguous() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + char rotationMappingData[8*sizeof(UnsignedInt)]; + char rotationFieldData[8*sizeof(Complex)]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Rotation, + Containers::StridedArrayView2D{rotationMappingData, {4, 2*sizeof(UnsignedInt)}}.every({1, 2}), + SceneFieldType::Complex, + Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}}; + SceneFieldData{SceneField::Rotation, + Containers::StridedArrayView2D{rotationMappingData, {4, sizeof(UnsignedInt)}}, + SceneFieldType::Complex, + Containers::StridedArrayView2D{rotationFieldData, {4, 2*sizeof(Complex)}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: second mapping view dimension is not contiguous\n" + "Trade::SceneFieldData: second field view dimension is not contiguous\n"); +} + +void SceneDataTest::constructFieldArrayNonContiguous() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + UnsignedByte offsetMappingData[3]; + Int offsetFieldData[3*4]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData data{sceneFieldCustom(34), Containers::arrayView(offsetMappingData), Containers::StridedArrayView2D{offsetFieldData, {3, 4}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: second field view dimension is not contiguous\n"); +} + +void SceneDataTest::constructFieldArrayNotAllowed() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + UnsignedShort rotationMappingData[3]{}; + Quaternion rotationFieldData[3]; + Containers::ArrayView rotationMapping = rotationMappingData; + Containers::ArrayView rotationField = rotationFieldData; + Containers::StridedArrayView2D rotationFields2D{rotationFieldData, {3, 3}, {0, sizeof(Quaternion)}}; + auto rotationMappingChar = Containers::arrayCast<2, const char>(rotationMapping); + auto rotationField2DChar = Containers::arrayCast<2, const char>(rotationFields2D); + + /* This is all fine */ + SceneFieldData{SceneField::Rotation, + SceneMappingType::UnsignedShort, rotationMapping, + SceneFieldType::Quaternion, rotationField, 0}; + SceneFieldData{SceneField::Rotation, 3, + SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), + SceneFieldType::Quaternion, 0, sizeof(Quaternion), 0}; + SceneFieldData{sceneFieldCustom(37), + rotationMapping, + rotationFields2D}; + SceneFieldData{sceneFieldCustom(37), + rotationMappingChar, + SceneFieldType::Quaternion, rotationField2DChar, 3}; + SceneFieldData{sceneFieldCustom(37), 3, + SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), + SceneFieldType::Quaternion, 0, sizeof(Quaternion), 3}; + + /* This is not */ + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Rotation, + SceneMappingType::UnsignedShort, rotationMapping, + SceneFieldType::Quaternion, rotationField, 3}; + SceneFieldData{SceneField::Rotation, 3, + SceneMappingType::UnsignedShort, 0, sizeof(UnsignedShort), + SceneFieldType::Quaternion, 0, sizeof(Quaternion), 3}; + SceneFieldData{SceneField::Rotation, + rotationMapping, + rotationFields2D}; + SceneFieldData{SceneField::Rotation, + rotationMappingChar, + SceneFieldType::Quaternion, rotationField2DChar, 3}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: Trade::SceneField::Rotation can't be an array field\n" + "Trade::SceneFieldData: Trade::SceneField::Rotation can't be an array field\n" + "Trade::SceneFieldData: Trade::SceneField::Rotation can't be an array field\n" + "Trade::SceneFieldData: Trade::SceneField::Rotation can't be an array field\n"); +} + +void SceneDataTest::constructFieldArray2DWrongSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + char rotationMappingData[4*sizeof(UnsignedInt)]; + char rotationFieldData[4*sizeof(Complex)]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{sceneFieldCustom(37), + Containers::StridedArrayView2D{rotationMappingData, {4, sizeof(UnsignedInt)}}.every(2), + SceneFieldType::Int, + Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}.every(2), 3}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: second field view dimension size 8 doesn't match Trade::SceneFieldType::Int and field array size 3\n"); +} + +void SceneDataTest::constructFieldArray2DNonContiguous() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + char offsetMappingData[18*sizeof(UnsignedInt)]; + char offsetFieldData[18*sizeof(Int)]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{sceneFieldCustom(37), + Containers::StridedArrayView2D{offsetMappingData, {3, 2*sizeof(UnsignedInt)}}.every({1, 2}), + SceneFieldType::Int, + Containers::StridedArrayView2D{offsetFieldData, {3, 3*sizeof(Int)}}, 3}; + SceneFieldData{sceneFieldCustom(37), + Containers::StridedArrayView2D{offsetMappingData, {3, sizeof(UnsignedInt)}}, + SceneFieldType::Int, + Containers::StridedArrayView2D{offsetFieldData, {3, 6*sizeof(Int)}}.every({1, 2}), 3}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: second mapping view dimension is not contiguous\n" + "Trade::SceneFieldData: second field view dimension is not contiguous\n"); } void SceneDataTest::construct() { - const int a{}; - const SceneData data{{0, 1, 4}, {2, 5}, &a}; + struct TransformParent { + UnsignedShort object; + Matrix4 transformation; + Int parent; + }; + + Containers::StridedArrayView1D transformsParentFieldMappingData; + Containers::StridedArrayView1D meshFieldData; + Containers::StridedArrayView1D radiusFieldData; + Containers::StridedArrayView1D materialMeshRadiusMappingData; + Containers::ArrayTuple data{ + {NoInit, 5, transformsParentFieldMappingData}, + {NoInit, 2, meshFieldData}, + {NoInit, 2, radiusFieldData}, + {NoInit, 2, materialMeshRadiusMappingData}, + }; + transformsParentFieldMappingData[0].object = 4; + transformsParentFieldMappingData[0].transformation = Matrix4::translation(Vector3::xAxis(5.0f)); + transformsParentFieldMappingData[0].parent = -1; + + transformsParentFieldMappingData[1].object = 2; + transformsParentFieldMappingData[1].transformation = Matrix4::translation(Vector3::yAxis(5.0f)); + transformsParentFieldMappingData[1].parent = 0; + + transformsParentFieldMappingData[2].object = 3; + transformsParentFieldMappingData[2].transformation = Matrix4::translation(Vector3::zAxis(5.0f)); + transformsParentFieldMappingData[2].parent = 2; + + transformsParentFieldMappingData[3].object = 0; + transformsParentFieldMappingData[3].transformation = Matrix4::translation(Vector3::yScale(5.0f)); + transformsParentFieldMappingData[3].parent = 1; + + transformsParentFieldMappingData[4].object = 1; + transformsParentFieldMappingData[4].transformation = Matrix4::translation(Vector3::zScale(5.0f)); + transformsParentFieldMappingData[4].parent = -1; + + meshFieldData[0] = 5; + radiusFieldData[0] = {37.5f, 1.5f}; + materialMeshRadiusMappingData[0] = 2; + + meshFieldData[1] = 7; + radiusFieldData[1] = {22.5f, 0.5f}; + materialMeshRadiusMappingData[1] = 6; + + int importerState; + SceneFieldData transformations{SceneField::Transformation, + transformsParentFieldMappingData.slice(&TransformParent::object), + transformsParentFieldMappingData.slice(&TransformParent::transformation)}; + /* Offset-only */ + SceneFieldData parents{SceneField::Parent, 5, + SceneMappingType::UnsignedShort, offsetof(TransformParent, object), sizeof(TransformParent), + SceneFieldType::Int, offsetof(TransformParent, parent), sizeof(TransformParent)}; + SceneFieldData meshes{SceneField::Mesh, + materialMeshRadiusMappingData, + meshFieldData, SceneFieldFlag::OrderedMapping}; + /* Custom & array */ + SceneFieldData radiuses{sceneFieldCustom(37), + materialMeshRadiusMappingData, + Containers::arrayCast<2, Float>(radiusFieldData), SceneFieldFlag::OrderedMapping}; + SceneData scene{SceneMappingType::UnsignedShort, 8, std::move(data), { + transformations, parents, meshes, radiuses + }, &importerState}; + + /* Basics */ + CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_VERIFY(!scene.fieldData().empty()); + CORRADE_COMPARE(static_cast(scene.data()), transformsParentFieldMappingData.data()); + CORRADE_COMPARE(static_cast(scene.mutableData()), transformsParentFieldMappingData.data()); + CORRADE_COMPARE(scene.mappingBound(), 8); + CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(scene.fieldCount(), 4); + CORRADE_COMPARE(scene.importerState(), &importerState); + + /* is2D() / is3D() exhaustively tested in transformations*DAsArray[TRS]() + and constructZeroFields() */ + + /* Field property access by ID */ + CORRADE_COMPARE(scene.fieldName(0), SceneField::Transformation); + CORRADE_COMPARE(scene.fieldName(1), SceneField::Parent); + CORRADE_COMPARE(scene.fieldName(2), SceneField::Mesh); + CORRADE_COMPARE(scene.fieldName(3), sceneFieldCustom(37)); + CORRADE_COMPARE(scene.fieldFlags(0), SceneFieldFlags{}); + CORRADE_COMPARE(scene.fieldFlags(1), SceneFieldFlag::OffsetOnly); + CORRADE_COMPARE(scene.fieldFlags(2), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(scene.fieldFlags(3), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(scene.fieldType(0), SceneFieldType::Matrix4x4); + CORRADE_COMPARE(scene.fieldType(1), SceneFieldType::Int); + CORRADE_COMPARE(scene.fieldType(2), SceneFieldType::UnsignedByte); + CORRADE_COMPARE(scene.fieldType(3), SceneFieldType::Float); + CORRADE_COMPARE(scene.fieldSize(0), 5); + CORRADE_COMPARE(scene.fieldSize(1), 5); + CORRADE_COMPARE(scene.fieldSize(2), 2); + CORRADE_COMPARE(scene.fieldSize(3), 2); + CORRADE_COMPARE(scene.fieldArraySize(0), 0); + CORRADE_COMPARE(scene.fieldArraySize(1), 0); + CORRADE_COMPARE(scene.fieldArraySize(2), 0); + CORRADE_COMPARE(scene.fieldArraySize(3), 2); + + /* Raw field data access by ID */ + CORRADE_COMPARE(scene.fieldData(2).name(), SceneField::Mesh); + CORRADE_COMPARE(scene.fieldData(2).size(), 2); + CORRADE_COMPARE(scene.fieldData(2).mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(2).mappingData())[1], 6); + CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(2).fieldData())[1], 7); + CORRADE_COMPARE(scene.fieldData(2).flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(scene.fieldData(2).fieldType(), SceneFieldType::UnsignedByte); + CORRADE_COMPARE(scene.fieldData(2).fieldArraySize(), 0); + /* Offset-only */ + CORRADE_COMPARE(scene.fieldData(1).name(), SceneField::Parent); + CORRADE_COMPARE(scene.fieldData(1).size(), 5); + CORRADE_COMPARE(scene.fieldData(1).mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(scene.fieldData(1).flags(), SceneFieldFlags{}); + CORRADE_COMPARE(scene.fieldData(1).fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(scene.fieldData(1).fieldArraySize(), 0); + CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(1).mappingData())[4], 1); + CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(1).fieldData())[4], -1); + /* Array */ + CORRADE_COMPARE(scene.fieldData(3).name(), sceneFieldCustom(37)); + CORRADE_COMPARE(scene.fieldData(3).size(), 2); + CORRADE_COMPARE(scene.fieldData(3).mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(scene.fieldData(3).flags(), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(scene.fieldData(3).fieldType(), SceneFieldType::Float); + CORRADE_COMPARE(scene.fieldData(3).fieldArraySize(), 2); + CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(3).mappingData())[0], 2); + CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(3).fieldData())[0], (Vector2{37.5f, 1.5f})); + + /* Typeless mapping access by ID with a cast later */ + CORRADE_COMPARE(scene.mapping(0).size()[0], 5); + CORRADE_COMPARE(scene.mapping(1).size()[0], 5); + CORRADE_COMPARE(scene.mapping(2).size()[0], 2); + CORRADE_COMPARE(scene.mapping(3).size()[0], 2); + CORRADE_COMPARE(scene.mutableMapping(0).size()[0], 5); + CORRADE_COMPARE(scene.mutableMapping(1).size()[0], 5); + CORRADE_COMPARE(scene.mutableMapping(2).size()[0], 2); + CORRADE_COMPARE(scene.mutableMapping(3).size()[0], 2); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.mapping(0))[2]), 3); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.mapping(1))[4]), 1); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.mapping(2))[1]), 6); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.mapping(3))[0]), 2); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableMapping(0))[2]), 3); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableMapping(1))[4]), 1); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableMapping(2))[1]), 6); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableMapping(3))[0]), 2); + + /* Typeless field access by ID with a cast later */ + CORRADE_COMPARE(scene.field(0).size()[0], 5); + CORRADE_COMPARE(scene.field(1).size()[0], 5); + CORRADE_COMPARE(scene.field(2).size()[0], 2); + CORRADE_COMPARE(scene.field(3).size()[0], 2); + CORRADE_COMPARE(scene.mutableField(0).size()[0], 5); + CORRADE_COMPARE(scene.mutableField(1).size()[0], 5); + CORRADE_COMPARE(scene.mutableField(2).size()[0], 2); + CORRADE_COMPARE(scene.mutableField(3).size()[0], 2); + CORRADE_COMPARE((Containers::arrayCast<1, const Matrix4>(scene.field(0))[2]), Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE((Containers::arrayCast<1, const Int>(scene.field(1))[4]), -1); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedByte>(scene.field(2))[1]), 7); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>(scene.field(3))[0]), (Vector2{37.5f, 1.5f})); + CORRADE_COMPARE((Containers::arrayCast<1, Matrix4>(scene.mutableField(0))[2]), Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE((Containers::arrayCast<1, Int>(scene.mutableField(1))[4]), -1); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedByte>(scene.mutableField(2))[1]), 7); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>(scene.mutableField(3))[0]), (Vector2{37.5f, 1.5f})); + + /* Typed mapping access by ID */ + CORRADE_COMPARE(scene.mapping(0).size(), 5); + CORRADE_COMPARE(scene.mapping(1).size(), 5); + CORRADE_COMPARE(scene.mapping(2).size(), 2); + CORRADE_COMPARE(scene.mapping(3).size(), 2); + CORRADE_COMPARE(scene.mutableMapping(0).size(), 5); + CORRADE_COMPARE(scene.mutableMapping(1).size(), 5); + CORRADE_COMPARE(scene.mutableMapping(2).size(), 2); + CORRADE_COMPARE(scene.mutableMapping(3).size(), 2); + CORRADE_COMPARE(scene.mapping(0)[2], 3); + CORRADE_COMPARE(scene.mapping(1)[4], 1); + CORRADE_COMPARE(scene.mapping(2)[1], 6); + CORRADE_COMPARE(scene.mapping(3)[0], 2); + CORRADE_COMPARE(scene.mutableMapping(0)[2], 3); + CORRADE_COMPARE(scene.mutableMapping(1)[4], 1); + CORRADE_COMPARE(scene.mutableMapping(2)[1], 6); + CORRADE_COMPARE(scene.mutableMapping(3)[0], 2); - CORRADE_COMPARE(data.children2D(), (std::vector{0, 1, 4})); - CORRADE_COMPARE(data.children3D(), (std::vector{2, 5})); - CORRADE_COMPARE(data.importerState(), &a); + /* Typed field access by ID */ + CORRADE_COMPARE(scene.field(0).size(), 5); + CORRADE_COMPARE(scene.field(1).size(), 5); + CORRADE_COMPARE(scene.field(2).size(), 2); + CORRADE_COMPARE(scene.field(3).size()[0], 2); + CORRADE_COMPARE(scene.field(3).size()[1], 2); + CORRADE_COMPARE(scene.mutableField(0).size(), 5); + CORRADE_COMPARE(scene.mutableField(1).size(), 5); + CORRADE_COMPARE(scene.mutableField(2).size(), 2); + CORRADE_COMPARE(scene.mutableField(3).size()[0], 2); + CORRADE_COMPARE(scene.mutableField(3).size()[1], 2); + CORRADE_COMPARE(scene.field(0)[2], Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE(scene.field(1)[4], -1); + CORRADE_COMPARE(scene.field(2)[1], 7); + CORRADE_COMPARE(scene.field(3)[0][0], 37.5f); + CORRADE_COMPARE(scene.field(3)[0][1], 1.5f); + CORRADE_COMPARE(scene.mutableField(0)[2], Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE(scene.mutableField(1)[4], -1); + CORRADE_COMPARE(scene.mutableField(2)[1], 7); + CORRADE_COMPARE(scene.mutableField(3)[0][0], 37.5f); + CORRADE_COMPARE(scene.mutableField(3)[0][1], 1.5f); + + /* Field property access by name */ + CORRADE_COMPARE(scene.fieldFlags(SceneField::Transformation), SceneFieldFlags{}); + CORRADE_COMPARE(scene.fieldFlags(SceneField::Parent), SceneFieldFlag::OffsetOnly); + CORRADE_COMPARE(scene.fieldFlags(SceneField::Mesh), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(scene.fieldFlags(sceneFieldCustom(37)), SceneFieldFlag::OrderedMapping); + CORRADE_COMPARE(scene.fieldType(SceneField::Transformation), SceneFieldType::Matrix4x4); + CORRADE_COMPARE(scene.fieldType(SceneField::Parent), SceneFieldType::Int); + CORRADE_COMPARE(scene.fieldType(SceneField::Mesh), SceneFieldType::UnsignedByte); + CORRADE_COMPARE(scene.fieldType(sceneFieldCustom(37)), SceneFieldType::Float); + CORRADE_COMPARE(scene.fieldSize(SceneField::Transformation), 5); + CORRADE_COMPARE(scene.fieldSize(SceneField::Parent), 5); + CORRADE_COMPARE(scene.fieldSize(SceneField::Mesh), 2); + CORRADE_COMPARE(scene.fieldSize(sceneFieldCustom(37)), 2); + CORRADE_COMPARE(scene.fieldArraySize(SceneField::Transformation), 0); + CORRADE_COMPARE(scene.fieldArraySize(SceneField::Parent), 0); + CORRADE_COMPARE(scene.fieldArraySize(SceneField::Mesh), 0); + CORRADE_COMPARE(scene.fieldArraySize(sceneFieldCustom(37)), 2); + + /* Typeless mapping access by name with a cast later */ + CORRADE_COMPARE(scene.mapping(SceneField::Transformation).size()[0], 5); + CORRADE_COMPARE(scene.mapping(SceneField::Parent).size()[0], 5); + CORRADE_COMPARE(scene.mapping(2).size()[0], 2); + CORRADE_COMPARE(scene.mapping(3).size()[0], 2); + CORRADE_COMPARE(scene.mutableMapping(SceneField::Transformation).size()[0], 5); + CORRADE_COMPARE(scene.mutableMapping(SceneField::Parent).size()[0], 5); + CORRADE_COMPARE(scene.mutableMapping(2).size()[0], 2); + CORRADE_COMPARE(scene.mutableMapping(3).size()[0], 2); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.mapping(SceneField::Transformation))[2]), 3); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.mapping(SceneField::Parent))[4]), 1); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.mapping(2))[1]), 6); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.mapping(3))[0]), 2); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableMapping(SceneField::Transformation))[2]), 3); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableMapping(SceneField::Parent))[4]), 1); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableMapping(2))[1]), 6); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableMapping(3))[0]), 2); + + /* Typeless field access by name with a cast later */ + CORRADE_COMPARE(scene.field(SceneField::Transformation).size()[0], 5); + CORRADE_COMPARE(scene.field(SceneField::Parent).size()[0], 5); + CORRADE_COMPARE(scene.field(SceneField::Mesh).size()[0], 2); + CORRADE_COMPARE(scene.field(sceneFieldCustom(37)).size()[0], 2); + CORRADE_COMPARE(scene.mutableField(SceneField::Transformation).size()[0], 5); + CORRADE_COMPARE(scene.mutableField(SceneField::Parent).size()[0], 5); + CORRADE_COMPARE(scene.mutableField(SceneField::Mesh).size()[0], 2); + CORRADE_COMPARE(scene.mutableField(sceneFieldCustom(37)).size()[0], 2); + CORRADE_COMPARE((Containers::arrayCast<1, const Matrix4>(scene.field(SceneField::Transformation))[2]), Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE((Containers::arrayCast<1, const Int>(scene.field(SceneField::Parent))[4]), -1); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedByte>(scene.field(SceneField::Mesh))[1]), 7); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>(scene.field(sceneFieldCustom(37)))[0]), (Vector2{37.5f, 1.5f})); + CORRADE_COMPARE((Containers::arrayCast<1, Matrix4>(scene.mutableField(SceneField::Transformation))[2]), Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE((Containers::arrayCast<1, Int>(scene.mutableField(SceneField::Parent))[4]), -1); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedByte>(scene.mutableField(SceneField::Mesh))[1]), 7); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>(scene.mutableField(sceneFieldCustom(37)))[0]), (Vector2{37.5f, 1.5f})); + + /* Typed mapping access by name */ + CORRADE_COMPARE(scene.mapping(SceneField::Transformation).size(), 5); + CORRADE_COMPARE(scene.mapping(SceneField::Parent).size(), 5); + CORRADE_COMPARE(scene.mapping(SceneField::Mesh).size(), 2); + CORRADE_COMPARE(scene.mapping(sceneFieldCustom(37)).size(), 2); + CORRADE_COMPARE(scene.mutableMapping(SceneField::Transformation).size(), 5); + CORRADE_COMPARE(scene.mutableMapping(SceneField::Parent).size(), 5); + CORRADE_COMPARE(scene.mutableMapping(SceneField::Mesh).size(), 2); + CORRADE_COMPARE(scene.mutableMapping(sceneFieldCustom(37)).size(), 2); + CORRADE_COMPARE(scene.mapping(SceneField::Transformation)[2], 3); + CORRADE_COMPARE(scene.mapping(SceneField::Parent)[4], 1); + CORRADE_COMPARE(scene.mapping(SceneField::Mesh)[1], 6); + CORRADE_COMPARE(scene.mapping(sceneFieldCustom(37))[0], 2); + CORRADE_COMPARE(scene.mutableMapping(SceneField::Transformation)[2], 3); + CORRADE_COMPARE(scene.mutableMapping(SceneField::Parent)[4], 1); + CORRADE_COMPARE(scene.mutableMapping(SceneField::Mesh)[1], 6); + CORRADE_COMPARE(scene.mutableMapping(sceneFieldCustom(37))[0], 2); + + /* Typed field access by name */ + CORRADE_COMPARE(scene.field(SceneField::Transformation).size(), 5); + CORRADE_COMPARE(scene.field(SceneField::Parent).size(), 5); + CORRADE_COMPARE(scene.field(SceneField::Mesh).size(), 2); + CORRADE_COMPARE(scene.field(sceneFieldCustom(37)).size()[0], 2); + CORRADE_COMPARE(scene.field(sceneFieldCustom(37)).size()[1], 2); + CORRADE_COMPARE(scene.mutableField(SceneField::Transformation).size(), 5); + CORRADE_COMPARE(scene.mutableField(SceneField::Parent).size(), 5); + CORRADE_COMPARE(scene.mutableField(SceneField::Mesh).size(), 2); + CORRADE_COMPARE(scene.mutableField(sceneFieldCustom(37)).size()[0], 2); + CORRADE_COMPARE(scene.mutableField(sceneFieldCustom(37)).size()[1], 2); + CORRADE_COMPARE(scene.field(SceneField::Transformation)[2], Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE(scene.field(SceneField::Parent)[4], -1); + CORRADE_COMPARE(scene.field(SceneField::Mesh)[1], 7); + CORRADE_COMPARE(scene.field(sceneFieldCustom(37))[0][0], 37.5f); + CORRADE_COMPARE(scene.field(sceneFieldCustom(37))[0][1], 1.5f); + CORRADE_COMPARE(scene.mutableField(SceneField::Transformation)[2], Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE(scene.mutableField(SceneField::Parent)[4], -1); + CORRADE_COMPARE(scene.mutableField(SceneField::Mesh)[1], 7); + CORRADE_COMPARE(scene.mutableField(sceneFieldCustom(37))[0][0], 37.5f); + CORRADE_COMPARE(scene.mutableField(sceneFieldCustom(37))[0][1], 1.5f); } -void SceneDataTest::constructCopy() { - CORRADE_VERIFY(!std::is_copy_constructible{}); - CORRADE_VERIFY(!std::is_copy_assignable{}); +void SceneDataTest::constructZeroFields() { + int importerState; + SceneData scene{SceneMappingType::UnsignedShort, 37563, nullptr, {}, &importerState}; + CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_VERIFY(scene.fieldData().empty()); + CORRADE_COMPARE(static_cast(scene.data()), nullptr); + CORRADE_COMPARE(static_cast(scene.mutableData()), nullptr); + CORRADE_COMPARE(scene.importerState(), &importerState); + CORRADE_COMPARE(scene.mappingBound(), 37563); + CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(scene.fieldCount(), 0); + CORRADE_VERIFY(!scene.is2D()); + CORRADE_VERIFY(!scene.is3D()); } -void SceneDataTest::constructMove() { - const int a{}; - SceneData data{{0, 1, 4}, {2, 5}, &a}; +void SceneDataTest::constructZeroObjects() { + int importerState; + SceneFieldData meshes{SceneField::Mesh, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::UnsignedShort, nullptr}; + SceneFieldData materials{SceneField::MeshMaterial, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}; + SceneData scene{SceneMappingType::UnsignedInt, 0, nullptr, {meshes, materials}, &importerState}; + CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_VERIFY(!scene.fieldData().empty()); + CORRADE_COMPARE(static_cast(scene.data()), nullptr); + CORRADE_COMPARE(static_cast(scene.mutableData()), nullptr); + CORRADE_COMPARE(scene.importerState(), &importerState); + CORRADE_COMPARE(scene.mappingBound(), 0); + CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedInt); + CORRADE_COMPARE(scene.fieldCount(), 2); - SceneData b{std::move(data)}; + /* Field property access by name */ + CORRADE_COMPARE(scene.fieldType(SceneField::Mesh), SceneFieldType::UnsignedShort); + CORRADE_COMPARE(scene.fieldType(SceneField::MeshMaterial), SceneFieldType::Int); + CORRADE_COMPARE(scene.fieldSize(SceneField::Mesh), 0); + CORRADE_COMPARE(scene.fieldSize(SceneField::MeshMaterial), 0); + CORRADE_COMPARE(scene.mapping(SceneField::Mesh).data(), nullptr); + CORRADE_COMPARE(scene.mapping(SceneField::MeshMaterial).data(), nullptr); +} - CORRADE_COMPARE(b.children2D(), (std::vector{0, 1, 4})); - CORRADE_COMPARE(b.children3D(), (std::vector{2, 5})); - CORRADE_COMPARE(b.importerState(), &a); +void SceneDataTest::constructSpecialStrides() { + Containers::StridedArrayView1D broadcastedData; + Containers::StridedArrayView1D nonBroadcastedData; + Containers::ArrayTuple data{ + {NoInit, 1, broadcastedData}, + {NoInit, 4, nonBroadcastedData} + }; - const int c{}; - SceneData d{{1, 3}, {1, 4, 5}, &c}; - d = std::move(b); + broadcastedData[0] = 15; + nonBroadcastedData[0] = 1; + nonBroadcastedData[1] = 2; + nonBroadcastedData[2] = 3; + nonBroadcastedData[3] = 4; - CORRADE_COMPARE(d.children2D(), (std::vector{0, 1, 4})); - CORRADE_COMPARE(d.children3D(), (std::vector{2, 5})); - CORRADE_COMPARE(d.importerState(), &a); + SceneFieldData broadcastedMapping{sceneFieldCustom(38), + broadcastedData.broadcasted<0>(4), nonBroadcastedData}; + SceneFieldData broadcastedField{sceneFieldCustom(39), + nonBroadcastedData, broadcastedData.broadcasted<0>(4)}; + SceneFieldData flippedField{sceneFieldCustom(40), + nonBroadcastedData.flipped<0>(), nonBroadcastedData.flipped<0>()}; + SceneData scene{SceneMappingType::UnsignedShort, 8, std::move(data), { + broadcastedMapping, broadcastedField, flippedField + }}; - CORRADE_VERIFY(std::is_nothrow_move_constructible::value); - CORRADE_VERIFY(std::is_nothrow_move_assignable::value); + CORRADE_COMPARE_AS(scene.mapping(0), + Containers::arrayView({15, 15, 15, 15}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(0), + Containers::arrayView({1, 2, 3, 4}), + TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(scene.mapping(1), + Containers::arrayView({1, 2, 3, 4}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(1), + Containers::arrayView({15, 15, 15, 15}), + TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(scene.mapping(2), + Containers::arrayView({4, 3, 2, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(2), + Containers::arrayView({4, 3, 2, 1}), + TestSuite::Compare::Container); +} + +void SceneDataTest::constructNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + struct Data { + UnsignedShort object; + UnsignedByte mesh; + } data[]{ + {0, 2}, + {1, 1}, + {2, 0} + }; + + int importerState; + SceneFieldData mesh{SceneField::Mesh, + Containers::stridedArrayView(data).slice(&Data::object), + Containers::stridedArrayView(data).slice(&Data::mesh)}; + SceneData scene{SceneMappingType::UnsignedShort, 7, instanceData.dataFlags, Containers::arrayView(data), {mesh}, &importerState}; + + CORRADE_COMPARE(scene.dataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(static_cast(scene.data()), +data); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(scene.mutableData()), static_cast(data)); + CORRADE_COMPARE(scene.mappingBound(), 7); + CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(scene.fieldCount(), 1); + CORRADE_COMPARE(scene.importerState(), &importerState); + + CORRADE_COMPARE(scene.mapping(0).size(), 3); + CORRADE_COMPARE(scene.mapping(0)[2], 2); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(scene.mutableMapping(0)[2], 2); + + CORRADE_COMPARE(scene.field(0).size(), 3); + CORRADE_COMPARE(scene.field(0)[2], 0); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(scene.mutableField(0)[2], 0); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void SceneDataTest::constructDeprecated() { + auto&& data = ChildrenDeprecatedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + int a; + CORRADE_IGNORE_DEPRECATED_PUSH + SceneData scene{ + data.is2D ? std::vector{5, 17, 36, 22} : std::vector{}, + data.is3D ? std::vector{5, 17, 36, 22} : std::vector{}, + &a}; + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedInt); + if(data.is2D || data.is3D) + CORRADE_COMPARE(scene.mappingBound(), 37); + else + CORRADE_COMPARE(scene.mappingBound(), 0); + CORRADE_COMPARE(scene.dataFlags(), DataFlag::Mutable|DataFlag::Owned); + CORRADE_COMPARE(scene.importerState(), &a); + CORRADE_COMPARE(scene.fieldCount(), 1); + CORRADE_COMPARE(scene.fieldName(0), SceneField::Parent); + CORRADE_COMPARE(scene.fieldFlags(0), SceneFieldFlags{}); + CORRADE_COMPARE(scene.fieldType(0), SceneFieldType::Int); + if(data.is2D || data.is3D) { + CORRADE_COMPARE_AS(scene.mapping(0), + Containers::arrayView({5, 17, 36, 22}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.field(0), + Containers::arrayView({-1, -1, -1, -1}), + TestSuite::Compare::Container); + } else CORRADE_COMPARE(scene.fieldSize(SceneField::Parent), 0); + /* There's no transformation field that would disambiguate this, the state + is set directly */ + CORRADE_COMPARE(scene.is2D(), data.is2D); + CORRADE_COMPARE(scene.is3D(), data.is3D); + + /* The deleters have to be trivial, otherwise this instance wouldn't be + usable from an AbstractImporter */ + CORRADE_VERIFY(!scene.releaseFieldData().deleter()); + CORRADE_VERIFY(!scene.releaseData().deleter()); +} + +void SceneDataTest::constructDeprecatedBoth2DAnd3D() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH + SceneData scene{{5, 17}, {36, 22}}; + CORRADE_IGNORE_DEPRECATED_POP + CORRADE_COMPARE(out.str(), "Trade::SceneData: it's no longer possible to have a scene with both 2D and 3D objects\n"); +} +#endif + +void SceneDataTest::constructDuplicateField() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + /* Builtin fields are checked using a bitfield, as they have monotonic + numbering */ + SceneFieldData meshes{SceneField::Mesh, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::UnsignedShort, nullptr}; + SceneFieldData materials{SceneField::MeshMaterial, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}; + SceneFieldData meshesAgain{SceneField::Mesh, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::UnsignedInt, nullptr}; + + std::ostringstream out; + Error redirectError{&out}; + SceneData scene{SceneMappingType::UnsignedInt, 0, nullptr, {meshes, materials, meshesAgain}}; + CORRADE_COMPARE(out.str(), "Trade::SceneData: duplicate field Trade::SceneField::Mesh\n"); +} + +void SceneDataTest::constructDuplicateCustomField() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + /* These are checked in an O(n^2) way, separately from builtin fields. + Can't use a bitfield since the field index can be anything. */ + SceneFieldData customA{sceneFieldCustom(37), SceneMappingType::UnsignedInt, nullptr, SceneFieldType::UnsignedShort, nullptr}; + SceneFieldData customB{sceneFieldCustom(1038576154), SceneMappingType::UnsignedInt, nullptr, SceneFieldType::UnsignedInt, nullptr}; + SceneFieldData customAAgain{sceneFieldCustom(37), SceneMappingType::UnsignedInt, nullptr, SceneFieldType::UnsignedInt, nullptr}; + + std::ostringstream out; + Error redirectError{&out}; + SceneData scene{SceneMappingType::UnsignedInt, 0, nullptr, {customA, customB, customAAgain}}; + CORRADE_COMPARE(out.str(), "Trade::SceneData: duplicate field Trade::SceneField::Custom(37)\n"); +} + +void SceneDataTest::constructInconsistentMappingType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneFieldData meshes{SceneField::Mesh, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::UnsignedShort, nullptr}; + SceneFieldData materials{SceneField::MeshMaterial, SceneMappingType::UnsignedShort, nullptr, SceneFieldType::Int, nullptr}; + + std::ostringstream out; + Error redirectError{&out}; + SceneData scene{SceneMappingType::UnsignedInt, 0, nullptr, {meshes, materials}}; + CORRADE_COMPARE(out.str(), "Trade::SceneData: inconsistent mapping type, got Trade::SceneMappingType::UnsignedShort for field 1 but expected Trade::SceneMappingType::UnsignedInt\n"); +} + +void SceneDataTest::constructMappingDataNotContained() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Containers::Array data{reinterpret_cast(0xbadda9), 10, [](char*, std::size_t){}}; + Containers::ArrayView dataIn{reinterpret_cast(0xbadda9), 5}; + Containers::ArrayView dataSlightlyOut{reinterpret_cast(0xbaddaa), 5}; + Containers::ArrayView dataOut{reinterpret_cast(0xdead), 5}; + + std::ostringstream out; + Error redirectError{&out}; + /* First a "slightly off" view that exceeds the original by one byte */ + SceneData{SceneMappingType::UnsignedShort, 5, {}, data, { + SceneFieldData{SceneField::Mesh, dataSlightlyOut, dataIn} + }}; + /* Second a view that's in a completely different location */ + SceneData{SceneMappingType::UnsignedShort, 5, {}, data, { + SceneFieldData{SceneField::Light, dataIn, dataIn}, + SceneFieldData{SceneField::Mesh, dataOut, dataIn} + }}; + /* Verify the owning constructor does the checks as well */ + SceneData{SceneMappingType::UnsignedShort, 5, std::move(data), { + SceneFieldData{SceneField::Light, dataIn, dataIn}, + SceneFieldData{SceneField::Mesh, dataOut, dataIn} + }}; + /* And if we have no data at all, it doesn't try to dereference them but + still checks properly */ + SceneData{SceneMappingType::UnsignedShort, 5, nullptr, { + SceneFieldData{SceneField::Mesh, dataOut, dataIn} + }}; + /* Finally, offset-only fields with a different message */ + SceneData{SceneMappingType::UnsignedByte, 6, Containers::Array{24}, { + SceneFieldData{SceneField::Mesh, 6, SceneMappingType::UnsignedByte, 4, 4, SceneFieldType::UnsignedByte, 0, 4} + }}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: mapping data [0xbaddaa:0xbaddb4] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: mapping data [0xdead:0xdeb7] of field 1 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: mapping data [0xdead:0xdeb7] of field 1 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: mapping data [0xdead:0xdeb7] of field 0 are not contained in passed data array [0x0:0x0]\n" + + "Trade::SceneData: offset-only mapping data of field 0 span 25 bytes but passed data array has only 24\n"); +} + +void SceneDataTest::constructFieldDataNotContained() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + /* Mostly the same as constructMappingDataNotContained() with mapping and + field views swapped, and added checks for array fields */ + + Containers::Array data{reinterpret_cast(0xbadda9), 10, [](char*, std::size_t){}}; + Containers::ArrayView dataIn{reinterpret_cast(0xbadda9), 5}; + Containers::ArrayView dataSlightlyOut{reinterpret_cast(0xbaddaa), 5}; + Containers::ArrayView dataOut{reinterpret_cast(0xdead), 5}; + + std::ostringstream out; + Error redirectError{&out}; + /* First a "slightly off" view that exceeds the original by one byte */ + SceneData{SceneMappingType::UnsignedShort, 5, {}, data, { + SceneFieldData{SceneField::Mesh, dataIn, dataSlightlyOut} + }}; + /* Second a view that's in a completely different location */ + SceneData{SceneMappingType::UnsignedShort, 5, {}, data, { + SceneFieldData{SceneField::Light, dataIn, dataIn}, + SceneFieldData{SceneField::Mesh, dataIn, dataOut} + }}; + /* Verify array size is taken into account as well. If not, the data would + span only 7 bytes out of 10 (instead of 12), which is fine. */ + SceneData{SceneMappingType::UnsignedShort, 5, {}, data, { + SceneFieldData{sceneFieldCustom(37), dataIn.prefix(2), Containers::StridedArrayView2D{Containers::ArrayView{reinterpret_cast(0xbadda9), 12}, {2, 6}}} + }}; + /* Verify the owning constructor does the checks as well */ + SceneData{SceneMappingType::UnsignedShort, 5, std::move(data), { + SceneFieldData{SceneField::Light, dataIn, dataIn}, + SceneFieldData{SceneField::Mesh, dataIn, dataOut} + }}; + /* Not checking for nullptr data, since that got checked for mapping view + already and there's no way to trigger it for fields */ + /* Finally, offset-only fields with a different message */ + SceneData{SceneMappingType::UnsignedShort, 6, Containers::Array{24}, { + SceneFieldData{SceneField::Mesh, 6, SceneMappingType::UnsignedShort, 0, 4, SceneFieldType::UnsignedByte, 4, 4} + }}; + /* This again spans 21 bytes if array size isn't taken into account, and 25 + if it is */ + SceneData{SceneMappingType::UnsignedShort, 5, Containers::Array{24}, { + SceneFieldData{sceneFieldCustom(37), 5, SceneMappingType::UnsignedShort, 0, 5, SceneFieldType::UnsignedByte, 0, 5, 5} + }}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: field data [0xbaddaa:0xbaddb4] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: field data [0xdead:0xdeb7] of field 1 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: field data [0xbadda9:0xbaddb5] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: field data [0xdead:0xdeb7] of field 1 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + + "Trade::SceneData: offset-only field data of field 0 span 25 bytes but passed data array has only 24\n" + "Trade::SceneData: offset-only field data of field 0 span 25 bytes but passed data array has only 24\n"); +} + +void SceneDataTest::constructMappingTypeTooSmall() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + /* This is fine */ + SceneData{SceneMappingType::UnsignedByte, 0xff, nullptr, {}}; + SceneData{SceneMappingType::UnsignedShort, 0xffff, nullptr, {}}; + SceneData{SceneMappingType::UnsignedInt, 0xffffffffu, nullptr, {}}; + + std::ostringstream out; + Error redirectError{&out}; + SceneData{SceneMappingType::UnsignedByte, 0x100, nullptr, {}}; + SceneData{SceneMappingType::UnsignedShort, 0x10000, nullptr, {}}; + SceneData{SceneMappingType::UnsignedInt, 0x100000000ull, nullptr, {}}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: Trade::SceneMappingType::UnsignedByte is too small for 256 objects\n" + "Trade::SceneData: Trade::SceneMappingType::UnsignedShort is too small for 65536 objects\n" + "Trade::SceneData: Trade::SceneMappingType::UnsignedInt is too small for 4294967296 objects\n"); +} + +void SceneDataTest::constructNotOwnedFlagOwned() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + const char data[32]{}; + + std::ostringstream out; + Error redirectError{&out}; + SceneData{SceneMappingType::UnsignedByte, 5, DataFlag::Owned, Containers::arrayView(data), {}}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: can't construct with non-owned data but Trade::DataFlag::Owned\n"); +} + +void SceneDataTest::constructMismatchedTRSViews() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Containers::ArrayView data{reinterpret_cast(0xcafe0000), + /* Three entries, each having a 2D TRS and 3 object IDs */ + 3*(24 + 12)}; + Containers::ArrayView translationMappingData{ + reinterpret_cast(data.data()), 3}; + Containers::ArrayView translationFieldData{ + reinterpret_cast(data.data() + 0x0c), 3}; + Containers::ArrayView rotationMappingData{ + reinterpret_cast(data.data() + 0x24), 3}; + Containers::ArrayView rotationFieldData{ + reinterpret_cast(data.data() + 0x30), 3}; + Containers::ArrayView scalingMappingData{ + reinterpret_cast(data.data() + 0x48), 3}; + Containers::ArrayView scalingFieldData{ + reinterpret_cast(data.data() + 0x54), 3}; + + SceneFieldData translations{SceneField::Translation, translationMappingData, translationFieldData}; + SceneFieldData rotationsDifferent{SceneField::Rotation, rotationMappingData, rotationFieldData}; + SceneFieldData scalingsDifferent{SceneField::Scaling, scalingMappingData, scalingFieldData}; + SceneFieldData rotationsSameButLess{SceneField::Rotation, translationMappingData.except(1), rotationFieldData.except(1)}; + SceneFieldData scalingsSameButLess{SceneField::Scaling, translationMappingData.except(2), scalingFieldData.except(2)}; + + /* Test that all pairs get checked */ + std::ostringstream out; + Error redirectError{&out}; + SceneData{SceneMappingType::UnsignedInt, 3, {}, data, {translations, rotationsDifferent}}; + SceneData{SceneMappingType::UnsignedInt, 3, {}, data, {translations, scalingsDifferent}}; + SceneData{SceneMappingType::UnsignedInt, 3, {}, data, {rotationsDifferent, scalingsDifferent}}; + SceneData{SceneMappingType::UnsignedInt, 3, {}, data, {translations, rotationsSameButLess}}; + SceneData{SceneMappingType::UnsignedInt, 3, {}, data, {translations, scalingsSameButLess}}; + SceneData{SceneMappingType::UnsignedInt, 3, {}, data, {rotationsSameButLess, scalingsSameButLess}}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: Trade::SceneField::Rotation mapping data [0xcafe0024:0xcafe0030] is different from Trade::SceneField::Translation mapping data [0xcafe0000:0xcafe000c]\n" + "Trade::SceneData: Trade::SceneField::Scaling mapping data [0xcafe0048:0xcafe0054] is different from Trade::SceneField::Translation mapping data [0xcafe0000:0xcafe000c]\n" + "Trade::SceneData: Trade::SceneField::Scaling mapping data [0xcafe0048:0xcafe0054] is different from Trade::SceneField::Rotation mapping data [0xcafe0024:0xcafe0030]\n" + "Trade::SceneData: Trade::SceneField::Rotation mapping data [0xcafe0000:0xcafe0008] is different from Trade::SceneField::Translation mapping data [0xcafe0000:0xcafe000c]\n" + "Trade::SceneData: Trade::SceneField::Scaling mapping data [0xcafe0000:0xcafe0004] is different from Trade::SceneField::Translation mapping data [0xcafe0000:0xcafe000c]\n" + "Trade::SceneData: Trade::SceneField::Scaling mapping data [0xcafe0000:0xcafe0004] is different from Trade::SceneField::Rotation mapping data [0xcafe0000:0xcafe0008]\n"); +} + +template struct NameTraits; +#define _c(format) template<> struct NameTraits { \ + static const char* name() { return #format; } \ + }; +_c(UnsignedByte) +_c(Byte) +_c(UnsignedShort) +_c(Short) +_c(UnsignedInt) +_c(Int) +_c(UnsignedLong) +_c(Long) +_c(Float) +_c(Double) +_c(Vector2) +_c(Vector2d) +_c(Vector3) +_c(Vector3d) +_c(Matrix3) +_c(Matrix3d) +_c(Matrix3x2) +_c(Matrix3x2d) +_c(Matrix4) +_c(Matrix4d) +_c(Matrix4x3) +_c(Matrix4x3d) +_c(Complex) +_c(Complexd) +_c(Quaternion) +_c(Quaterniond) +_c(DualComplex) +_c(DualComplexd) +_c(DualQuaternion) +_c(DualQuaterniond) +template struct NameTraits { + static const char* name() { return "Pointer"; } +}; +template struct NameTraits { + static const char* name() { return "MutablePointer"; } +}; +#undef _c + +template void SceneDataTest::constructMismatchedTRSDimensionality() { + setTestCaseTemplateName(NameTraits::name()); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneFieldData transformationMatrices2D{SceneField::Transformation, SceneMappingType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr}; + SceneFieldData transformationRectangularMatrices2D{SceneField::Transformation, SceneMappingType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr}; + SceneFieldData transformations2D{SceneField::Transformation, SceneMappingType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr}; + SceneFieldData transformationMatrices3D{SceneField::Transformation, SceneMappingType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr}; + SceneFieldData transformationRectangularMatrices3D{SceneField::Transformation, SceneMappingType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr}; + SceneFieldData transformations3D{SceneField::Transformation, SceneMappingType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr}; + SceneFieldData translations2D{SceneField::Translation, SceneMappingType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr}; + SceneFieldData translations3D{SceneField::Translation, SceneMappingType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr}; + SceneFieldData rotations2D{SceneField::Rotation, SceneMappingType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr}; + SceneFieldData rotations3D{SceneField::Rotation, SceneMappingType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr}; + SceneFieldData scalings2D{SceneField::Scaling, SceneMappingType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr}; + SceneFieldData scalings3D{SceneField::Scaling, SceneMappingType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr}; + + /* Test that all pairs get checked */ + std::ostringstream out; + Error redirectError{&out}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformationMatrices2D, translations3D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformationMatrices2D, rotations3D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformationMatrices2D, scalings3D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformationRectangularMatrices2D, translations3D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformationRectangularMatrices2D, rotations3D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformationRectangularMatrices2D, scalings3D}}; + + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformations2D, translations3D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformations2D, rotations3D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformations2D, scalings3D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {translations2D, rotations3D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {translations2D, scalings3D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {rotations2D, scalings3D}}; + + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformationMatrices3D, translations2D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformationMatrices3D, rotations2D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformationMatrices3D, scalings2D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformationRectangularMatrices3D, translations2D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformationRectangularMatrices3D, rotations2D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformationRectangularMatrices3D, scalings2D}}; + + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformations3D, translations2D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformations3D, rotations2D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {transformations3D, scalings2D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {translations3D, rotations2D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {translations3D, scalings2D}}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, {rotations3D, scalings2D}}; + CORRADE_COMPARE(out.str(), Utility::formatString( + "Trade::SceneData: expected a 2D translation field but got Trade::SceneFieldType::{0}\n" + "Trade::SceneData: expected a 2D rotation field but got Trade::SceneFieldType::{1}\n" + "Trade::SceneData: expected a 2D scaling field but got Trade::SceneFieldType::{0}\n" + "Trade::SceneData: expected a 2D translation field but got Trade::SceneFieldType::{0}\n" + "Trade::SceneData: expected a 2D rotation field but got Trade::SceneFieldType::{1}\n" + "Trade::SceneData: expected a 2D scaling field but got Trade::SceneFieldType::{0}\n" + + "Trade::SceneData: expected a 2D translation field but got Trade::SceneFieldType::{0}\n" + "Trade::SceneData: expected a 2D rotation field but got Trade::SceneFieldType::{1}\n" + "Trade::SceneData: expected a 2D scaling field but got Trade::SceneFieldType::{0}\n" + "Trade::SceneData: expected a 2D rotation field but got Trade::SceneFieldType::{1}\n" + "Trade::SceneData: expected a 2D scaling field but got Trade::SceneFieldType::{0}\n" + "Trade::SceneData: expected a 2D scaling field but got Trade::SceneFieldType::{0}\n" + + "Trade::SceneData: expected a 3D translation field but got Trade::SceneFieldType::{2}\n" + "Trade::SceneData: expected a 3D rotation field but got Trade::SceneFieldType::{3}\n" + "Trade::SceneData: expected a 3D scaling field but got Trade::SceneFieldType::{2}\n" + "Trade::SceneData: expected a 3D translation field but got Trade::SceneFieldType::{2}\n" + "Trade::SceneData: expected a 3D rotation field but got Trade::SceneFieldType::{3}\n" + "Trade::SceneData: expected a 3D scaling field but got Trade::SceneFieldType::{2}\n" + + "Trade::SceneData: expected a 3D translation field but got Trade::SceneFieldType::{2}\n" + "Trade::SceneData: expected a 3D rotation field but got Trade::SceneFieldType::{3}\n" + "Trade::SceneData: expected a 3D scaling field but got Trade::SceneFieldType::{2}\n" + "Trade::SceneData: expected a 3D rotation field but got Trade::SceneFieldType::{3}\n" + "Trade::SceneData: expected a 3D scaling field but got Trade::SceneFieldType::{2}\n" + "Trade::SceneData: expected a 3D scaling field but got Trade::SceneFieldType::{2}\n", + NameTraits>::name(), + NameTraits>::name(), + NameTraits>::name(), + NameTraits>::name())); +} + +void SceneDataTest::constructMismatchedMeshMaterialView() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Containers::ArrayView data{reinterpret_cast(0xcafe0000), + /* Three entries, each having mesh/material ID and 2 object IDs */ + 3*(8 + 8)}; + Containers::ArrayView meshMappingData{ + reinterpret_cast(data.data()), 3}; + Containers::ArrayView meshFieldData{ + reinterpret_cast(data.data() + 0x0c), 3}; + Containers::ArrayView meshMaterialMappingData{ + reinterpret_cast(data.data() + 0x18), 3}; + Containers::ArrayView meshMaterialFieldData{ + reinterpret_cast(data.data() + 0x24), 3}; + + SceneFieldData meshes{SceneField::Mesh, meshMappingData, meshFieldData}; + SceneFieldData meshMaterialsDifferent{SceneField::MeshMaterial, meshMaterialMappingData, meshMaterialFieldData}; + SceneFieldData meshMaterialsSameButLess{SceneField::MeshMaterial, meshMappingData.except(1), meshMaterialFieldData.except(1)}; + + std::ostringstream out; + Error redirectError{&out}; + SceneData{SceneMappingType::UnsignedInt, 3, {}, data, {meshes, meshMaterialsDifferent}}; + SceneData{SceneMappingType::UnsignedInt, 3, {}, data, {meshes, meshMaterialsSameButLess}}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: Trade::SceneField::MeshMaterial mapping data [0xcafe0018:0xcafe0024] is different from Trade::SceneField::Mesh mapping data [0xcafe0000:0xcafe000c]\n" + "Trade::SceneData: Trade::SceneField::MeshMaterial mapping data [0xcafe0000:0xcafe0008] is different from Trade::SceneField::Mesh mapping data [0xcafe0000:0xcafe000c]\n"); +} + +void SceneDataTest::constructAmbiguousSkinDimensions() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + SceneData{SceneMappingType::UnsignedInt, 0, nullptr, { + SceneFieldData{SceneField::Skin, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::UnsignedInt, nullptr} + }}; + CORRADE_COMPARE(out.str(), "Trade::SceneData: a skin field requires some transformation field to be present in order to disambiguate between 2D and 3D\n"); +} + +void SceneDataTest::constructCopy() { + CORRADE_VERIFY(!std::is_copy_constructible{}); + CORRADE_VERIFY(!std::is_copy_assignable{}); +} + +void SceneDataTest::constructMove() { + struct Mesh { + UnsignedShort object; + UnsignedInt mesh; + }; + + Containers::Array data{NoInit, 3*sizeof(Mesh)}; + auto meshData = Containers::arrayCast(data); + meshData[0] = {0, 2}; + meshData[1] = {73, 1}; + meshData[2] = {122, 2}; + + int importerState; + SceneFieldData meshes{SceneField::Mesh, stridedArrayView(meshData).slice(&Mesh::object), stridedArrayView(meshData).slice(&Mesh::mesh)}; + SceneData a{SceneMappingType::UnsignedShort, 15, std::move(data), {meshes}, &importerState}; + + SceneData b{std::move(a)}; + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(b.mappingBound(), 15); + CORRADE_COMPARE(b.mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(b.fieldCount(), 1); + CORRADE_COMPARE(b.importerState(), &importerState); + CORRADE_COMPARE(static_cast(b.data()), meshData.data()); + CORRADE_COMPARE(b.fieldName(0), SceneField::Mesh); + CORRADE_COMPARE(b.fieldType(0), SceneFieldType::UnsignedInt); + CORRADE_COMPARE(b.fieldSize(0), 3); + CORRADE_COMPARE(b.fieldArraySize(0), 0); + CORRADE_COMPARE(b.mapping(0)[2], 122); + CORRADE_COMPARE(b.field(0)[2], 2); + + SceneData c{SceneMappingType::UnsignedByte, 76, nullptr, {}}; + c = std::move(b); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(c.mappingBound(), 15); + CORRADE_COMPARE(c.mappingType(), SceneMappingType::UnsignedShort); + CORRADE_COMPARE(c.fieldCount(), 1); + CORRADE_COMPARE(c.importerState(), &importerState); + CORRADE_COMPARE(static_cast(c.data()), meshData.data()); + CORRADE_COMPARE(c.fieldName(0), SceneField::Mesh); + CORRADE_COMPARE(c.fieldType(0), SceneFieldType::UnsignedInt); + CORRADE_COMPARE(c.fieldSize(0), 3); + CORRADE_COMPARE(c.fieldArraySize(0), 0); + CORRADE_COMPARE(c.mapping(0)[2], 122); + CORRADE_COMPARE(c.field(0)[2], 2); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} + +void SceneDataTest::findFieldId() { + SceneData scene{SceneMappingType::UnsignedInt, 0, {}, nullptr, { + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::UnsignedByte, nullptr} + }}; + + CORRADE_COMPARE(scene.findFieldId(SceneField::Parent), 0); + CORRADE_COMPARE(scene.findFieldId(SceneField::Mesh), 1); + CORRADE_COMPARE(scene.findFieldId(SceneField::MeshMaterial), Containers::NullOpt); + + CORRADE_COMPARE(scene.fieldId(SceneField::Parent), 0); + CORRADE_COMPARE(scene.fieldId(SceneField::Mesh), 1); + + CORRADE_VERIFY(scene.hasField(SceneField::Parent)); + CORRADE_VERIFY(scene.hasField(SceneField::Mesh)); + CORRADE_VERIFY(!scene.hasField(SceneField::MeshMaterial)); +} + +template void SceneDataTest::findFieldObjectOffset() { + setTestCaseTemplateName(NameTraits::name()); + + auto&& data = FindFieldObjectOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Field { + T object; + UnsignedInt mesh; + } fields[5]{ + {T(data.mapping[0]), 0}, + {T(data.mapping[1]), 0}, + {T(data.mapping[2]), 0}, + {T(data.mapping[3]), 0}, + {T(data.mapping[4]), 0} + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{Implementation::sceneMappingTypeFor(), 7, {}, fields, { + /* Test also with a completely empty field */ + SceneFieldData{SceneField::Parent, Implementation::sceneMappingTypeFor(), nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh), data.flags} + }}; + + /* An empty field should not find anything for any query with any flags */ + if(data.offset == 0) { + CORRADE_COMPARE(scene.findFieldObjectOffset(0, data.object), Containers::NullOpt); + CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Parent, data.object), Containers::NullOpt); + CORRADE_VERIFY(!scene.hasFieldObject(0, data.object)); + CORRADE_VERIFY(!scene.hasFieldObject(SceneField::Parent, data.object)); + } + + CORRADE_COMPARE(scene.findFieldObjectOffset(1, data.object, data.offset), data.expected); + CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, data.object, data.offset), data.expected); + if(data.offset == 0) { + CORRADE_COMPARE(scene.hasFieldObject(1, data.object), !!data.expected); + CORRADE_COMPARE(scene.hasFieldObject(SceneField::Mesh, data.object), !!data.expected); + } + + if(data.expected) { + CORRADE_COMPARE(scene.fieldObjectOffset(1, data.object, data.offset), *data.expected); + CORRADE_COMPARE(scene.fieldObjectOffset(SceneField::Mesh, data.object, data.offset), *data.expected); + } +} + +void SceneDataTest::findFieldObjectOffsetInvalidOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + } fields[]{ + {4, 1}, + {1, 3}, + {2, 4} + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.findFieldObjectOffset(0, 1, 4); + scene.findFieldObjectOffset(SceneField::Mesh, 1, 4); + scene.fieldObjectOffset(0, 1, 4); + scene.fieldObjectOffset(SceneField::Mesh, 1, 4); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::findFieldObjectOffset(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::findFieldObjectOffset(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::fieldObjectOffset(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::fieldObjectOffset(): offset 4 out of bounds for a field of size 3\n"); +} + +void SceneDataTest::fieldObjectOffsetNotFound() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + } fields[]{ + {4, 1}, + {1, 3}, + {2, 4}, + {0, 5}, + {2, 5} + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + /* Test also with a completely empty field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.fieldObjectOffset(0, 4); + scene.fieldObjectOffset(SceneField::Parent, 4); + scene.fieldObjectOffset(1, 1, 2); + scene.fieldObjectOffset(SceneField::Mesh, 1, 2); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::fieldObjectOffset(): object 4 not found in field Trade::SceneField::Parent starting at offset 0\n" + "Trade::SceneData::fieldObjectOffset(): object 4 not found in field Trade::SceneField::Parent starting at offset 0\n" + "Trade::SceneData::fieldObjectOffset(): object 1 not found in field Trade::SceneField::Mesh starting at offset 2\n" + "Trade::SceneData::fieldObjectOffset(): object 1 not found in field Trade::SceneField::Mesh starting at offset 2\n"); +} + +template void SceneDataTest::mappingAsArrayByIndex() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + T object; + UnsignedByte mesh; + } fields[]{ + {T(15), 0}, + {T(37), 1}, + {T(44), 15} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{Implementation::sceneMappingTypeFor(), 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, Implementation::sceneMappingTypeFor(), nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + CORRADE_COMPARE_AS(scene.mappingAsArray(1), + Containers::arrayView({15, 37, 44}), + TestSuite::Compare::Container); +} + +template void SceneDataTest::mappingAsArrayByName() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + T object; + UnsignedByte mesh; + } fields[]{ + {T(15), 0}, + {T(37), 1}, + {T(44), 15} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{Implementation::sceneMappingTypeFor(), 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, Implementation::sceneMappingTypeFor(), nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + CORRADE_COMPARE_AS(scene.mappingAsArray(SceneField::Mesh), + Containers::arrayView({15, 37, 44}), + TestSuite::Compare::Container); +} + +void SceneDataTest::mappingIntoArrayByIndex() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + } fields[] { + {15, 0}, + {37, 1}, + {44, 15} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, + view.slice(&Field::object), + view.slice(&Field::mesh)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt out[3]; + scene.mappingInto(1, out); + CORRADE_COMPARE_AS(Containers::stridedArrayView(out), + view.slice(&Field::object), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.mappingInto(1, data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::mappingIntoArrayByName() { + auto&& data = IntoArrayOffsetData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + } fields[] { + {15, 0}, + {37, 1}, + {44, 15} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, + view.slice(&Field::object), + view.slice(&Field::mesh)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt out[3]; + scene.mappingInto(SceneField::Mesh, out); + CORRADE_COMPARE_AS(Containers::stridedArrayView(out), + view.slice(&Field::object), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array out{data.size}; + CORRADE_COMPARE(scene.mappingInto(SceneField::Mesh, data.offset, out), data.expectedSize); + CORRADE_COMPARE_AS(out.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::mappingIntoArrayInvalidSizeOrOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedByte mesh; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt destination[2]; + scene.mappingInto(0, destination); + scene.mappingInto(SceneField::Mesh, destination); + scene.mappingInto(0, 4, destination); + scene.mappingInto(SceneField::Mesh, 4, destination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::mappingInto(): expected a view with 3 elements but got 2\n" + "Trade::SceneData::mappingInto(): expected a view with 3 elements but got 2\n" + "Trade::SceneData::mappingInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::mappingInto(): offset 4 out of bounds for a field of size 3\n"); +} + +template void SceneDataTest::parentsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + UnsignedByte object; + T parent; + } fields[]{ + {0, T(15)}, + {1, T(-1)}, + {15, T(44)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedByte, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedByte, nullptr, SceneFieldType::UnsignedInt, nullptr}, + SceneFieldData{SceneField::Parent, view.slice(&Field::object), view.slice(&Field::parent)} + }}; + + CORRADE_COMPARE_AS(scene.parentsAsArray(), (Containers::arrayView>({ + {0, 15}, + {1, -1}, + {15, 44} + })), TestSuite::Compare::Container); +} + +void SceneDataTest::parentsIntoArray() { + auto&& data = IntoArrayOffset1Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + Int parent; + } fields[] { + {1, 15}, + {0, -1}, + {4, 44} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Mesh, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::UnsignedInt, nullptr}, + SceneFieldData{SceneField::Parent, + view.slice(&Field::object), + view.slice(&Field::parent)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt mapping[3]; + Int field[3]; + scene.parentsInto( + data.mapping ? Containers::arrayView(mapping) : nullptr, + data.field ? Containers::arrayView(field) : nullptr + ); + if(data.mapping) CORRADE_COMPARE_AS(Containers::stridedArrayView(mapping), + view.slice(&Field::object), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(Containers::stridedArrayView(field), + view.slice(&Field::parent), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array mapping{data.size}; + Containers::Array field{data.size}; + CORRADE_COMPARE(scene.parentsInto(data.offset, + data.mapping ? arrayView(mapping) : nullptr, + data.field ? arrayView(field) : nullptr + ), data.expectedSize); + if(data.mapping) CORRADE_COMPARE_AS(mapping.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(field.prefix(data.expectedSize), + view.slice(&Field::parent) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::parentsIntoArrayInvalidSizeOrOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + Int parent; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Parent, view.slice(&Field::object), view.slice(&Field::parent)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt mappingDestinationCorrect[3]; + UnsignedInt mappingDestination[2]; + Int fieldDestinationCorrect[3]; + Int fieldDestination[2]; + scene.parentsInto(mappingDestination, fieldDestinationCorrect); + scene.parentsInto(mappingDestinationCorrect, fieldDestination); + scene.parentsInto(4, mappingDestination, fieldDestination); + scene.parentsInto(0, mappingDestinationCorrect, fieldDestination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::parentsInto(): expected mapping destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::parentsInto(): expected field destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::parentsInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::parentsInto(): mapping and field destination views have different size, 3 vs 2\n"); +} + +template struct TransformationTypeFor { + typedef T Type; +}; +template struct TransformationTypeFor> { + typedef Math::Matrix3 Type; +}; +template struct TransformationTypeFor> { + typedef Math::Matrix4 Type; +}; + +template void SceneDataTest::transformations2DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + typedef typename T::Type U; + typedef typename TransformationTypeFor::Type TT; + + struct Transformation { + UnsignedInt object; + T transformation; + }; + + struct Component { + UnsignedInt object; + Vector2 translation; + Vector2 scaling; + }; + + Containers::StridedArrayView1D transformations; + Containers::StridedArrayView1D components; + Containers::Array data = Containers::ArrayTuple{ + {NoInit, 4, transformations}, + {NoInit, 2, components} + }; + transformations[0] = {1, T{TT::translation({U(3.0), U(2.0)})}}; + transformations[1] = {0, T{TT::rotation(Math::Deg(35.0))}}; + transformations[2] = {4, T{TT::translation({U(1.5), U(2.5)})* + TT::rotation(Math::Deg(-15.0))}}; + transformations[3] = {5, T{TT::rotation(Math::Deg(-15.0))* + TT::translation({U(1.5), U(2.5)})}}; + /* Object number 4 additionally has a scaling component (which isn't + representable with dual complex numbers). It currently doesn't get added + to the transformations returned from transformations2DInto() but that + may change in the future for dual complex numbers). The translation + component is then *assumed* to be equivalent to what's stored in the + Transformation field and so applied neither. Here it's different, and + that shouldn't affect anything. */ + components[0] = {4, {-1.5f, -2.5f}, {2.0f, 5.0f}}; + /* This is deliberately an error -- specifying a TRS for an object that + doesn't have a Transformation field. Since there's no fast way to check + for those and error/warn on those, they get just ignored. */ + components[1] = {2, {3.5f, -1.0f}, {1.0f, 1.5f}}; + + SceneData scene{SceneMappingType::UnsignedInt, 6, std::move(data), { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Transformation, + transformations.slice(&Transformation::object), + transformations.slice(&Transformation::transformation)}, + SceneFieldData{SceneField::Translation, + components.slice(&Component::object), + components.slice(&Component::translation)}, + SceneFieldData{SceneField::Scaling, + components.slice(&Component::object), + components.slice(&Component::scaling)}, + }}; + + CORRADE_VERIFY(scene.is2D()); + CORRADE_VERIFY(!scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), (Containers::arrayView>({ + {1, Matrix3::translation({3.0f, 2.0f})}, + {0, Matrix3::rotation(35.0_degf)}, + {4, Matrix3::translation({1.5f, 2.5f})*Matrix3::rotation(-15.0_degf)}, + {5, Matrix3::rotation(-15.0_degf)*Matrix3::translation({1.5f, 2.5f})} + })), TestSuite::Compare::Container); +} + +template void SceneDataTest::transformations2DAsArrayTRS() { + setTestCaseTemplateName({NameTraits::name(), NameTraits::name(), NameTraits::name()}); + + struct Field { + UnsignedInt object; + Math::Vector2 translation; + Math::Complex rotation; + Math::Vector2 scaling; + } fields[]{ + {1, {T(3.0), T(2.0)}, + {}, + {V(1.0), V(1.0)}}, + {0, {}, + Math::Complex::rotation(Math::Deg{U(35.0)}), + {V(1.0), V(1.0)}}, + {2, {}, /* Identity transformation here */ + {}, + {V(1.0), V(1.0)}}, + {4, {}, + {}, + {V(2.0), V(1.0)}}, + {7, {T(1.5), T(2.5)}, + Math::Complex::rotation(Math::Deg{U(-15.0)}), + {V(-0.5), V(4.0)}}, + }; + + Containers::StridedArrayView1D view = fields; + + SceneFieldData translation{SceneField::Translation, + view.slice(&Field::object), + view.slice(&Field::translation)}; + SceneFieldData rotation{SceneField::Rotation, + view.slice(&Field::object), + view.slice(&Field::rotation)}; + SceneFieldData scaling{SceneField::Scaling, + view.slice(&Field::object), + view.slice(&Field::scaling)}; + + /* Just one of translation / rotation / scaling */ + { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + translation + }}; + CORRADE_VERIFY(scene.is2D()); + CORRADE_VERIFY(!scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), (Containers::arrayView>({ + {1, Matrix3::translation({3.0f, 2.0f})}, + {0, Matrix3{Math::IdentityInit}}, + {2, Matrix3{Math::IdentityInit}}, + {4, Matrix3{Math::IdentityInit}}, + {7, Matrix3::translation({1.5f, 2.5f})} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>>({ + {1, {{3.0f, 2.0f}, {}, Vector2{1.0f}}}, + {0, {{}, {}, Vector2{1.0f}}}, + {2, {{}, {}, Vector2{1.0f}}}, + {4, {{}, {}, Vector2{1.0f}}}, + {7, {{1.5f, 2.5f}, {}, Vector2{1.0f}}}, + })), TestSuite::Compare::Container); + } { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + rotation + }}; + CORRADE_VERIFY(scene.is2D()); + CORRADE_VERIFY(!scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), (Containers::arrayView>({ + {1, Matrix3{Math::IdentityInit}}, + {0, Matrix3::rotation(35.0_degf)}, + {2, Matrix3{Math::IdentityInit}}, + {4, Matrix3{Math::IdentityInit}}, + {7, Matrix3::rotation(-15.0_degf)} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>>({ + {1, {{}, {}, Vector2{1.0f}}}, + {0, {{}, Complex::rotation(35.0_degf), Vector2{1.0f}}}, + {2, {{}, {}, Vector2{1.0f}}}, + {4, {{}, {}, Vector2{1.0f}}}, + {7, {{}, Complex::rotation(-15.0_degf), Vector2{1.0f}}}, + })), TestSuite::Compare::Container); + } { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + scaling + }}; + CORRADE_VERIFY(scene.is2D()); + CORRADE_VERIFY(!scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), (Containers::arrayView>({ + {1, Matrix3{Math::IdentityInit}}, + {0, Matrix3{Math::IdentityInit}}, + {2, Matrix3{Math::IdentityInit}}, + {4, Matrix3::scaling({2.0f, 1.0f})}, + {7, Matrix3::scaling({-0.5f, 4.0f})} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>>({ + {1, {{}, {}, Vector2{1.0f}}}, + {0, {{}, {}, Vector2{1.0f}}}, + {2, {{}, {}, Vector2{1.0f}}}, + {4, {{}, {}, {2.0f, 1.0f}}}, + {7, {{}, {}, {-0.5f, 4.0f}}}, + })), TestSuite::Compare::Container); + } + + /* Pairs */ + { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + translation, + rotation + }}; + CORRADE_VERIFY(scene.is2D()); + CORRADE_VERIFY(!scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), (Containers::arrayView>({ + {1, Matrix3::translation({3.0f, 2.0f})}, + {0, Matrix3::rotation(35.0_degf)}, + {2, Matrix3{Math::IdentityInit}}, + {4, Matrix3{Math::IdentityInit}}, + {7, Matrix3::translation({1.5f, 2.5f})*Matrix3::rotation({-15.0_degf})} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>>({ + {1, {{3.0f, 2.0f}, {}, Vector2{1.0f}}}, + {0, {{}, Complex::rotation(35.0_degf), Vector2{1.0f}}}, + {2, {{}, {}, Vector2{1.0f}}}, + {4, {{}, {}, Vector2{1.0f}}}, + {7, {{1.5f, 2.5f}, Complex::rotation(-15.0_degf), Vector2{1.0f}}}, + })), TestSuite::Compare::Container); + } { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + translation, + scaling + }}; + CORRADE_VERIFY(scene.is2D()); + CORRADE_VERIFY(!scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), (Containers::arrayView>({ + {1, Matrix3::translation({3.0f, 2.0f})}, + {0, Matrix3{Math::IdentityInit}}, + {2, Matrix3{Math::IdentityInit}}, + {4, Matrix3::scaling({2.0f, 1.0f})}, + {7, Matrix3::translation({1.5f, 2.5f})*Matrix3::scaling({-0.5f, 4.0f})} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>>({ + {1, {{3.0f, 2.0f}, {}, Vector2{1.0f}}}, + {0, {{}, {}, Vector2{1.0f}}}, + {2, {{}, {}, Vector2{1.0f}}}, + {4, {{}, {}, {2.0f, 1.0f}}}, + {7, {{1.5f, 2.5f}, {}, {-0.5f, 4.0f}}}, + })), TestSuite::Compare::Container); + } { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + rotation, + scaling + }}; + CORRADE_VERIFY(scene.is2D()); + CORRADE_VERIFY(!scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), (Containers::arrayView>({ + {1, Matrix3{Math::IdentityInit}}, + {0, Matrix3::rotation(35.0_degf)}, + {2, Matrix3{Math::IdentityInit}}, + {4, Matrix3::scaling({2.0f, 1.0f})}, + {7, Matrix3::rotation({-15.0_degf})*Matrix3::scaling({-0.5f, 4.0f})} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>>({ + {1, {{}, {}, Vector2{1.0f}}}, + {0, {{}, Complex::rotation(35.0_degf), Vector2{1.0f}}}, + {2, {{}, {}, Vector2{1.0f}}}, + {4, {{}, {}, {2.0f, 1.0f}}}, + {7, {{}, Complex::rotation(-15.0_degf), {-0.5f, 4.0f}}}, + })), TestSuite::Compare::Container); + } + + /* All */ + { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + translation, + rotation, + scaling + }}; + CORRADE_VERIFY(scene.is2D()); + CORRADE_VERIFY(!scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), (Containers::arrayView>({ + {1, Matrix3::translation({3.0f, 2.0f})}, + {0, Matrix3::rotation(35.0_degf)}, + {2, Matrix3{Math::IdentityInit}}, + {4, Matrix3::scaling({2.0f, 1.0f})}, + {7, Matrix3::translation({1.5f, 2.5f})*Matrix3::rotation({-15.0_degf})*Matrix3::scaling({-0.5f, 4.0f})} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>>({ + {1, {{3.0f, 2.0f}, {}, Vector2{1.0f}}}, + {0, {{}, Complex::rotation(35.0_degf), Vector2{1.0f}}}, + {2, {{}, {}, Vector2{1.0f}}}, + {4, {{}, {}, {2.0f, 1.0f}}}, + {7, {{1.5f, 2.5f}, Complex::rotation(-15.0_degf), {-0.5f, 4.0f}}}, + })), TestSuite::Compare::Container); + } +} + +void SceneDataTest::transformations2DAsArrayBut3DType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + /* Because TRSAsArray() allocates an Array and then calls + TRSInto(), which skips views that are nullptr, we wouldn't get the + assertion for translations, as those are at offset 0, which would be + interpreted as an empty view if there were no elements. Thus using + rotations instead. */ + SceneData scene{SceneMappingType::UnsignedInt, 0, nullptr, { + SceneFieldData{SceneField::Rotation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Quaternion, nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.transformations2DAsArray(); + scene.translationsRotationsScalings2DAsArray(); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::transformations2DInto(): scene has a 3D transformation type\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): scene has a 3D transformation type\n"); +} + +void SceneDataTest::transformations2DIntoArray() { + auto&& data = IntoArrayOffset1Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + Matrix3 transformation; + } fields[] { + {1, Matrix3::translation({3.0f, 2.0f})*Matrix3::scaling({1.5f, 2.0f})}, + {0, Matrix3::rotation(35.0_degf)}, + {4, Matrix3::translation({3.0f, 2.0f})*Matrix3::rotation(35.0_degf)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Transformation, + view.slice(&Field::object), + view.slice(&Field::transformation)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt mapping[3]; + Matrix3 field[3]; + scene.transformations2DInto( + data.mapping ? Containers::arrayView(mapping) : nullptr, + data.field ? Containers::arrayView(field) : nullptr + ); + if(data.mapping) CORRADE_COMPARE_AS(Containers::stridedArrayView(mapping), + view.slice(&Field::object), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(Containers::stridedArrayView(field), + view.slice(&Field::transformation), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array mapping{data.size}; + Containers::Array field{data.size}; + CORRADE_COMPARE(scene.transformations2DInto(data.offset, + data.mapping ? arrayView(mapping) : nullptr, + data.field ? arrayView(field) : nullptr + ), data.expectedSize); + if(data.mapping) CORRADE_COMPARE_AS(mapping.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(field.prefix(data.expectedSize), + view.slice(&Field::transformation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::transformations2DTRSIntoArray() { + auto&& data = IntoArrayOffset1Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Same APIs tested as in transformations2DIntoArray(), but with a TRS + input */ + + struct Field { + UnsignedInt object; + Vector2 translation; + Complex rotation; + Vector2 scaling; + } fields[] { + {1, {3.0f, 2.0f}, {}, {1.5f, 2.0f}}, + {0, {}, Complex::rotation(35.0_degf), {1.0f, 1.0f}}, + {4, {3.0f, 2.0f}, Complex::rotation(35.0_degf), {1.0f, 1.0f}} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Translation, + view.slice(&Field::object), + view.slice(&Field::translation)}, + SceneFieldData{SceneField::Rotation, + view.slice(&Field::object), + view.slice(&Field::rotation)}, + SceneFieldData{SceneField::Scaling, + view.slice(&Field::object), + view.slice(&Field::scaling)}, + }}; + + Matrix3 expected[]{ + Matrix3::translation({3.0f, 2.0f})*Matrix3::scaling({1.5f, 2.0f}), + Matrix3::rotation(35.0_degf), + Matrix3::translation({3.0f, 2.0f})*Matrix3::rotation(35.0_degf) + }; + + /* The offset-less overload should give back all data */ + { + UnsignedInt mapping[3]; + Matrix3 field[3]; + scene.transformations2DInto( + data.mapping ? Containers::arrayView(mapping) : nullptr, + data.field ? Containers::arrayView(field) : nullptr + ); + if(data.mapping) CORRADE_COMPARE_AS(Containers::stridedArrayView(mapping), + view.slice(&Field::object), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(Containers::stridedArrayView(field), + Containers::arrayView(expected), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array mapping{data.size}; + Containers::Array field{data.size}; + CORRADE_COMPARE(scene.transformations2DInto(data.offset, + data.mapping ? arrayView(mapping) : nullptr, + data.field ? arrayView(field) : nullptr + ), data.expectedSize); + if(data.mapping) CORRADE_COMPARE_AS(mapping.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(field.prefix(data.expectedSize), + Containers::arrayView(expected).slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::transformations2DIntoArrayTRS() { + auto&& data = IntoArrayOffset3Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + Vector2 translation; + Complex rotation; + Vector2 scaling; + } fields[] { + {1, {3.0f, 2.0f}, {}, {1.5f, 2.0f}}, + {0, {}, Complex::rotation(35.0_degf), {1.0f, 1.0f}}, + {4, {3.0f, 2.0f}, Complex::rotation(35.0_degf), {1.0f, 1.0f}} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Translation, + view.slice(&Field::object), + view.slice(&Field::translation)}, + SceneFieldData{SceneField::Rotation, + view.slice(&Field::object), + view.slice(&Field::rotation)}, + SceneFieldData{SceneField::Scaling, + view.slice(&Field::object), + view.slice(&Field::scaling)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt mapping[3]; + Vector2 translations[3]; + Complex rotations[3]; + Vector2 scalings[3]; + scene.translationsRotationsScalings2DInto( + data.mapping ? Containers::arrayView(mapping) : nullptr, + data.field1 ? Containers::arrayView(translations) : nullptr, + data.field2 ? Containers::arrayView(rotations) : nullptr, + data.field3 ? Containers::arrayView(scalings) : nullptr + ); + if(data.mapping) CORRADE_COMPARE_AS(Containers::stridedArrayView(mapping), + view.slice(&Field::object), + TestSuite::Compare::Container); + if(data.field1) CORRADE_COMPARE_AS(Containers::stridedArrayView(translations), + view.slice(&Field::translation), + TestSuite::Compare::Container); + if(data.field2) CORRADE_COMPARE_AS(Containers::stridedArrayView(rotations), + view.slice(&Field::rotation), + TestSuite::Compare::Container); + if(data.field3) CORRADE_COMPARE_AS(Containers::stridedArrayView(scalings), + view.slice(&Field::scaling), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array mapping{data.size}; + Containers::Array translations{data.size}; + Containers::Array rotations{data.size}; + Containers::Array scalings{data.size}; + CORRADE_COMPARE(scene.translationsRotationsScalings2DInto(data.offset, + data.mapping ? arrayView(mapping) : nullptr, + data.field1 ? arrayView(translations) : nullptr, + data.field2 ? arrayView(rotations) : nullptr, + data.field3 ? arrayView(scalings) : nullptr + ), data.expectedSize); + if(data.mapping) CORRADE_COMPARE_AS(mapping.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field1) CORRADE_COMPARE_AS(translations.prefix(data.expectedSize), + view.slice(&Field::translation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field2) CORRADE_COMPARE_AS(rotations.prefix(data.expectedSize), + view.slice(&Field::rotation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field3) CORRADE_COMPARE_AS(scalings.prefix(data.expectedSize), + view.slice(&Field::scaling) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::transformations2DIntoArrayInvalidSizeOrOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + Matrix3 transformation; + } fields[3]; /* GCC 4.8 ICEs if I do a {} here */ + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Transformation, view.slice(&Field::object), view.slice(&Field::transformation)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt mappingDestinationCorrect[3]; + UnsignedInt mappingDestination[2]; + Matrix3 fieldDestinationCorrect[3]; + Matrix3 fieldDestination[2]; + scene.transformations2DInto(mappingDestination, fieldDestinationCorrect); + scene.transformations2DInto(mappingDestinationCorrect, fieldDestination); + scene.transformations2DInto(4, mappingDestination, fieldDestination); + scene.transformations2DInto(0, mappingDestinationCorrect, fieldDestination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::transformations2DInto(): expected mapping destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::transformations2DInto(): expected field destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::transformations2DInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::transformations2DInto(): mapping and field destination views have different size, 3 vs 2\n"); +} + +void SceneDataTest::transformations2DIntoArrayInvalidSizeOrOffsetTRS() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + Vector2 translation; + } fields[3]; /* GCC 4.8 ICEs if I do a {} here */ + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Translation, view.slice(&Field::object), view.slice(&Field::translation)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt mappingDestinationCorrect[3]; + UnsignedInt mappingDestination[2]; + Vector2 translationDestinationCorrect[3]; + Vector2 translationDestination[2]; + Complex rotationDestinationCorrect[3]; + Complex rotationDestination[2]; + Vector2 scalingDestinationCorrect[3]; + Vector2 scalingDestination[2]; + scene.translationsRotationsScalings2DInto(mappingDestination, translationDestinationCorrect, rotationDestinationCorrect, scalingDestinationCorrect); + scene.translationsRotationsScalings2DInto(mappingDestinationCorrect, translationDestination, rotationDestinationCorrect, scalingDestinationCorrect); + scene.translationsRotationsScalings2DInto(mappingDestinationCorrect, translationDestinationCorrect, rotationDestination, scalingDestinationCorrect); + scene.translationsRotationsScalings2DInto(mappingDestinationCorrect, translationDestinationCorrect, rotationDestinationCorrect, scalingDestination); + scene.translationsRotationsScalings2DInto(4, mappingDestination, translationDestination, rotationDestination, scalingDestination); + scene.translationsRotationsScalings2DInto(0, mappingDestinationCorrect, translationDestination, nullptr, nullptr); + scene.translationsRotationsScalings2DInto(0, mappingDestinationCorrect, nullptr, rotationDestination, nullptr); + scene.translationsRotationsScalings2DInto(0, mappingDestinationCorrect, nullptr, nullptr, scalingDestination); + scene.translationsRotationsScalings2DInto(0, nullptr, translationDestinationCorrect, rotationDestination, nullptr); + scene.translationsRotationsScalings2DInto(0, nullptr, translationDestinationCorrect, nullptr, scalingDestination); + scene.translationsRotationsScalings2DInto(0, nullptr, nullptr, rotationDestinationCorrect, scalingDestination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::translationsRotationsScalings2DInto(): expected mapping destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): expected translation destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): expected rotation destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): expected scaling destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): mapping and translation destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): mapping and rotation destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): mapping and scaling destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): translation and rotation destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): translation and scaling destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): rotation and scaling destination views have different size, 3 vs 2\n"); +} + +template void SceneDataTest::transformations3DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + typedef typename T::Type U; + typedef typename TransformationTypeFor::Type TT; + + struct Transformation { + UnsignedInt object; + T transformation; + }; + + struct Component { + UnsignedInt object; + Vector3 translation; + Vector3 scaling; + }; + + Containers::StridedArrayView1D transformations; + Containers::StridedArrayView1D components; + Containers::Array data = Containers::ArrayTuple{ + {NoInit, 4, transformations}, + {NoInit, 2, components} + }; + transformations[0] = {1, T{TT::translation({U(3.0), U(2.0), U(-0.5)})}}; + transformations[1] = {0, T{TT::rotation(Math::Deg(35.0), + Math::Vector3::yAxis())}}; + transformations[2] = {4, T{TT::translation({U(1.5), U(2.5), U(0.75)})* + TT::rotation(Math::Deg(-15.0), + Math::Vector3::xAxis())}}; + transformations[3] = {5, T{TT::rotation(Math::Deg(-15.0), + Math::Vector3::xAxis())* + TT::translation({U(1.5), U(2.5), U(0.75)})}}; + /* Object number 4 additionally has a scaling component (which isn't + representable with dual quaternions). It currently doesn't get added + to the transformations returned from transformations2DInto() but that + may change in the future for dual quaternions). The translation + component is then *assumed* to be equivalent to what's stored in the + Transformation field and so applied neither. Here it's different, and + that shouldn't affect anything. */ + components[0] = {4, {-1.5f, -2.5f, 5.5f}, {2.0f, 5.0f, 3.0f}}; + /* This is deliberately an error -- specifying a TRS for an object that + doesn't have a Transformation field. Since there's no fast way to check + for those and error/warn on those, they get just ignored. */ + components[1] = {2, {3.5f, -1.0f, 2.2f}, {1.0f, 1.5f, 1.0f}}; + + SceneData scene{SceneMappingType::UnsignedInt, 6, std::move(data), { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Transformation, + transformations.slice(&Transformation::object), + transformations.slice(&Transformation::transformation)}, + SceneFieldData{SceneField::Translation, + components.slice(&Component::object), + components.slice(&Component::translation)}, + SceneFieldData{SceneField::Scaling, + components.slice(&Component::object), + components.slice(&Component::scaling)}, + }}; + + CORRADE_VERIFY(!scene.is2D()); + CORRADE_VERIFY(scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), (Containers::arrayView>({ + {1, Matrix4::translation({3.0f, 2.0f, -0.5f})}, + {0, Matrix4::rotationY(35.0_degf)}, + {4, Matrix4::translation({1.5f, 2.5f, 0.75f})*Matrix4::rotationX(-15.0_degf)}, + {5, Matrix4::rotationX(-15.0_degf)*Matrix4::translation({1.5f, 2.5f, 0.75f})} + })), TestSuite::Compare::Container); +} + +template void SceneDataTest::transformations3DAsArrayTRS() { + setTestCaseTemplateName({NameTraits::name(), NameTraits::name(), NameTraits::name()}); + + struct Field { + UnsignedInt object; + Math::Vector3 translation; + Math::Quaternion rotation; + Math::Vector3 scaling; + } fields[]{ + {1, {T(3.0), T(2.0), T(1.0)}, + {}, + {V(1.0), V(1.0), V(1.0)}}, + {0, {}, + Math::Quaternion::rotation(Math::Deg{U(35.0)}, Math::Vector3::yAxis()), + {V(1.0), V(1.0), V(1.0)}}, + {2, {}, /* Identity transformation here */ + {}, + {V(1.0), V(1.0), V(1.0)}}, + {4, {}, + {}, + {V(2.0), V(1.0), V(0.0)}}, + {7, {T(1.5), T(2.5), T(3.5)}, + Math::Quaternion::rotation(Math::Deg{U(-15.0)}, Math::Vector3::xAxis()), + {V(-0.5), V(4.0), V(-16.0)}}, + }; + + Containers::StridedArrayView1D view = fields; + + SceneFieldData translation{SceneField::Translation, + view.slice(&Field::object), + view.slice(&Field::translation)}; + SceneFieldData rotation{SceneField::Rotation, + view.slice(&Field::object), + view.slice(&Field::rotation)}; + SceneFieldData scaling{SceneField::Scaling, + view.slice(&Field::object), + view.slice(&Field::scaling)}; + + /* Just one of translation / rotation / scaling */ + { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + translation + }}; + CORRADE_VERIFY(!scene.is2D()); + CORRADE_VERIFY(scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), (Containers::arrayView>({ + {1, Matrix4::translation({3.0f, 2.0, 1.0f})}, + {0, Matrix4{Math::IdentityInit}}, + {2, Matrix4{Math::IdentityInit}}, + {4, Matrix4{Math::IdentityInit}}, + {7, Matrix4::translation({1.5f, 2.5f, 3.5f})} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>>({ + {1, {{3.0f, 2.0, 1.0f}, {}, Vector3{1.0f}}}, + {0, {{}, {}, Vector3{1.0f}}}, + {2, {{}, {}, Vector3{1.0f}}}, + {4, {{}, {}, Vector3{1.0f}}}, + {7, {{1.5f, 2.5f, 3.5f}, {}, Vector3{1.0f}}}, + })), TestSuite::Compare::Container); + } { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + rotation + }}; + CORRADE_VERIFY(!scene.is2D()); + CORRADE_VERIFY(scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), (Containers::arrayView>({ + {1, Matrix4{Math::IdentityInit}}, + {0, Matrix4::rotationY(35.0_degf)}, + {2, Matrix4{Math::IdentityInit}}, + {4, Matrix4{Math::IdentityInit}}, + {7, Matrix4::rotationX(-15.0_degf)} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>>({ + {1, {{}, {}, Vector3{1.0f}}}, + {0, {{}, Quaternion::rotation(35.0_degf, Vector3::yAxis()), Vector3{1.0f}}}, + {2, {{}, {}, Vector3{1.0f}}}, + {4, {{}, {}, Vector3{1.0f}}}, + {7, {{}, Quaternion::rotation(-15.0_degf, Vector3::xAxis()), Vector3{1.0f}}}, + })), TestSuite::Compare::Container); + } { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + scaling + }}; + CORRADE_VERIFY(!scene.is2D()); + CORRADE_VERIFY(scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), (Containers::arrayView>({ + {1, Matrix4{Math::IdentityInit}}, + {0, Matrix4{Math::IdentityInit}}, + {2, Matrix4{Math::IdentityInit}}, + {4, Matrix4::scaling({2.0f, 1.0f, 0.0f})}, + {7, Matrix4::scaling({-0.5f, 4.0f, -16.0f})} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>>({ + {1, {{}, {}, Vector3{1.0f}}}, + {0, {{}, {}, Vector3{1.0f}}}, + {2, {{}, {}, Vector3{1.0f}}}, + {4, {{}, {}, {2.0f, 1.0f, 0.0f}}}, + {7, {{}, {}, {-0.5f, 4.0f, -16.0f}}}, + })), TestSuite::Compare::Container); + } + + /* Pairs */ + { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + translation, + rotation + }}; + CORRADE_VERIFY(!scene.is2D()); + CORRADE_VERIFY(scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), (Containers::arrayView>({ + {1, Matrix4::translation({3.0f, 2.0, 1.0f})}, + {0, Matrix4::rotationY(35.0_degf)}, + {2, Matrix4{Math::IdentityInit}}, + {4, Matrix4{Math::IdentityInit}}, + {7, Matrix4::translation({1.5f, 2.5f, 3.5f})*Matrix4::rotationX(-15.0_degf)} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>>({ + {1, {{3.0f, 2.0, 1.0f}, {}, Vector3{1.0f}}}, + {0, {{}, Quaternion::rotation(35.0_degf, Vector3::yAxis()), Vector3{1.0f}}}, + {2, {{}, {}, Vector3{1.0f}}}, + {4, {{}, {}, Vector3{1.0f}}}, + {7, {{1.5f, 2.5f, 3.5f}, Quaternion::rotation(-15.0_degf, Vector3::xAxis()), Vector3{1.0f}}}, + })), TestSuite::Compare::Container); + } { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + translation, + scaling + }}; + CORRADE_VERIFY(!scene.is2D()); + CORRADE_VERIFY(scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), (Containers::arrayView>({ + {1, Matrix4::translation({3.0f, 2.0, 1.0f})}, + {0, Matrix4{Math::IdentityInit}}, + {2, Matrix4{Math::IdentityInit}}, + {4, Matrix4::scaling({2.0f, 1.0f, 0.0f})}, + {7, Matrix4::translation({1.5f, 2.5f, 3.5f})*Matrix4::scaling({-0.5f, 4.0f, -16.0f})} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>>({ + {1, {{3.0f, 2.0, 1.0f}, {}, Vector3{1.0f}}}, + {0, {{}, {}, Vector3{1.0f}}}, + {2, {{}, {}, Vector3{1.0f}}}, + {4, {{}, {}, {2.0f, 1.0f, 0.0f}}}, + {7, {{1.5f, 2.5f, 3.5f}, {}, {-0.5f, 4.0f, -16.0f}}}, + })), TestSuite::Compare::Container); + } { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + rotation, + scaling + }}; + CORRADE_VERIFY(!scene.is2D()); + CORRADE_VERIFY(scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), (Containers::arrayView>({ + {1, Matrix4{Math::IdentityInit}}, + {0, Matrix4::rotationY(35.0_degf)}, + {2, Matrix4{Math::IdentityInit}}, + {4, Matrix4::scaling({2.0f, 1.0f, 0.0f})}, + {7, Matrix4::rotationX({-15.0_degf})*Matrix4::scaling({-0.5f, 4.0f, -16.0f})} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>>({ + {1, {{}, {}, Vector3{1.0f}}}, + {0, {{}, Quaternion::rotation(35.0_degf, Vector3::yAxis()), Vector3{1.0f}}}, + {2, {{}, {}, Vector3{1.0f}}}, + {4, {{}, {}, {2.0f, 1.0f, 0.0f}}}, + {7, {{}, Quaternion::rotation(-15.0_degf, Vector3::xAxis()), {-0.5f, 4.0f, -16.0f}}}, + })), TestSuite::Compare::Container); + } + + /* All */ + { + SceneData scene{SceneMappingType::UnsignedInt, 8, {}, fields, { + translation, + rotation, + scaling + }}; + CORRADE_VERIFY(!scene.is2D()); + CORRADE_VERIFY(scene.is3D()); + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), (Containers::arrayView>({ + {1, Matrix4::translation({3.0f, 2.0, 1.0f})}, + {0, Matrix4::rotationY(35.0_degf)}, + {2, Matrix4{Math::IdentityInit}}, + {4, Matrix4::scaling({2.0f, 1.0f, 0.0f})}, + {7, Matrix4::translation({1.5f, 2.5f, 3.5f})*Matrix4::rotationX({-15.0_degf})*Matrix4::scaling({-0.5f, 4.0f, -16.0f})} + })), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>>({ + {1, {{3.0f, 2.0, 1.0f}, {}, Vector3{1.0f}}}, + {0, {{}, Quaternion::rotation(35.0_degf, Vector3::yAxis()), Vector3{1.0f}}}, + {2, {{}, {}, Vector3{1.0f}}}, + {4, {{}, {}, {2.0f, 1.0f, 0.0f}}}, + {7, {{1.5f, 2.5f, 3.5f}, Quaternion::rotation(-15.0_degf, Vector3::xAxis()), {-0.5f, 4.0f, -16.0f}}}, + })), TestSuite::Compare::Container); + } +} + +void SceneDataTest::transformations3DAsArrayBut2DType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + /* Because TRSAsArray() allocates an Array and then calls + TRSInto(), which skips views that are nullptr, we wouldn't get the + assertion for translations, as those are at offset 0, which would be + interpreted as an empty view if there were no elements. Thus using + rotations instead. */ + SceneData scene{SceneMappingType::UnsignedInt, 0, nullptr, { + SceneFieldData{SceneField::Rotation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Complex, nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.transformations3DAsArray(); + scene.translationsRotationsScalings3DAsArray(); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::transformations3DInto(): scene has a 2D transformation type\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): scene has a 2D transformation type\n"); +} + +void SceneDataTest::transformations3DIntoArray() { + auto&& data = IntoArrayOffset1Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + Matrix4 transformation; + } fields[] { + {1, Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::scaling({1.5f, 2.0f, 4.5f})}, + {0, Matrix4::rotationX(35.0_degf)}, + {4, Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::rotationX(35.0_degf)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Transformation, + view.slice(&Field::object), + view.slice(&Field::transformation)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt mapping[3]; + Matrix4 field[3]; + scene.transformations3DInto( + data.mapping ? Containers::arrayView(mapping) : nullptr, + data.field ? Containers::arrayView(field) : nullptr + ); + if(data.mapping) CORRADE_COMPARE_AS(Containers::stridedArrayView(mapping), + view.slice(&Field::object), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(Containers::stridedArrayView(field), + view.slice(&Field::transformation), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array mapping{data.size}; + Containers::Array field{data.size}; + CORRADE_COMPARE(scene.transformations3DInto(data.offset, + data.mapping ? arrayView(mapping) : nullptr, + data.field ? arrayView(field) : nullptr + ), data.expectedSize); + if(data.mapping) CORRADE_COMPARE_AS(mapping.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(field.prefix(data.expectedSize), + view.slice(&Field::transformation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::transformations3DTRSIntoArray() { + auto&& data = IntoArrayOffset1Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Same APIs tested as in transformations3DIntoArray(), but with a TRS + input */ + + struct Field { + UnsignedInt object; + Vector3 translation; + Quaternion rotation; + Vector3 scaling; + } fields[] { + {1, {3.0f, 2.0f, 1.0f}, {}, {1.5f, 2.0f, 4.5f}}, + {0, {}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), {1.0f, 1.0f, 1.0f}}, + {4, {3.0f, 2.0f, 1.0f}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), {1.0f, 1.0f, 1.0f}} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Translation, + view.slice(&Field::object), + view.slice(&Field::translation)}, + SceneFieldData{SceneField::Rotation, + view.slice(&Field::object), + view.slice(&Field::rotation)}, + SceneFieldData{SceneField::Scaling, + view.slice(&Field::object), + view.slice(&Field::scaling)}, + }}; + + Matrix4 expected[]{ + Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::scaling({1.5f, 2.0f, 4.5f}), + Matrix4::rotationX(35.0_degf), + Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::rotationX(35.0_degf) + }; + + /* The offset-less overload should give back all data */ + { + UnsignedInt mapping[3]; + Matrix4 field[3]; + scene.transformations3DInto( + data.mapping ? Containers::arrayView(mapping) : nullptr, + data.field ? Containers::arrayView(field) : nullptr + ); + if(data.mapping) CORRADE_COMPARE_AS(Containers::stridedArrayView(mapping), + view.slice(&Field::object), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(Containers::stridedArrayView(field), + Containers::arrayView(expected), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array mapping{data.size}; + Containers::Array field{data.size}; + CORRADE_COMPARE(scene.transformations3DInto(data.offset, + data.mapping ? arrayView(mapping) : nullptr, + data.field ? arrayView(field) : nullptr + ), data.expectedSize); + if(data.mapping) CORRADE_COMPARE_AS(mapping.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(field.prefix(data.expectedSize), + Containers::arrayView(expected).slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::transformations3DIntoArrayTRS() { + auto&& data = IntoArrayOffset3Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + Vector3 translation; + Quaternion rotation; + Vector3 scaling; + } fields[] { + {1, {3.0f, 2.0f, 1.0f}, {}, {1.5f, 2.0f, 4.5f}}, + {0, {}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), {1.0f, 1.0f, 1.0f}}, + {4, {3.0f, 2.0f, 1.0f}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), {1.0f, 1.0f, 1.0f}} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Translation, + view.slice(&Field::object), + view.slice(&Field::translation)}, + SceneFieldData{SceneField::Rotation, + view.slice(&Field::object), + view.slice(&Field::rotation)}, + SceneFieldData{SceneField::Scaling, + view.slice(&Field::object), + view.slice(&Field::scaling)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt mapping[3]; + Vector3 translations[3]; + Quaternion rotations[3]; + Vector3 scalings[3]; + scene.translationsRotationsScalings3DInto( + data.mapping ? Containers::arrayView(mapping) : nullptr, + data.field1 ? Containers::arrayView(translations) : nullptr, + data.field2 ? Containers::arrayView(rotations) : nullptr, + data.field3 ? Containers::arrayView(scalings) : nullptr + ); + if(data.mapping) CORRADE_COMPARE_AS(Containers::stridedArrayView(mapping), + view.slice(&Field::object), + TestSuite::Compare::Container); + if(data.field1) CORRADE_COMPARE_AS(Containers::stridedArrayView(translations), + view.slice(&Field::translation), + TestSuite::Compare::Container); + if(data.field2) CORRADE_COMPARE_AS(Containers::stridedArrayView(rotations), + view.slice(&Field::rotation), + TestSuite::Compare::Container); + if(data.field3) CORRADE_COMPARE_AS(Containers::stridedArrayView(scalings), + view.slice(&Field::scaling), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array mapping{data.size}; + Containers::Array translations{data.size}; + Containers::Array rotations{data.size}; + Containers::Array scalings{data.size}; + CORRADE_COMPARE(scene.translationsRotationsScalings3DInto(data.offset, + data.mapping ? arrayView(mapping) : nullptr, + data.field1 ? arrayView(translations) : nullptr, + data.field2 ? arrayView(rotations) : nullptr, + data.field3 ? arrayView(scalings) : nullptr + ), data.expectedSize); + if(data.mapping) CORRADE_COMPARE_AS(mapping.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field1) CORRADE_COMPARE_AS(translations.prefix(data.expectedSize), + view.slice(&Field::translation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field2) CORRADE_COMPARE_AS(rotations.prefix(data.expectedSize), + view.slice(&Field::rotation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field3) CORRADE_COMPARE_AS(scalings.prefix(data.expectedSize), + view.slice(&Field::scaling) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::transformations3DIntoArrayInvalidSizeOrOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + Matrix4 transformation; + } fields[3]; /* GCC 4.8 ICEs if I do a {} here */ + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Transformation, view.slice(&Field::object), view.slice(&Field::transformation)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt mappingDestinationCorrect[3]; + UnsignedInt mappingDestination[2]; + Matrix4 fieldDestinationCorrect[3]; + Matrix4 fieldDestination[2]; + scene.transformations3DInto(mappingDestination, fieldDestinationCorrect); + scene.transformations3DInto(mappingDestinationCorrect, fieldDestination); + scene.transformations3DInto(4, mappingDestination, fieldDestination); + scene.transformations3DInto(0, mappingDestinationCorrect, fieldDestination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::transformations3DInto(): expected mapping destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::transformations3DInto(): expected field destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::transformations3DInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::transformations3DInto(): mapping and field destination views have different size, 3 vs 2\n"); +} + +void SceneDataTest::transformations3DIntoArrayInvalidSizeOrOffsetTRS() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + Vector2 translation; + } fields[3]; /* GCC 4.8 ICEs if I do a {} here */ + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Translation, view.slice(&Field::object), view.slice(&Field::translation)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt mappingDestinationCorrect[3]; + UnsignedInt mappingDestination[2]; + Vector3 translationDestinationCorrect[3]; + Vector3 translationDestination[2]; + Quaternion rotationDestinationCorrect[3]; + Quaternion rotationDestination[2]; + Vector3 scalingDestinationCorrect[3]; + Vector3 scalingDestination[2]; + scene.translationsRotationsScalings3DInto(mappingDestination, translationDestinationCorrect, rotationDestinationCorrect, scalingDestinationCorrect); + scene.translationsRotationsScalings3DInto(mappingDestinationCorrect, translationDestination, rotationDestinationCorrect, scalingDestinationCorrect); + scene.translationsRotationsScalings3DInto(mappingDestinationCorrect, translationDestinationCorrect, rotationDestination, scalingDestinationCorrect); + scene.translationsRotationsScalings3DInto(mappingDestinationCorrect, translationDestinationCorrect, rotationDestinationCorrect, scalingDestination); + scene.translationsRotationsScalings3DInto(4, mappingDestination, translationDestination, rotationDestination, scalingDestination); + scene.translationsRotationsScalings3DInto(0, mappingDestinationCorrect, translationDestination, nullptr, nullptr); + scene.translationsRotationsScalings3DInto(0, mappingDestinationCorrect, nullptr, rotationDestination, nullptr); + scene.translationsRotationsScalings3DInto(0, mappingDestinationCorrect, nullptr, nullptr, scalingDestination); + scene.translationsRotationsScalings3DInto(0, nullptr, translationDestinationCorrect, rotationDestination, nullptr); + scene.translationsRotationsScalings3DInto(0, nullptr, translationDestinationCorrect, nullptr, scalingDestination); + scene.translationsRotationsScalings3DInto(0, nullptr, nullptr, rotationDestinationCorrect, scalingDestination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::translationsRotationsScalings3DInto(): expected mapping destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): expected translation destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): expected rotation destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): expected scaling destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): mapping and translation destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): mapping and rotation destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): mapping and scaling destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): translation and rotation destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): translation and scaling destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): rotation and scaling destination views have different size, 3 vs 2\n"); +} + +template void SceneDataTest::meshesMaterialsAsArray() { + setTestCaseTemplateName({NameTraits::name(), NameTraits::name()}); + + struct Field { + UnsignedByte object; + T mesh; + U meshMaterial; + } fields[]{ + {0, T(15), U(3)}, + {1, T(37), U(-1)}, + {15, T(44), U(25)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneFieldData meshes{SceneField::Mesh, + view.slice(&Field::object), + view.slice(&Field::mesh)}; + SceneFieldData meshMaterials{SceneField::MeshMaterial, + view.slice(&Field::object), + view.slice(&Field::meshMaterial)}; + + /* Both meshes and materials */ + { + SceneData scene{SceneMappingType::UnsignedByte, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + meshes, + meshMaterials + }}; + + CORRADE_COMPARE_AS(scene.meshesMaterialsAsArray(), (Containers::arrayView>>({ + {0, {15, 3}}, + {1, {37, -1}}, + {15, {44, 25}} + })), TestSuite::Compare::Container); + + /* Only meshes */ + } { + SceneData scene{SceneMappingType::UnsignedByte, 50, {}, fields, { + meshes + }}; + + CORRADE_COMPARE_AS(scene.meshesMaterialsAsArray(), (Containers::arrayView>>({ + {0, {15, -1}}, + {1, {37, -1}}, + {15, {44, -1}} + })), TestSuite::Compare::Container); + } +} + +void SceneDataTest::meshesMaterialsIntoArray() { + auto&& data = IntoArrayOffset2Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + Int meshMaterial; + } fields[]{ + {1, 15, 3}, + {0, 37, -1}, + {4, 44, 22} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, + view.slice(&Field::object), + view.slice(&Field::mesh)}, + SceneFieldData{SceneField::MeshMaterial, + view.slice(&Field::object), + view.slice(&Field::meshMaterial)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt mapping[3]; + UnsignedInt meshes[3]; + Int meshMaterials[3]; + scene.meshesMaterialsInto( + data.mapping ? Containers::arrayView(mapping) : nullptr, + data.field1 ? Containers::arrayView(meshes) : nullptr, + data.field2 ? Containers::arrayView(meshMaterials) : nullptr + ); + if(data.mapping) CORRADE_COMPARE_AS(Containers::stridedArrayView(mapping), + view.slice(&Field::object), + TestSuite::Compare::Container); + if(data.field1) CORRADE_COMPARE_AS(Containers::stridedArrayView(meshes), + view.slice(&Field::mesh), + TestSuite::Compare::Container); + if(data.field2) CORRADE_COMPARE_AS(Containers::stridedArrayView(meshMaterials), + view.slice(&Field::meshMaterial), + TestSuite::Compare::Container); + + /* The offset variant should give back only a subset */ + } { + Containers::Array mapping{data.size}; + Containers::Array meshes{data.size}; + Containers::Array meshMaterials{data.size}; + CORRADE_COMPARE(scene.meshesMaterialsInto(data.offset, + data.mapping ? arrayView(mapping) : nullptr, + data.field1 ? arrayView(meshes) : nullptr, + data.field2 ? arrayView(meshMaterials) : nullptr + ), data.expectedSize); + if(data.mapping) CORRADE_COMPARE_AS(mapping.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field1) CORRADE_COMPARE_AS(meshes.prefix(data.expectedSize), + view.slice(&Field::mesh) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field2) CORRADE_COMPARE_AS(meshMaterials.prefix(data.expectedSize), + view.slice(&Field::meshMaterial) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::meshesMaterialsIntoArrayInvalidSizeOrOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt mappingDestinationCorrect[3]; + UnsignedInt mappingDestination[2]; + UnsignedInt meshDestinationCorrect[3]; + UnsignedInt meshDestination[2]; + Int meshMaterialDestinationCorrect[3]; + Int meshMaterialDestination[2]; + scene.meshesMaterialsInto(mappingDestination, meshDestinationCorrect, meshMaterialDestinationCorrect); + scene.meshesMaterialsInto(mappingDestinationCorrect, meshDestination, meshMaterialDestinationCorrect); + scene.meshesMaterialsInto(mappingDestinationCorrect, meshDestinationCorrect, meshMaterialDestination); + scene.meshesMaterialsInto(4, mappingDestination, meshDestination, meshMaterialDestination); + scene.meshesMaterialsInto(0, mappingDestinationCorrect, meshDestination, nullptr); + scene.meshesMaterialsInto(0, mappingDestinationCorrect, nullptr, meshMaterialDestination); + scene.meshesMaterialsInto(0, nullptr, meshDestinationCorrect, meshMaterialDestination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::meshesMaterialsInto(): expected mapping destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::meshesMaterialsInto(): expected mesh destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::meshesMaterialsInto(): expected mesh material destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::meshesMaterialsInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::meshesMaterialsInto(): mapping and mesh destination views have different size, 3 vs 2\n" + "Trade::SceneData::meshesMaterialsInto(): mapping and mesh material destination views have different size, 3 vs 2\n" + "Trade::SceneData::meshesMaterialsInto(): mesh and mesh material destination views have different size, 3 vs 2\n"); +} + +template void SceneDataTest::lightsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + UnsignedByte object; + T light; + } fields[]{ + {0, T(15)}, + {1, T(37)}, + {15, T(44)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedByte, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Light, view.slice(&Field::object), view.slice(&Field::light)} + }}; + + CORRADE_COMPARE_AS(scene.lightsAsArray(), (Containers::arrayView>({ + {0, 15}, + {1, 37}, + {15, 44} + })), TestSuite::Compare::Container); +} + +void SceneDataTest::lightsIntoArray() { + auto&& data = IntoArrayOffset1Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt light; + } fields[] { + {1, 15}, + {0, 37}, + {4, 44} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Light, + view.slice(&Field::object), + view.slice(&Field::light)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt mapping[3]; + UnsignedInt field[3]; + scene.lightsInto( + data.mapping ? Containers::arrayView(mapping) : nullptr, + data.field ? Containers::arrayView(field) : nullptr + ); + if(data.mapping) CORRADE_COMPARE_AS(Containers::stridedArrayView(mapping), + view.slice(&Field::object), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(Containers::stridedArrayView(field), + view.slice(&Field::light), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array mapping{data.size}; + Containers::Array field{data.size}; + CORRADE_COMPARE(scene.lightsInto(data.offset, + data.mapping ? arrayView(mapping) : nullptr, + data.field ? arrayView(field) : nullptr + ), data.expectedSize); + if(data.mapping) CORRADE_COMPARE_AS(mapping.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(field.prefix(data.expectedSize), + view.slice(&Field::light) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::lightsIntoArrayInvalidSizeOrOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt light; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Light, view.slice(&Field::object), view.slice(&Field::light)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt mappingDestinationCorrect[3]; + UnsignedInt mappingDestination[2]; + UnsignedInt fieldDestinationCorrect[3]; + UnsignedInt fieldDestination[2]; + scene.lightsInto(mappingDestination, fieldDestinationCorrect); + scene.lightsInto(mappingDestinationCorrect, fieldDestination); + scene.lightsInto(4, mappingDestination, fieldDestination); + scene.lightsInto(0, mappingDestinationCorrect, fieldDestination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::lightsInto(): expected mapping destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::lightsInto(): expected field destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::lightsInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::lightsInto(): mapping and field destination views have different size, 3 vs 2\n"); +} + +template void SceneDataTest::camerasAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + UnsignedByte object; + T camera; + } fields[]{ + {0, T(15)}, + {1, T(37)}, + {15, T(44)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedByte, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Camera, view.slice(&Field::object), view.slice(&Field::camera)} + }}; + + CORRADE_COMPARE_AS(scene.camerasAsArray(), (Containers::arrayView>({ + {0, 15}, + {1, 37}, + {15, 44} + })), TestSuite::Compare::Container); +} + +void SceneDataTest::camerasIntoArray() { + auto&& data = IntoArrayOffset1Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt camera; + } fields[]{ + {1, 15}, + {0, 37}, + {4, 44} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Camera, + view.slice(&Field::object), + view.slice(&Field::camera)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt mapping[3]; + UnsignedInt field[3]; + scene.camerasInto( + data.mapping ? Containers::arrayView(mapping) : nullptr, + data.field ? Containers::arrayView(field) : nullptr + ); + if(data.mapping) CORRADE_COMPARE_AS(Containers::stridedArrayView(mapping), + view.slice(&Field::object), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(Containers::stridedArrayView(field), + view.slice(&Field::camera), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array mapping{data.size}; + Containers::Array field{data.size}; + CORRADE_COMPARE(scene.camerasInto(data.offset, + data.mapping ? arrayView(mapping) : nullptr, + data.field ? arrayView(field) : nullptr + ), data.expectedSize); + if(data.mapping) CORRADE_COMPARE_AS(mapping.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(field.prefix(data.expectedSize), + view.slice(&Field::camera) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::camerasIntoArrayInvalidSizeOrOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt camera; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Camera, view.slice(&Field::object), view.slice(&Field::camera)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt mappingDestinationCorrect[3]; + UnsignedInt mappingDestination[2]; + UnsignedInt fieldDestinationCorrect[3]; + UnsignedInt fieldDestination[2]; + scene.camerasInto(mappingDestination, fieldDestinationCorrect); + scene.camerasInto(mappingDestinationCorrect, fieldDestination); + scene.camerasInto(4, mappingDestination, fieldDestination); + scene.camerasInto(0, mappingDestinationCorrect, fieldDestination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::camerasInto(): expected mapping destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::camerasInto(): expected field destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::camerasInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::camerasInto(): mapping and field destination views have different size, 3 vs 2\n"); +} + +template void SceneDataTest::skinsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + UnsignedByte object; + T skin; + } fields[]{ + {0, T(15)}, + {1, T(37)}, + {15, T(44)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedByte, 50, {}, fields, { + /* To verify it isn't just picking the first ever field; also to + satisfy the requirement of having a transformation field to + disambiguate the dimensionality */ + SceneFieldData{SceneField::Translation, SceneMappingType::UnsignedByte, nullptr, SceneFieldType::Vector3, nullptr}, + SceneFieldData{SceneField::Skin, view.slice(&Field::object), view.slice(&Field::skin)} + }}; + + CORRADE_COMPARE_AS(scene.skinsAsArray(), (Containers::arrayView>({ + {0, 15}, + {1, 37}, + {15, 44} + })), TestSuite::Compare::Container); +} + +void SceneDataTest::skinsIntoArray() { + auto&& data = IntoArrayOffset1Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + struct Field { + UnsignedInt object; + UnsignedInt skin; + } fields[] { + {1, 15}, + {0, 37}, + {4, 44} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field; also to + satisfy the requirement of having a transformation field to + disambiguate the dimensionality */ + SceneFieldData{SceneField::Translation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Vector3, nullptr}, + SceneFieldData{SceneField::Skin, + view.slice(&Field::object), + view.slice(&Field::skin)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt mapping[3]; + UnsignedInt field[3]; + scene.skinsInto( + data.mapping ? Containers::arrayView(mapping) : nullptr, + data.field ? Containers::arrayView(field) : nullptr + ); + if(data.mapping) CORRADE_COMPARE_AS(Containers::stridedArrayView(mapping), + view.slice(&Field::object), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(Containers::stridedArrayView(field), + view.slice(&Field::skin), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array mapping{data.size}; + Containers::Array field{data.size}; + CORRADE_COMPARE(scene.skinsInto(data.offset, + data.mapping ? arrayView(mapping) : nullptr, + data.field ? arrayView(field) : nullptr + ), data.expectedSize); + if(data.mapping) CORRADE_COMPARE_AS(mapping.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(field.prefix(data.expectedSize), + view.slice(&Field::skin) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::skinsIntoArrayInvalidSizeOrOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt skin; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To satisfy the requirement of having a transformation field to + disambiguate the dimensionality */ + SceneFieldData{SceneField::Translation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Vector3, nullptr}, + SceneFieldData{SceneField::Skin, view.slice(&Field::object), view.slice(&Field::skin)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt mappingDestinationCorrect[3]; + UnsignedInt mappingDestination[2]; + UnsignedInt fieldDestinationCorrect[3]; + UnsignedInt fieldDestination[2]; + scene.skinsInto(mappingDestination, fieldDestinationCorrect); + scene.skinsInto(mappingDestinationCorrect, fieldDestination); + scene.skinsInto(4, mappingDestination, fieldDestination); + scene.skinsInto(0, mappingDestinationCorrect, fieldDestination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::skinsInto(): expected mapping destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::skinsInto(): expected field destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::skinsInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::skinsInto(): mapping and field destination views have different size, 3 vs 2\n"); +} + +template void SceneDataTest::importerStateAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + int a, b; + + struct Field { + UnsignedByte object; + T importerState; + } fields[]{ + {0, &a}, + {1, nullptr}, + {15, &b} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedByte, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::ImporterState, view.slice(&Field::object), view.slice(&Field::importerState)} + }}; + + CORRADE_COMPARE_AS(scene.importerStateAsArray(), (Containers::arrayView>({ + {0, &a}, + {1, nullptr}, + {15, &b} + })), TestSuite::Compare::Container); +} + +void SceneDataTest::importerStateIntoArray() { + auto&& data = IntoArrayOffset1Data[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* Both AsArray() and Into() share a common helper. The AsArray() test + above verified handling of various data types and this checks the + offset/size parameters of the Into() variant. */ + + int a, b; + + struct Field { + UnsignedInt object; + const void* importerState; + } fields[]{ + {1, &a}, + {0, nullptr}, + {4, &b} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::ImporterState, + view.slice(&Field::object), + view.slice(&Field::importerState)}, + }}; + + /* The offset-less overload should give back all data */ + { + UnsignedInt mapping[3]; + const void* field[3]; + scene.importerStateInto( + data.mapping ? Containers::arrayView(mapping) : nullptr, + data.field ? Containers::arrayView(field) : nullptr + ); + if(data.mapping) CORRADE_COMPARE_AS(Containers::stridedArrayView(mapping), + view.slice(&Field::object), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(Containers::stridedArrayView(field), + view.slice(&Field::importerState), + TestSuite::Compare::Container); + + /* The offset variant only a subset */ + } { + Containers::Array mapping{data.size}; + Containers::Array field{data.size}; + CORRADE_COMPARE(scene.importerStateInto(data.offset, + data.mapping ? arrayView(mapping) : nullptr, + data.field ? arrayView(field) : nullptr + ), data.expectedSize); + if(data.mapping) CORRADE_COMPARE_AS(mapping.prefix(data.expectedSize), + view.slice(&Field::object) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + if(data.field) CORRADE_COMPARE_AS(field.prefix(data.expectedSize), + view.slice(&Field::importerState) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + } +} + +void SceneDataTest::importerStateIntoArrayInvalidSizeOrOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + const void* importerState; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::ImporterState, view.slice(&Field::object), view.slice(&Field::importerState)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt mappingDestinationCorrect[3]; + UnsignedInt mappingDestination[2]; + const void* fieldDestinationCorrect[3]; + const void* fieldDestination[2]; + scene.importerStateInto(mappingDestination, fieldDestinationCorrect); + scene.importerStateInto(mappingDestinationCorrect, fieldDestination); + scene.importerStateInto(4, mappingDestination, fieldDestination); + scene.importerStateInto(0, mappingDestinationCorrect, fieldDestination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::importerStateInto(): expected mapping destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::importerStateInto(): expected field destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::importerStateInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::importerStateInto(): mapping and field destination views have different size, 3 vs 2\n"); +} + +void SceneDataTest::mutableAccessNotAllowed() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + const struct Field { + UnsignedInt object; + UnsignedShort foobar; + UnsignedShort mesh; + } fields[2]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, {}, fields, { + SceneFieldData{sceneFieldCustom(35), + view.slice(&Field::object), + view.slice(&Field::foobar)}, + SceneFieldData{SceneField::Mesh, + view.slice(&Field::object), + view.slice(&Field::mesh)}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.mutableData(); + scene.mutableMapping(0); + scene.mutableMapping(0); + scene.mutableMapping(SceneField::Mesh); + scene.mutableMapping(SceneField::Mesh); + scene.mutableField(0); + scene.mutableField(0); + scene.mutableField(1); + scene.mutableField(SceneField::Mesh); + scene.mutableField(SceneField::Mesh); + scene.mutableField(sceneFieldCustom(35)); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::mutableData(): data not mutable\n" + "Trade::SceneData::mutableMapping(): data not mutable\n" + "Trade::SceneData::mutableMapping(): data not mutable\n" + "Trade::SceneData::mutableMapping(): data not mutable\n" + "Trade::SceneData::mutableMapping(): data not mutable\n" + "Trade::SceneData::mutableField(): data not mutable\n" + "Trade::SceneData::mutableField(): data not mutable\n" + "Trade::SceneData::mutableField(): data not mutable\n" + "Trade::SceneData::mutableField(): data not mutable\n" + "Trade::SceneData::mutableField(): data not mutable\n" + "Trade::SceneData::mutableField(): data not mutable\n"); +} + +void SceneDataTest::mappingNotFound() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedShort foobar; + UnsignedShort mesh; + } fields[2]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, DataFlag::Mutable, fields, { + SceneFieldData{sceneFieldCustom(35), view.slice(&Field::object), view.slice(&Field::foobar)}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.mapping(2); + scene.mapping(2); + scene.mutableMapping(2); + scene.mutableMapping(2); + scene.mapping(sceneFieldCustom(666)); + scene.mapping(sceneFieldCustom(666)); + scene.mutableMapping(sceneFieldCustom(666)); + scene.mutableMapping(sceneFieldCustom(666)); + + scene.mappingAsArray(2); + scene.mappingAsArray(sceneFieldCustom(666)); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::mapping(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mapping(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mutableMapping(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mutableMapping(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mapping(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mapping(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mutableMapping(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mutableMapping(): field Trade::SceneField::Custom(666) not found\n" + + "Trade::SceneData::mappingInto(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mappingInto(): field Trade::SceneField::Custom(666) not found\n"); +} + +void SceneDataTest::mappingWrongType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedShort object; + UnsignedShort foobar; + UnsignedInt mesh; + } fields[2]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedShort, 5, DataFlag::Mutable, fields, { + SceneFieldData{sceneFieldCustom(35), view.slice(&Field::object), view.slice(&Field::foobar)}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.mapping(1); + scene.mutableMapping(1); + scene.mapping(SceneField::Mesh); + scene.mutableMapping(SceneField::Mesh); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::mapping(): mapping is Trade::SceneMappingType::UnsignedShort but requested Trade::SceneMappingType::UnsignedByte\n" + "Trade::SceneData::mutableMapping(): mapping is Trade::SceneMappingType::UnsignedShort but requested Trade::SceneMappingType::UnsignedByte\n" + "Trade::SceneData::mapping(): mapping is Trade::SceneMappingType::UnsignedShort but requested Trade::SceneMappingType::UnsignedByte\n" + "Trade::SceneData::mutableMapping(): mapping is Trade::SceneMappingType::UnsignedShort but requested Trade::SceneMappingType::UnsignedByte\n"); +} + +void SceneDataTest::fieldNotFound() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt foo, bar; + } fields[2]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, DataFlag::Mutable, fields, { + SceneFieldData{sceneFieldCustom(34), view.slice(&Field::object), view.slice(&Field::foo)}, + SceneFieldData{sceneFieldCustom(35), view.slice(&Field::object), view.slice(&Field::bar)}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.findFieldObjectOffset(2, 0); + scene.fieldObjectOffset(2, 0); + scene.hasFieldObject(2, 0); + scene.fieldData(2); + scene.fieldName(2); + scene.fieldFlags(2); + scene.fieldType(2); + scene.fieldSize(2); + scene.fieldArraySize(2); + scene.field(2); + scene.field(2); + scene.field(2); + scene.mutableField(2); + scene.mutableField(2); + scene.mutableField(2); + + scene.fieldId(sceneFieldCustom(666)); + scene.fieldFlags(sceneFieldCustom(666)); + scene.findFieldObjectOffset(sceneFieldCustom(666), 0); + scene.fieldObjectOffset(sceneFieldCustom(666), 0); + scene.hasFieldObject(sceneFieldCustom(666), 0); + scene.fieldType(sceneFieldCustom(666)); + scene.fieldSize(sceneFieldCustom(666)); + scene.fieldArraySize(sceneFieldCustom(666)); + scene.field(sceneFieldCustom(666)); + scene.field(sceneFieldCustom(666)); + scene.field(sceneFieldCustom(666)); + scene.mutableField(sceneFieldCustom(666)); + scene.mutableField(sceneFieldCustom(666)); + scene.mutableField(sceneFieldCustom(666)); + + scene.parentsAsArray(); + scene.parentsInto(nullptr, nullptr); + scene.parentsInto(0, nullptr, nullptr); + scene.transformations2DAsArray(); + scene.transformations2DInto(nullptr, nullptr); + scene.transformations2DInto(0, nullptr, nullptr); + scene.translationsRotationsScalings2DAsArray(); + scene.translationsRotationsScalings2DInto(nullptr, nullptr, nullptr, nullptr); + scene.translationsRotationsScalings2DInto(0, nullptr, nullptr, nullptr, nullptr); + scene.transformations3DAsArray(); + scene.transformations3DInto(nullptr, nullptr); + scene.transformations3DInto(0, nullptr, nullptr); + scene.translationsRotationsScalings3DAsArray(); + scene.translationsRotationsScalings3DInto(nullptr, nullptr, nullptr, nullptr); + scene.translationsRotationsScalings3DInto(0, nullptr, nullptr, nullptr, nullptr); + scene.meshesMaterialsAsArray(); + scene.meshesMaterialsInto(nullptr, nullptr, nullptr); + scene.meshesMaterialsInto(0, nullptr, nullptr, nullptr); + scene.lightsAsArray(); + scene.lightsInto(nullptr, nullptr); + scene.lightsInto(0, nullptr, nullptr); + scene.camerasAsArray(); + scene.camerasInto(nullptr, nullptr); + scene.camerasInto(0, nullptr, nullptr); + scene.skinsAsArray(); + scene.skinsInto(nullptr, nullptr); + scene.skinsInto(0, nullptr, nullptr); + scene.importerStateAsArray(); + scene.importerStateInto(nullptr, nullptr); + scene.importerStateInto(0, nullptr, nullptr); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::findFieldObjectOffset(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldObjectOffset(): index 2 out of range for 2 fields\n" + "Trade::SceneData::hasFieldObject(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldData(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldName(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldFlags(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldType(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldSize(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldArraySize(): index 2 out of range for 2 fields\n" + "Trade::SceneData::field(): index 2 out of range for 2 fields\n" + "Trade::SceneData::field(): index 2 out of range for 2 fields\n" + "Trade::SceneData::field(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mutableField(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mutableField(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mutableField(): index 2 out of range for 2 fields\n" + + "Trade::SceneData::fieldId(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::fieldFlags(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::findFieldObjectOffset(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::fieldObjectOffset(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::hasFieldObject(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::fieldType(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::fieldSize(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::fieldArraySize(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::field(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::field(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::field(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mutableField(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mutableField(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mutableField(): field Trade::SceneField::Custom(666) not found\n" + + /* AsArray() and Into() each share a common helper but have different + top-level code paths. They however have the same assertion messages + to save binary size a bit. */ + "Trade::SceneData::parentsInto(): field not found\n" + "Trade::SceneData::parentsInto(): field not found\n" + "Trade::SceneData::parentsInto(): field not found\n" + "Trade::SceneData::transformations2DInto(): no transformation-related field found\n" + "Trade::SceneData::transformations2DInto(): no transformation-related field found\n" + "Trade::SceneData::transformations2DInto(): no transformation-related field found\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): no transformation-related field found\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): no transformation-related field found\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): no transformation-related field found\n" + "Trade::SceneData::transformations3DInto(): no transformation-related field found\n" + "Trade::SceneData::transformations3DInto(): no transformation-related field found\n" + "Trade::SceneData::transformations3DInto(): no transformation-related field found\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): no transformation-related field found\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): no transformation-related field found\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): no transformation-related field found\n" + "Trade::SceneData::meshesMaterialsInto(): field Trade::SceneField::Mesh not found\n" + "Trade::SceneData::meshesMaterialsInto(): field Trade::SceneField::Mesh not found\n" + "Trade::SceneData::meshesMaterialsInto(): field Trade::SceneField::Mesh not found\n" + "Trade::SceneData::lightsInto(): field not found\n" + "Trade::SceneData::lightsInto(): field not found\n" + "Trade::SceneData::lightsInto(): field not found\n" + "Trade::SceneData::camerasInto(): field not found\n" + "Trade::SceneData::camerasInto(): field not found\n" + "Trade::SceneData::camerasInto(): field not found\n" + "Trade::SceneData::skinsInto(): field not found\n" + "Trade::SceneData::skinsInto(): field not found\n" + "Trade::SceneData::skinsInto(): field not found\n" + "Trade::SceneData::importerStateInto(): field not found\n" + "Trade::SceneData::importerStateInto(): field not found\n" + "Trade::SceneData::importerStateInto(): field not found\n"); +} + +void SceneDataTest::fieldWrongType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedShort foobar; + UnsignedShort mesh; + } fields[2]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, DataFlag::Mutable, fields, { + SceneFieldData{sceneFieldCustom(35), view.slice(&Field::object), view.slice(&Field::foobar)}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.field(1); + scene.field(1); + scene.mutableField(1); + scene.mutableField(1); + scene.field(SceneField::Mesh); + scene.field(SceneField::Mesh); + scene.mutableField(SceneField::Mesh); + scene.mutableField(SceneField::Mesh); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::field(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::field(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::field(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::field(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n"); +} + +void SceneDataTest::fieldWrongPointerType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Thing { + UnsignedInt object; + Int* foobar; + const Int* importerState; + } things[2]; + Containers::StridedArrayView1D view = things; + + SceneData scene{SceneMappingType::UnsignedInt, 5, DataFlag::Mutable, things, { + SceneFieldData{sceneFieldCustom(35), view.slice(&Thing::object), Containers::arrayCast<2, Int*>(view.slice(&Thing::foobar))}, + SceneFieldData{SceneField::ImporterState, view.slice(&Thing::object), view.slice(&Thing::importerState)}, + }}; + + /* These are fine (type is not checked) */ + scene.field(0); + scene.field(1); + scene.mutableField(0); + scene.mutableField(1); + scene.field(sceneFieldCustom(35)); + scene.field(SceneField::ImporterState); + scene.mutableField(sceneFieldCustom(35)); + scene.mutableField(SceneField::ImporterState); + + std::ostringstream out; + Error redirectError{&out}; + scene.field(0); + scene.field(0); + scene.field(0); + scene.field(1); + scene.field(1); + scene.mutableField(0); + scene.mutableField(0); + scene.mutableField(0); + scene.mutableField(1); + scene.mutableField(1); + scene.field(sceneFieldCustom(35)); + scene.field(sceneFieldCustom(35)); + scene.field(sceneFieldCustom(35)); + scene.field(SceneField::ImporterState); + scene.field(SceneField::ImporterState); + scene.mutableField(sceneFieldCustom(35)); + scene.mutableField(sceneFieldCustom(35)); + scene.mutableField(sceneFieldCustom(35)); + scene.mutableField(SceneField::ImporterState); + scene.mutableField(SceneField::ImporterState); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::field(): Trade::SceneField::Custom(35) is Trade::SceneFieldType::MutablePointer but requested a type equivalent to Trade::SceneFieldType::Int\n" + "Trade::SceneData::field(): Trade::SceneField::Custom(35) is Trade::SceneFieldType::MutablePointer but requested a type equivalent to Trade::SceneFieldType::Pointer\n" + "Trade::SceneData::field(): Trade::SceneField::Custom(35) is Trade::SceneFieldType::MutablePointer but requested a type equivalent to Trade::SceneFieldType::Pointer\n" + "Trade::SceneData::field(): Trade::SceneField::ImporterState is Trade::SceneFieldType::Pointer but requested a type equivalent to Trade::SceneFieldType::MutablePointer\n" + "Trade::SceneData::field(): Trade::SceneField::ImporterState is Trade::SceneFieldType::Pointer but requested a type equivalent to Trade::SceneFieldType::MutablePointer\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is Trade::SceneFieldType::MutablePointer but requested a type equivalent to Trade::SceneFieldType::Int\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is Trade::SceneFieldType::MutablePointer but requested a type equivalent to Trade::SceneFieldType::Pointer\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is Trade::SceneFieldType::MutablePointer but requested a type equivalent to Trade::SceneFieldType::Pointer\n" + "Trade::SceneData::mutableField(): Trade::SceneField::ImporterState is Trade::SceneFieldType::Pointer but requested a type equivalent to Trade::SceneFieldType::MutablePointer\n" + "Trade::SceneData::mutableField(): Trade::SceneField::ImporterState is Trade::SceneFieldType::Pointer but requested a type equivalent to Trade::SceneFieldType::MutablePointer\n" + "Trade::SceneData::field(): Trade::SceneField::Custom(35) is Trade::SceneFieldType::MutablePointer but requested a type equivalent to Trade::SceneFieldType::Int\n" + "Trade::SceneData::field(): Trade::SceneField::Custom(35) is Trade::SceneFieldType::MutablePointer but requested a type equivalent to Trade::SceneFieldType::Pointer\n" + "Trade::SceneData::field(): Trade::SceneField::Custom(35) is Trade::SceneFieldType::MutablePointer but requested a type equivalent to Trade::SceneFieldType::Pointer\n" + "Trade::SceneData::field(): Trade::SceneField::ImporterState is Trade::SceneFieldType::Pointer but requested a type equivalent to Trade::SceneFieldType::MutablePointer\n" + "Trade::SceneData::field(): Trade::SceneField::ImporterState is Trade::SceneFieldType::Pointer but requested a type equivalent to Trade::SceneFieldType::MutablePointer\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is Trade::SceneFieldType::MutablePointer but requested a type equivalent to Trade::SceneFieldType::Int\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is Trade::SceneFieldType::MutablePointer but requested a type equivalent to Trade::SceneFieldType::Pointer\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is Trade::SceneFieldType::MutablePointer but requested a type equivalent to Trade::SceneFieldType::Pointer\n" + "Trade::SceneData::mutableField(): Trade::SceneField::ImporterState is Trade::SceneFieldType::Pointer but requested a type equivalent to Trade::SceneFieldType::MutablePointer\n" + "Trade::SceneData::mutableField(): Trade::SceneField::ImporterState is Trade::SceneFieldType::Pointer but requested a type equivalent to Trade::SceneFieldType::MutablePointer\n"); +} + +void SceneDataTest::fieldWrongArrayAccess() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + UnsignedInt foobar; + } fields[2]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 5, DataFlag::Mutable, fields, { + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)}, + SceneFieldData{sceneFieldCustom(35), view.slice(&Field::object), Containers::arrayCast<2, UnsignedInt>(view.slice(&Field::foobar))}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.field(0); + scene.field(1); + scene.mutableField(0); + scene.mutableField(1); + scene.field(SceneField::Mesh); + scene.field(sceneFieldCustom(35)); + scene.mutableField(SceneField::Mesh); + scene.mutableField(sceneFieldCustom(35)); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::field(): Trade::SceneField::Mesh is not an array field, can't use T[] to access it\n" + "Trade::SceneData::field(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is not an array field, can't use T[] to access it\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n" + "Trade::SceneData::field(): Trade::SceneField::Mesh is not an array field, can't use T[] to access it\n" + "Trade::SceneData::field(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is not an array field, can't use T[] to access it\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n"); +} + +void SceneDataTest::parentFor() { + struct Field { + UnsignedInt object; + Int parent; + } fields[]{ + {3, -1}, + {4, 3}, + {2, 4}, + {4, 2} /* duplicate, ignored */ + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Parent, view.slice(&Field::object), view.slice(&Field::parent)} + }}; + + CORRADE_COMPARE(scene.parentFor(2), 4); + CORRADE_COMPARE(scene.parentFor(3), -1); + + /* Duplicate entries -- only the first one gets used, it doesn't traverse + further */ + CORRADE_COMPARE(scene.parentFor(4), 3); + + /* Object that's not in the array at all */ + CORRADE_COMPARE(scene.parentFor(1), Containers::NullOpt); +} + +void SceneDataTest::parentForTrivialParent() { + /* Going a bit overboard with the arrays, it makes the view creation below + easier tho */ + struct Field { + UnsignedInt object[4]; + Int parent[1]; + } fields[]{{ + {3, 4, 2, 4 /* duplicate, ignored */}, {-1} + }}; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Parent, + Containers::stridedArrayView(fields->object), Containers::stridedArrayView(fields->parent).broadcasted<0>(4)} + }}; + + CORRADE_COMPARE(scene.parentFor(2), -1); + CORRADE_COMPARE(scene.parentFor(3), -1); + + /* Duplicate entries -- only the first one gets used, it doesn't traverse + further */ + CORRADE_COMPARE(scene.parentFor(4), -1); + + /* Object that's not in the array at all */ + CORRADE_COMPARE(scene.parentFor(1), Containers::NullOpt); +} + +void SceneDataTest::childrenFor() { + struct Field { + UnsignedInt object; + Int parent; + } fields[]{ + {4, -1}, + {3, 4}, + {2, 3}, + {1, 4}, + {5, 4}, + {0, -1}, + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Parent, view.slice(&Field::object), view.slice(&Field::parent)} + }}; + + /* Just one child */ + CORRADE_COMPARE_AS(scene.childrenFor(3), + Containers::arrayView({2}), + TestSuite::Compare::Container); + + /* More */ + CORRADE_COMPARE_AS(scene.childrenFor(-1), + Containers::arrayView({4, 0}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.childrenFor(4), + Containers::arrayView({3, 1, 5}), + TestSuite::Compare::Container); + + /* Object that is present in the parent array but has no children */ + CORRADE_COMPARE_AS(scene.childrenFor(5), + Containers::arrayView({}), + TestSuite::Compare::Container); + + /* Object that is not in the parent array at all */ + CORRADE_COMPARE_AS(scene.childrenFor(6), + Containers::arrayView({}), + TestSuite::Compare::Container); +} + +void SceneDataTest::childrenForTrivialParent() { + /* Going a bit overboard with the arrays, it makes the view creation below + easier tho */ + struct Field { + UnsignedInt mapping[4]; + Int parent[1]; + } fields[]{{ + {3, 4, 2, 4 /* duplicate, gets put to the output */}, {-1} + }}; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Parent, + Containers::stridedArrayView(fields->mapping), Containers::stridedArrayView(fields->parent).broadcasted<0>(4)} + }}; + + /* Trivial children */ + CORRADE_COMPARE_AS(scene.childrenFor(-1), + Containers::arrayView({3, 4, 2, 4}), + TestSuite::Compare::Container); + + /* Object that is present in the parent array but has no children */ + CORRADE_COMPARE_AS(scene.childrenFor(4), + Containers::arrayView({}), + TestSuite::Compare::Container); + + /* Object that is not in the parent array */ + CORRADE_COMPARE_AS(scene.childrenFor(5), + Containers::arrayView({}), + TestSuite::Compare::Container); +} + +void SceneDataTest::transformation2DFor() { + const struct Field { + UnsignedInt object; + Matrix3 transformation; + } fields[] { + {1, Matrix3::translation({3.0f, 2.0f})*Matrix3::scaling({1.5f, 2.0f})}, + {0, Matrix3::rotation(35.0_degf)}, + {4, Matrix3::translation({3.0f, 2.0f})*Matrix3::rotation(35.0_degf)}, + {1, Matrix3::translation({1.0f, 2.0f})} /* duplicate, ignored */ + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Transformation, view.slice(&Field::object), view.slice(&Field::transformation)} + }}; + + CORRADE_COMPARE(scene.transformation2DFor(4), + Matrix3::translation({3.0f, 2.0f})*Matrix3::rotation(35.0_degf)); + CORRADE_COMPARE(scene.transformation2DFor(0), + Matrix3::Matrix3::Matrix3::Matrix3::rotation(35.0_degf)); + + /* Duplicate entries -- only the first one gets used, it doesn't traverse + further */ + CORRADE_COMPARE(scene.transformation2DFor(1), + Matrix3::translation({3.0f, 2.0f})*Matrix3::scaling({1.5f, 2.0f})); + + /* Object that's not in the array at all */ + CORRADE_COMPARE(scene.transformation2DFor(2), + Containers::NullOpt); +} + +void SceneDataTest::transformation2DForTRS() { + const struct Field { + UnsignedInt object; + Vector2 translation; + Complex rotation; + Vector2 scaling; + } fields[] { + {1, {3.0f, 2.0f}, {}, {1.5f, 2.0f}}, + {0, {}, Complex::rotation(35.0_degf), {1.0f, 1.0f}}, + {4, {3.0f, 2.0f}, Complex::rotation(35.0_degf), {1.0f, 1.0f}}, + {1, {1.0f, 2.0f}, {}, {1.0f, 1.0f}} /* duplicate, ignored */ + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Translation, view.slice(&Field::object), view.slice(&Field::translation)}, + SceneFieldData{SceneField::Rotation, view.slice(&Field::object), view.slice(&Field::rotation)}, + SceneFieldData{SceneField::Scaling, view.slice(&Field::object), view.slice(&Field::scaling)} + }}; + + CORRADE_COMPARE(scene.transformation2DFor(4), + Matrix3::translation({3.0f, 2.0f})*Matrix3::rotation(35.0_degf)); + CORRADE_COMPARE(scene.translationRotationScaling2DFor(4), + Containers::triple(Vector2{3.0f, 2.0f}, Complex::rotation(35.0_degf), Vector2{1.0f})); + CORRADE_COMPARE(scene.transformation2DFor(0), + Matrix3::rotation(35.0_degf)); + CORRADE_COMPARE(scene.translationRotationScaling2DFor(0), + Containers::triple(Vector2{}, Complex::rotation(35.0_degf), Vector2{1.0f})); + + /* Duplicate entries -- only the first one gets used, it doesn't traverse + further */ + CORRADE_COMPARE(scene.transformation2DFor(1), + Matrix3::translation({3.0f, 2.0f})*Matrix3::scaling({1.5f, 2.0f})); + CORRADE_COMPARE(scene.translationRotationScaling2DFor(1), + Containers::triple(Vector2{3.0f, 2.0f}, Complex{}, Vector2{1.5f, 2.0f})); + + /* Object that's not in the array at all */ + CORRADE_COMPARE(scene.transformation2DFor(2), + Containers::NullOpt); + CORRADE_COMPARE(scene.translationRotationScaling2DFor(2), + Containers::NullOpt); +} + +void SceneDataTest::transformation2DForBut3DType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneData scene{SceneMappingType::UnsignedInt, 1, nullptr, { + SceneFieldData{SceneField::Translation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Vector3, nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.transformation2DFor(0); + scene.translationRotationScaling2DFor(0); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::transformation2DFor(): scene has a 3D transformation type\n" + "Trade::SceneData::translationRotationScaling2DFor(): scene has a 3D transformation type\n"); +} + +void SceneDataTest::transformation3DFor() { + const struct Field { + UnsignedInt object; + Matrix4 transformation; + } fields[] { + {1, Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::scaling({1.5f, 2.0f, 4.5f})}, + {0, Matrix4::rotationX(35.0_degf)}, + {4, Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::rotationX(35.0_degf)}, + {1, Matrix4::translation({1.0f, 2.0f, 3.0f})} /* duplicate, ignored */ + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Transformation, view.slice(&Field::object), view.slice(&Field::transformation)} + }}; + + CORRADE_COMPARE(scene.transformation3DFor(4), + Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::rotationX(35.0_degf)); + CORRADE_COMPARE(scene.transformation3DFor(0), + Matrix4::rotationX(35.0_degf)); + + /* Duplicate entries -- only the first one gets used, it doesn't traverse + further */ + CORRADE_COMPARE(scene.transformation3DFor(1), + Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::scaling({1.5f, 2.0f, 4.5f})); + + /* Object that's not in the array at all */ + CORRADE_COMPARE(scene.transformation3DFor(2), + Containers::NullOpt); +} + +void SceneDataTest::transformation3DForTRS() { + const struct Field { + UnsignedInt object; + Vector3 translation; + Quaternion rotation; + Vector3 scaling; + } fields[] { + {1, {3.0f, 2.0f, 1.0f}, {}, {1.5f, 2.0f, 4.5f}}, + {0, {}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), {1.0f, 1.0f, 1.0f}}, + {4, {3.0f, 2.0f, 1.0f}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), {1.0f, 1.0f, 1.0f}}, + {1, {1.0f, 2.0f, 3.0f}, Quaternion{}, Vector3{1.0f}} /* duplicate, ignored */ + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Translation, view.slice(&Field::object), view.slice(&Field::translation)}, + SceneFieldData{SceneField::Rotation, view.slice(&Field::object), view.slice(&Field::rotation)}, + SceneFieldData{SceneField::Scaling, view.slice(&Field::object), view.slice(&Field::scaling)} + }}; + + CORRADE_COMPARE(scene.transformation3DFor(4), + Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::rotationX(35.0_degf)); + CORRADE_COMPARE(scene.translationRotationScaling3DFor(4), + Containers::triple(Vector3{3.0f, 2.0f, 1.0f}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), Vector3{1.0f})); + CORRADE_COMPARE(scene.transformation3DFor(0), + Matrix4::rotationX(35.0_degf)); + CORRADE_COMPARE(scene.translationRotationScaling3DFor(0), + Containers::triple(Vector3{}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), Vector3{1.0f})); + + /* Duplicate entries -- only the first one gets used, it doesn't traverse + further */ + CORRADE_COMPARE(scene.transformation3DFor(1), + Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::scaling({1.5f, 2.0f, 4.5f})); + CORRADE_COMPARE(scene.translationRotationScaling3DFor(1), + Containers::triple(Vector3{3.0f, 2.0f, 1.0f}, Quaternion{}, Vector3{1.5f, 2.0f, 4.5f})); + + /* Object that's not in the array at all */ + CORRADE_COMPARE(scene.transformation3DFor(2), + Containers::NullOpt); + CORRADE_COMPARE(scene.translationRotationScaling3DFor(2), + Containers::NullOpt); +} + +void SceneDataTest::transformation3DForBut2DType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneData scene{SceneMappingType::UnsignedInt, 1, nullptr, { + SceneFieldData{SceneField::Translation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Vector2, nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.transformation3DFor(0); + scene.translationRotationScaling3DFor(0); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::transformation3DFor(): scene has a 2D transformation type\n" + "Trade::SceneData::translationRotationScaling3DFor(): scene has a 2D transformation type\n"); +} + +void SceneDataTest::meshesMaterialsFor() { + struct Field { + UnsignedInt object; + UnsignedInt mesh; + Int meshMaterial; + } fields[]{ + {4, 1, -1}, + {1, 3, 0}, + {2, 4, 1}, + {2, 5, -1}, + {2, 1, 0}, + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)}, + SceneFieldData{SceneField::MeshMaterial, view.slice(&Field::object), view.slice(&Field::meshMaterial)} + }}; + + /* Just one */ + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(1), + (Containers::arrayView>({{3, 0}})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(4), + (Containers::arrayView>({{1, -1}})), + TestSuite::Compare::Container); + + /* More */ + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(2), + (Containers::arrayView>({ + {4, 1}, {5, -1}, {1, 0} + })), TestSuite::Compare::Container); + + /* Object that is not in the array at all */ + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(6), + (Containers::arrayView>({})), + TestSuite::Compare::Container); +} + +void SceneDataTest::lightsFor() { + struct Field { + UnsignedInt object; + UnsignedInt light; + } fields[]{ + {4, 1}, + {1, 3}, + {2, 4}, + {2, 5} + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Light, view.slice(&Field::object), view.slice(&Field::light)} + }}; + + /* Just one */ + CORRADE_COMPARE_AS(scene.lightsFor(1), + (Containers::arrayView({3})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.lightsFor(4), + (Containers::arrayView({1})), + TestSuite::Compare::Container); + + /* More */ + CORRADE_COMPARE_AS(scene.lightsFor(2), + (Containers::arrayView({ + 4, 5 + })), TestSuite::Compare::Container); + + /* Object that is not in the array at all */ + CORRADE_COMPARE_AS(scene.lightsFor(6), + (Containers::arrayView({})), + TestSuite::Compare::Container); +} + +void SceneDataTest::camerasFor() { + struct Field { + UnsignedInt object; + UnsignedInt camera; + } fields[]{ + {4, 1}, + {1, 3}, + {2, 4}, + {2, 5} + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Camera, view.slice(&Field::object), view.slice(&Field::camera)} + }}; + + /* Just one */ + CORRADE_COMPARE_AS(scene.camerasFor(1), + (Containers::arrayView({3})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.camerasFor(4), + (Containers::arrayView({1})), + TestSuite::Compare::Container); + + /* More */ + CORRADE_COMPARE_AS(scene.camerasFor(2), + (Containers::arrayView({ + 4, 5 + })), TestSuite::Compare::Container); + + /* Object that is not in the array at all */ + CORRADE_COMPARE_AS(scene.camerasFor(6), + (Containers::arrayView({})), + TestSuite::Compare::Container); +} + +void SceneDataTest::skinsFor() { + struct Field { + UnsignedInt object; + UnsignedInt skin; + } fields[]{ + {4, 1}, + {1, 3}, + {2, 4}, + {2, 5} + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + /* To satisfy the requirement of having a transformation field to + disambiguate the dimensionality */ + SceneFieldData{SceneField::Translation, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Vector3, nullptr}, + SceneFieldData{SceneField::Skin, view.slice(&Field::object), view.slice(&Field::skin)} + }}; + + /* Just one */ + CORRADE_COMPARE_AS(scene.skinsFor(1), + (Containers::arrayView({3})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.skinsFor(4), + (Containers::arrayView({1})), + TestSuite::Compare::Container); + + /* More */ + CORRADE_COMPARE_AS(scene.skinsFor(2), + (Containers::arrayView({ + 4, 5 + })), TestSuite::Compare::Container); + + /* Object that is not in the array at all */ + CORRADE_COMPARE_AS(scene.skinsFor(6), + (Containers::arrayView({})), + TestSuite::Compare::Container); +} + +void SceneDataTest::importerStateFor() { + int a, b, c; + + struct Field { + UnsignedInt object; + const void* importerState; + } fields[]{ + {3, &a}, + {4, &b}, + {2, nullptr}, + {4, &c} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneMappingType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::ImporterState, view.slice(&Field::object), view.slice(&Field::importerState)} + }}; + + CORRADE_COMPARE(scene.importerStateFor(2), nullptr); + CORRADE_COMPARE(scene.importerStateFor(3), &a); + + /* Duplicate entries -- only the first one gets used, it doesn't traverse + further */ + CORRADE_COMPARE(scene.importerStateFor(4), &b); + + /* Object that's not in the array at all */ + CORRADE_COMPARE(scene.importerStateFor(1), Containers::NullOpt); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void SceneDataTest::childrenDeprecated() { + auto&& data = ChildrenDeprecatedData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + struct Field { + UnsignedByte object; + Short parent; + } fields[]{ + {5, -1}, + {2, 0}, + {3, 0}, + {0, -1}, + {1, 2}, + {4, -1} + }; + Containers::StridedArrayView1D view = fields; + + Containers::Array fieldData; + if(!data.skipParent) + arrayAppend(fieldData, SceneFieldData{SceneField::Parent, view.slice(&Field::object), view.slice(&Field::parent)}); + if(data.is2D) + arrayAppend(fieldData, SceneFieldData{SceneField::Translation, SceneMappingType::UnsignedByte, nullptr, SceneFieldType::Vector2, nullptr}); + if(data.is3D) + arrayAppend(fieldData, SceneFieldData{SceneField::Translation, SceneMappingType::UnsignedByte, nullptr, SceneFieldType::Vector3, nullptr}); + + SceneData scene{SceneMappingType::UnsignedByte, 25, {}, fields, std::move(fieldData)}; + + if(!data.skipParent) { + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_COMPARE_AS(scene.children2D(), + (data.is2D ? std::vector{5, 0, 4} : std::vector{}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.children3D(), + (data.is3D ? std::vector{5, 0, 4} : std::vector{}), + TestSuite::Compare::Container); + CORRADE_IGNORE_DEPRECATED_POP + } else { + std::ostringstream out; + Warning redirectWarning{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH + CORRADE_VERIFY(scene.children2D().empty()); + CORRADE_VERIFY(scene.children3D().empty()); + CORRADE_IGNORE_DEPRECATED_POP + if(data.is2D) + CORRADE_COMPARE(out.str(), "Trade::SceneData::children2D(): no parent field present, returned array will be empty\n"); + else if(data.is3D) + CORRADE_COMPARE(out.str(), "Trade::SceneData::children3D(): no parent field present, returned array will be empty\n"); + else + CORRADE_COMPARE(out.str(), ""); + } +} +#endif + +void SceneDataTest::fieldForFieldMissing() { + SceneData scene{SceneMappingType::UnsignedInt, 7, nullptr, {}}; + + CORRADE_COMPARE(scene.parentFor(6), Containers::NullOpt); + CORRADE_COMPARE_AS(scene.childrenFor(6), + Containers::arrayView({}), + TestSuite::Compare::Container); + CORRADE_COMPARE(scene.transformation2DFor(6), Containers::NullOpt); + CORRADE_COMPARE(scene.translationRotationScaling2DFor(6), Containers::NullOpt); + CORRADE_COMPARE(scene.transformation3DFor(6), Containers::NullOpt); + CORRADE_COMPARE(scene.translationRotationScaling3DFor(6), Containers::NullOpt); + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(6), + (Containers::arrayView>({})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.lightsFor(6), + Containers::arrayView({}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.camerasFor(6), + Containers::arrayView({}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.skinsFor(6), + Containers::arrayView({}), + TestSuite::Compare::Container); +} + +void SceneDataTest::findFieldObjectOffsetInvalidObject() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneData scene{SceneMappingType::UnsignedInt, 7, nullptr, { + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.findFieldObjectOffset(0, 7); + scene.findFieldObjectOffset(SceneField::Parent, 7); + scene.fieldObjectOffset(0, 7); + scene.fieldObjectOffset(SceneField::Parent, 7); + scene.hasFieldObject(0, 7); + scene.hasFieldObject(SceneField::Parent, 7); + scene.parentFor(7); + scene.childrenFor(-2); + scene.childrenFor(7); + scene.transformation2DFor(7); + scene.translationRotationScaling2DFor(7); + scene.transformation3DFor(7); + scene.translationRotationScaling3DFor(7); + scene.meshesMaterialsFor(7); + scene.lightsFor(7); + scene.camerasFor(7); + scene.skinsFor(7); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::findFieldObjectOffset(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::findFieldObjectOffset(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::fieldObjectOffset(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::fieldObjectOffset(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::hasFieldObject(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::hasFieldObject(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::parentFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::childrenFor(): object -2 out of bounds for 7 objects\n" + "Trade::SceneData::childrenFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::transformation2DFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::translationRotationScaling2DFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::transformation3DFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::translationRotationScaling3DFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::meshesMaterialsFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::lightsFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::camerasFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::skinsFor(): object 7 out of bounds for 7 objects\n"); +} + +void SceneDataTest::releaseFieldData() { + struct Field { + UnsignedByte object; + UnsignedInt mesh; + }; + + Containers::Array data{NoInit, 3*sizeof(Field)}; + Containers::StridedArrayView1D view = Containers::arrayCast(data); + + auto fields = Containers::array({ + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }); + SceneFieldData* originalFields = fields; + + SceneData scene{SceneMappingType::UnsignedByte, 50, std::move(data), std::move(fields)}; + + Containers::Array released = scene.releaseFieldData(); + CORRADE_COMPARE(released.data(), originalFields); + CORRADE_COMPARE(released.size(), 2); + + /* Fields are all gone */ + CORRADE_COMPARE(static_cast(scene.fieldData()), nullptr); + CORRADE_COMPARE(scene.fieldCount(), 0); + + /* Data stays untouched, object count and type as well, as it con't result + in any dangling data access */ + CORRADE_COMPARE(scene.data(), view.data()); + CORRADE_COMPARE(scene.mappingBound(), 50); + CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedByte); +} + +void SceneDataTest::releaseData() { + struct Field { + UnsignedByte object; + UnsignedByte mesh; + }; + + Containers::Array data{NoInit, 3*sizeof(Field)}; + Containers::StridedArrayView1D view = Containers::arrayCast(data); + + SceneData scene{SceneMappingType::UnsignedByte, 50, std::move(data), { + SceneFieldData{SceneField::Parent, SceneMappingType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + Containers::Array released = scene.releaseData(); + CORRADE_COMPARE(released.data(), view.data()); + CORRADE_COMPARE(released.size(), 3*sizeof(Field)); + + /* Both fields and data are all gone */ + CORRADE_COMPARE(static_cast(scene.fieldData()), nullptr); + CORRADE_COMPARE(scene.fieldCount(), 0); + CORRADE_COMPARE(static_cast(scene.data()), nullptr); + + /* Object count and type stays untouched, as it con't result in any + dangling data access */ + CORRADE_COMPARE(scene.mappingBound(), 50); + CORRADE_COMPARE(scene.mappingType(), SceneMappingType::UnsignedByte); } }}}} diff --git a/src/Magnum/Trade/Test/SceneToolsTest.cpp b/src/Magnum/Trade/Test/SceneToolsTest.cpp new file mode 100644 index 0000000000..ccc3d3c76a --- /dev/null +++ b/src/Magnum/Trade/Test/SceneToolsTest.cpp @@ -0,0 +1,666 @@ +/* + 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) diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index e0104d5a11..dde9a1df06 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -89,16 +89,23 @@ class MeshData; #ifdef MAGNUM_BUILD_DEPRECATED class CORRADE_DEPRECATED("use MeshData instead") MeshData2D; class CORRADE_DEPRECATED("use MeshData instead") MeshData3D; +class CORRADE_DEPRECATED("use SceneData instead") MeshObjectData2D; +class CORRADE_DEPRECATED("use SceneData instead") MeshObjectData3D; +class CORRADE_DEPRECATED("use SceneData instead") ObjectData2D; +class CORRADE_DEPRECATED("use SceneData instead") ObjectData3D; #endif -class MeshObjectData2D; -class MeshObjectData3D; -class ObjectData2D; -class ObjectData3D; class PbrClearCoatMaterialData; class PbrMetallicRoughnessMaterialData; class PbrSpecularGlossinessMaterialData; class PhongMaterialData; class TextureData; + +enum class SceneMappingType: UnsignedByte; +enum class SceneField: UnsignedInt; +enum class SceneFieldType: UnsignedShort; +enum class SceneFieldFlag: UnsignedByte; +typedef Containers::EnumSet SceneFieldFlags; +class SceneFieldData; class SceneData; template class SkinData; diff --git a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp index d177ad43fb..6ca93fb511 100644 --- a/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp +++ b/src/MagnumPlugins/AnyImageImporter/AnyImageImporter.cpp @@ -280,4 +280,4 @@ Containers::Optional AnyImageImporter::doImage3D(const UnsignedInt }} CORRADE_PLUGIN_REGISTER(AnyImageImporter, Magnum::Trade::AnyImageImporter, - "cz.mosra.magnum.Trade.AbstractImporter/0.3.4") + "cz.mosra.magnum.Trade.AbstractImporter/0.4") diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp index 1fd7439401..6bc8c03d8d 100644 --- a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp +++ b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp @@ -38,8 +38,6 @@ #include "Magnum/Trade/LightData.h" #include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" -#include "Magnum/Trade/ObjectData2D.h" -#include "Magnum/Trade/ObjectData3D.h" #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/SkinData.h" #include "Magnum/Trade/TextureData.h" @@ -47,9 +45,12 @@ #ifdef MAGNUM_BUILD_DEPRECATED #define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ +#define _MAGNUM_NO_DEPRECATED_OBJECTDATA /* So it doesn't yell here */ #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/ObjectData2D.h" +#include "Magnum/Trade/ObjectData3D.h" #endif namespace Magnum { namespace Trade { @@ -174,8 +175,11 @@ Containers::Optional AnySceneImporter::doAnimation(const Unsigned Int AnySceneImporter::doDefaultScene() const { return _in->defaultScene(); } UnsignedInt AnySceneImporter::doSceneCount() const { return _in->sceneCount(); } +UnsignedLong AnySceneImporter::doObjectCount() const { return _in->objectCount(); } Int AnySceneImporter::doSceneForName(const std::string& name) { return _in->sceneForName(name); } +Long AnySceneImporter::doObjectForName(const std::string& name) { return _in->objectForName(name); } std::string AnySceneImporter::doSceneName(const UnsignedInt id) { return _in->sceneName(id); } +std::string AnySceneImporter::doObjectName(const UnsignedLong id) { return _in->objectName(id); } Containers::Optional AnySceneImporter::doScene(const UnsignedInt id) { return _in->scene(id); } UnsignedInt AnySceneImporter::doLightCount() const { return _in->lightCount(); } @@ -188,6 +192,8 @@ Int AnySceneImporter::doCameraForName(const std::string& name) { return _in->cam std::string AnySceneImporter::doCameraName(const UnsignedInt id) { return _in->cameraName(id); } Containers::Optional AnySceneImporter::doCamera(const UnsignedInt id) { return _in->camera(id); } +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH UnsignedInt AnySceneImporter::doObject2DCount() const { return _in->object2DCount(); } Int AnySceneImporter::doObject2DForName(const std::string& name) { return _in->object2DForName(name); } std::string AnySceneImporter::doObject2DName(const UnsignedInt id) { return _in->object2DName(id); } @@ -197,6 +203,8 @@ UnsignedInt AnySceneImporter::doObject3DCount() const { return _in->object3DCoun Int AnySceneImporter::doObject3DForName(const std::string& name) { return _in->object3DForName(name); } std::string AnySceneImporter::doObject3DName(const UnsignedInt id) { return _in->object3DName(id); } Containers::Pointer AnySceneImporter::doObject3D(const UnsignedInt id) { return _in->object3D(id); } +CORRADE_IGNORE_DEPRECATED_POP +#endif UnsignedInt AnySceneImporter::doSkin2DCount() const { return _in->skin2DCount(); } Int AnySceneImporter::doSkin2DForName(const std::string& name) { return _in->skin2DForName(name); } @@ -261,4 +269,4 @@ Containers::Optional AnySceneImporter::doImage3D(const UnsignedInt }} CORRADE_PLUGIN_REGISTER(AnySceneImporter, Magnum::Trade::AnySceneImporter, - "cz.mosra.magnum.Trade.AbstractImporter/0.3.4") + "cz.mosra.magnum.Trade.AbstractImporter/0.4") diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h index 836d504d67..bb84c95cfe 100644 --- a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h +++ b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h @@ -167,8 +167,11 @@ class MAGNUM_ANYSCENEIMPORTER_EXPORT AnySceneImporter: public AbstractImporter { MAGNUM_ANYSCENEIMPORTER_LOCAL Int doDefaultScene() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doSceneCount() const override; + MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedLong doObjectCount() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL Int doSceneForName(const std::string& name) override; + MAGNUM_ANYSCENEIMPORTER_LOCAL Long doObjectForName(const std::string& name) override; MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doSceneName(UnsignedInt id) override; + MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doObjectName(UnsignedLong id) override; MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Optional doScene(UnsignedInt id) override; MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doLightCount() const override; @@ -181,15 +184,21 @@ class MAGNUM_ANYSCENEIMPORTER_EXPORT AnySceneImporter: public AbstractImporter { MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doCameraName(UnsignedInt id) override; MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Optional doCamera(UnsignedInt id) override; + #ifdef MAGNUM_BUILD_DEPRECATED MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doObject2DCount() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL Int doObject2DForName(const std::string& name) override; MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doObject2DName(UnsignedInt id) override; + CORRADE_IGNORE_DEPRECATED_PUSH MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Pointer doObject2D(UnsignedInt id) override; + CORRADE_IGNORE_DEPRECATED_POP MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doObject3DCount() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL Int doObject3DForName(const std::string& name) override; MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doObject3DName(UnsignedInt id) override; + CORRADE_IGNORE_DEPRECATED_PUSH MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Pointer doObject3D(UnsignedInt id) override; + CORRADE_IGNORE_DEPRECATED_POP + #endif MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doSkin2DCount() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL Int doSkin2DForName(const std::string& name) override; diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp index 5c4f37baa2..f8228c4307 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp @@ -487,4 +487,4 @@ Containers::Optional ObjImporter::doMesh(UnsignedInt id, UnsignedInt) }} CORRADE_PLUGIN_REGISTER(ObjImporter, Magnum::Trade::ObjImporter, - "cz.mosra.magnum.Trade.AbstractImporter/0.3.4") + "cz.mosra.magnum.Trade.AbstractImporter/0.4") diff --git a/src/MagnumPlugins/TgaImporter/TgaImporter.cpp b/src/MagnumPlugins/TgaImporter/TgaImporter.cpp index 84d3e23414..7bf6a90700 100644 --- a/src/MagnumPlugins/TgaImporter/TgaImporter.cpp +++ b/src/MagnumPlugins/TgaImporter/TgaImporter.cpp @@ -209,4 +209,4 @@ Containers::Optional TgaImporter::doImage2D(UnsignedInt, UnsignedIn }} CORRADE_PLUGIN_REGISTER(TgaImporter, Magnum::Trade::TgaImporter, - "cz.mosra.magnum.Trade.AbstractImporter/0.3.4") + "cz.mosra.magnum.Trade.AbstractImporter/0.4")