diff --git a/doc/snippets/MagnumSceneTools.cpp b/doc/snippets/MagnumSceneTools.cpp index 49eed2a5f..a649a7f42 100644 --- a/doc/snippets/MagnumSceneTools.cpp +++ b/doc/snippets/MagnumSceneTools.cpp @@ -30,8 +30,9 @@ #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#include "Magnum/MeshTools/Concatenate.h" #include "Magnum/MeshTools/Transform.h" -#include "Magnum/SceneTools/FlattenMeshHierarchy.h" +#include "Magnum/SceneTools/FlattenTransformationHierarchy.h" #include "Magnum/SceneTools/OrderClusterParents.h" #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/MeshData.h" @@ -42,74 +43,45 @@ using namespace Magnum; int main() { { -/* [flattenMeshHierarchy2D-transformations] */ +/* [flattenTransformationHierarchy2D-mesh-concatenate] */ Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}}); Containers::Array meshes = DOXYGEN_ELLIPSIS({}); +Containers::Array>> + meshesMaterials = scene.meshesMaterialsAsArray(); +Containers::Array transformations = + SceneTools::flattenTransformationHierarchy2D(scene, Trade::SceneField::Mesh); + /* Since a mesh can be referenced multiple times, we can't operate in-place */ Containers::Array flattenedMeshes; -for(const Containers::Triple& meshTransformation: - SceneTools::flattenMeshHierarchy2D(scene)) -{ +for(std::size_t i = 0; i != meshesMaterials.size(); ++i) { arrayAppend(flattenedMeshes, MeshTools::transform2D( - meshes[meshTransformation.first()], meshTransformation.third())); + meshes[meshesMaterials[i].second().first()], transformations[i])); } -/* [flattenMeshHierarchy2D-transformations] */ + +Trade::MeshData concatenated = MeshTools::concatenate(flattenedMeshes); +/* [flattenTransformationHierarchy2D-mesh-concatenate] */ } { -/* [flattenMeshHierarchy3D-transformations] */ +/* [flattenTransformationHierarchy3D-mesh-concatenate] */ Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}}); Containers::Array meshes = DOXYGEN_ELLIPSIS({}); +Containers::Array>> + meshesMaterials = scene.meshesMaterialsAsArray(); +Containers::Array transformations = + SceneTools::flattenTransformationHierarchy3D(scene, Trade::SceneField::Mesh); + /* Since a mesh can be referenced multiple times, we can't operate in-place */ Containers::Array flattenedMeshes; -for(const Containers::Triple& meshTransformation: - SceneTools::flattenMeshHierarchy3D(scene)) -{ +for(std::size_t i = 0; i != meshesMaterials.size(); ++i) { arrayAppend(flattenedMeshes, MeshTools::transform3D( - meshes[meshTransformation.first()], meshTransformation.third())); -} -/* [flattenMeshHierarchy3D-transformations] */ + meshes[meshesMaterials[i].second().first()], transformations[i])); } -{ -/* [flattenMeshHierarchy2DInto] */ -Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}}); - -struct Data { - Matrix3 transformation; - UnsignedInt object; - UnsignedInt mesh; -}; -Containers::Array data{NoInit, scene.fieldSize(Trade::SceneField::Mesh)}; - -SceneTools::flattenMeshHierarchy2DInto(scene, - stridedArrayView(data).slice(&Data::transformation)); -scene.meshesMaterialsInto( - stridedArrayView(data).slice(&Data::object), - stridedArrayView(data).slice(&Data::mesh), - nullptr); -/* [flattenMeshHierarchy2DInto] */ -} { -/* [flattenMeshHierarchy3DInto] */ -Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}}); - -struct Data { - Matrix4 transformation; - UnsignedInt object; - UnsignedInt mesh; -}; -Containers::Array data{NoInit, scene.fieldSize(Trade::SceneField::Mesh)}; - -SceneTools::flattenMeshHierarchy3DInto(scene, - stridedArrayView(data).slice(&Data::transformation)); -scene.meshesMaterialsInto( - stridedArrayView(data).slice(&Data::object), - stridedArrayView(data).slice(&Data::mesh), - nullptr); -/* [flattenMeshHierarchy3DInto] */ +Trade::MeshData concatenated = MeshTools::concatenate(flattenedMeshes); +/* [flattenTransformationHierarchy3D-mesh-concatenate] */ } - { /* [orderClusterParents-transformations] */ Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}}); diff --git a/src/Magnum/SceneTools/CMakeLists.txt b/src/Magnum/SceneTools/CMakeLists.txt index 6ffad5730..fc158c426 100644 --- a/src/Magnum/SceneTools/CMakeLists.txt +++ b/src/Magnum/SceneTools/CMakeLists.txt @@ -43,11 +43,11 @@ set(MagnumSceneTools_SRCS ) # Files compiled with different flags for main library and unit test library set(MagnumSceneTools_GracefulAssert_SRCS - FlattenMeshHierarchy.cpp + FlattenTransformationHierarchy.cpp OrderClusterParents.cpp) set(MagnumSceneTools_HEADERS - FlattenMeshHierarchy.h + FlattenTransformationHierarchy.h OrderClusterParents.h visibility.h) @@ -57,6 +57,11 @@ set(MagnumSceneTools_PRIVATE_HEADERS Implementation/convertToSingleFunctionObjects.h Implementation/sceneConverterUtilities.h) +if(MAGNUM_BUILD_DEPRECATED) + list(APPEND MagnumSceneTools_GracefulAssert_SRCS FlattenMeshHierarchy.cpp) + list(APPEND MagnumSceneTools_HEADERS FlattenMeshHierarchy.h) +endif() + ## Objects shared between main and test library #add_library(MagnumSceneToolsObjects OBJECT #${MagnumSceneTools_SRCS} diff --git a/src/Magnum/SceneTools/FlattenMeshHierarchy.cpp b/src/Magnum/SceneTools/FlattenMeshHierarchy.cpp index b54856fd6..27df093f6 100644 --- a/src/Magnum/SceneTools/FlattenMeshHierarchy.cpp +++ b/src/Magnum/SceneTools/FlattenMeshHierarchy.cpp @@ -23,120 +23,68 @@ DEALINGS IN THE SOFTWARE. */ +#define _MAGNUM_NO_DEPRECATED_FLATTENMESHHIERARCHY /* So it doesn't yell here */ + #include "FlattenMeshHierarchy.h" #include -#include -#include #include -#include #include #include "Magnum/DimensionTraits.h" #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#include "Magnum/SceneTools/FlattenTransformationHierarchy.h" #include "Magnum/Trade/SceneData.h" -#include "Magnum/SceneTools/OrderClusterParents.h" namespace Magnum { namespace SceneTools { namespace { -template struct SceneDataDimensionTraits; -template<> struct SceneDataDimensionTraits<2> { - static bool isDimensions(const Trade::SceneData& scene) { - return scene.is2D(); +template struct DimensionTraits; +template<> struct DimensionTraits<2> { + static Containers::Array flatten(const Trade::SceneData& scene, const UnsignedInt fieldId, const Matrix3& globalTransformation) { + return flattenTransformationHierarchy2D(scene, fieldId, globalTransformation); } - static void transformationsInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& transformationDestination) { - return scene.transformations2DInto(mappingDestination, transformationDestination); + static void flattenInto(const Trade::SceneData& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D& transformations, const Matrix3& globalTransformation) { + return flattenTransformationHierarchy2DInto(scene, fieldId, transformations, globalTransformation); } }; -template<> struct SceneDataDimensionTraits<3> { - static bool isDimensions(const Trade::SceneData& scene) { - return scene.is3D(); +template<> struct DimensionTraits<3> { + static Containers::Array flatten(const Trade::SceneData& scene, const UnsignedInt fieldId, const Matrix4& globalTransformation) { + return flattenTransformationHierarchy3D(scene, fieldId, globalTransformation); } - static void transformationsInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& transformationDestination) { - return scene.transformations3DInto(mappingDestination, transformationDestination); + static void flattenInto(const Trade::SceneData& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D& transformations, const Matrix4& globalTransformation) { + return flattenTransformationHierarchy3DInto(scene, fieldId, transformations, globalTransformation); } }; template void flattenMeshHierarchyIntoImplementation(const Trade::SceneData& scene, const Containers::StridedArrayView1D>& outputTransformations, const MatrixTypeFor& globalTransformation) { - CORRADE_ASSERT(SceneDataDimensionTraits::isDimensions(scene), - "SceneTools::flattenMeshHierarchy(): the scene is not" << dimensions << Debug::nospace << "D", ); - const Containers::Optional parentFieldId = scene.findFieldId(Trade::SceneField::Parent); - CORRADE_ASSERT(parentFieldId, - "SceneTools::flattenMeshHierarchy(): the scene has no hierarchy", ); - Containers::Optional meshFieldId = scene.findFieldId(Trade::SceneField::Mesh); - CORRADE_ASSERT(outputTransformations.size() == (meshFieldId ? scene.fieldSize(*meshFieldId) : 0), - "SceneTools::flattenMeshHierarchyInto(): bad output size, expected" << scene.fieldSize(*meshFieldId) << "but got" << outputTransformations.size(), ); - - /* If there's no mesh field in the file, nothing to do. Another case is - that there is a mesh field but it's empty, then for simplicity we still - go through everything. */ + const Containers::Optional meshFieldId = scene.findFieldId(Trade::SceneField::Mesh); + /* If there's no mesh field in the file, nothing to do. This is how the + original API behaved, it's an assertion in the new one. */ if(!meshFieldId) return; - /* Allocate a single storage for all temporary data */ - Containers::ArrayView> orderedClusteredParents; - Containers::ArrayView>> transformations; - Containers::ArrayView> absoluteTransformations; - Containers::ArrayTuple storage{ - /* Output of orderClusterParentsInto() */ - {NoInit, scene.fieldSize(*parentFieldId), orderedClusteredParents}, - /* Output of scene.transformationsXDInto() */ - {NoInit, scene.transformationFieldSize(), transformations}, - /* Above transformations but indexed by object ID */ - {ValueInit, std::size_t(scene.mappingBound() + 1), absoluteTransformations} - }; - orderClusterParentsInto(scene, - stridedArrayView(orderedClusteredParents).slice(&decltype(orderedClusteredParents)::Type::first), - stridedArrayView(orderedClusteredParents).slice(&decltype(orderedClusteredParents)::Type::second)); - SceneDataDimensionTraits::transformationsInto(scene, - stridedArrayView(transformations).slice(&decltype(transformations)::Type::first), - stridedArrayView(transformations).slice(&decltype(transformations)::Type::second)); - - /* Retrieve transformations of all objects, indexed by object ID. Since not - all nodes in the hierarchy may have a transformation assigned, the whole - array got initialized to identity first. */ - /** @todo switch to a hashmap eventually? */ - absoluteTransformations[0] = globalTransformation; - for(const Containers::Pair>& transformation: transformations) { - CORRADE_INTERNAL_ASSERT(transformation.first() < scene.mappingBound()); - absoluteTransformations[transformation.first() + 1] = transformation.second(); - } - - /* Turn the transformations into absolute */ - for(const Containers::Pair& parentOffset: orderedClusteredParents) { - absoluteTransformations[parentOffset.first() + 1] = - absoluteTransformations[parentOffset.second() + 1]* - absoluteTransformations[parentOffset.first() + 1]; - } - - /* Allocate the output array, retrieve mesh & material IDs and assign - absolute transformations to each. The matrix location is abused for - object mapping, which is subsequently replaced by the absolute object - transformation for given mesh. */ - const auto mapping = Containers::arrayCast(outputTransformations); - scene.mappingInto(*meshFieldId, mapping); - for(std::size_t i = 0; i != mapping.size(); ++i) { - CORRADE_INTERNAL_ASSERT(mapping[i] < scene.mappingBound()); - outputTransformations[i] = absoluteTransformations[mapping[i] + 1]; - } + DimensionTraits::flattenInto(scene, *meshFieldId, outputTransformations, globalTransformation); } template Containers::Array>> flattenMeshHierarchyImplementation(const Trade::SceneData& scene, const MatrixTypeFor& globalTransformation) { const Containers::Optional meshFieldId = scene.findFieldId(Trade::SceneField::Mesh); + /* If there's no mesh field in the file, nothing to do. This is how the + original API behaved, it's an assertion in the new one. */ + if(!meshFieldId) return {}; /* Get the transformations. This will be a no-op if the mesh field isn't present, but will go through other assertions that may still be rather valuable */ - Containers::Array>> out{NoInit, meshFieldId ? scene.fieldSize(*meshFieldId) : 0}; - flattenMeshHierarchyIntoImplementation(scene, + Containers::Array>> out{NoInit, scene.fieldSize(*meshFieldId)}; + DimensionTraits::flattenInto(scene, *meshFieldId, stridedArrayView(out).slice(&decltype(out)::Type::third), globalTransformation); /* Fetch the additional mesh and material ID as well, which are in the same order */ - if(meshFieldId) scene.meshesMaterialsInto(nullptr, + scene.meshesMaterialsInto(nullptr, stridedArrayView(out).slice(&decltype(out)::Type::first), stridedArrayView(out).slice(&decltype(out)::Type::second)); diff --git a/src/Magnum/SceneTools/FlattenMeshHierarchy.h b/src/Magnum/SceneTools/FlattenMeshHierarchy.h index 949c0df6e..d1bfe490c 100644 --- a/src/Magnum/SceneTools/FlattenMeshHierarchy.h +++ b/src/Magnum/SceneTools/FlattenMeshHierarchy.h @@ -25,54 +25,44 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MAGNUM_BUILD_DEPRECATED /** @file * @brief Function @ref Magnum::SceneTools::flattenMeshHierarchy2D(), @ref Magnum::SceneTools::flattenMeshHierarchy2DInto(), @ref Magnum::SceneTools::flattenMeshHierarchy3D(), @ref Magnum::SceneTools::flattenMeshHierarchy3DInto() - * @m_since_latest + * @m_deprecated_since_latest Use @ref Magnum/SceneTools/FlattenTransformationHierarchy.h + * and the @relativeref{Magnum,SceneTools::flattenTransformationHierarchy2D()} / + * @relativeref{Magnum,SceneTools::flattenTransformationHierarchy3D()} + * functions instead. */ +#endif + +#include "Magnum/configure.h" + +#ifdef MAGNUM_BUILD_DEPRECATED +#include #include "Magnum/Magnum.h" #include "Magnum/SceneTools/visibility.h" #include "Magnum/Trade/Trade.h" +#ifndef _MAGNUM_NO_DEPRECATED_FLATTENMESHHIERARCHY +CORRADE_DEPRECATED_FILE("use Magnum/SceneTools/FlattenTransformationHierarchy.h and the SceneTools::flattenTransformationHierarchy*D() functions instead") +#endif + namespace Magnum { namespace SceneTools { /** @brief Flatten a 2D mesh hierarchy -@m_since_latest - -For all @ref Trade::SceneField::Mesh entries returns a triple of mesh ID, -@ref Trade::SceneField::MeshMaterial and its absolute transformation in the -scene with @p globalTransformation prepended. The -@ref Trade::SceneField::Parent field is expected to be contained in the scene, -having no cycles or duplicates, and the scene is expected to be 2D. If -@ref Trade::SceneField::Mesh is not present or is empty, returns an empty -array. You can then use @ref MeshTools::transform2D() to apply the -transformations to actual meshes: - -@snippet MagnumSceneTools.cpp flattenMeshHierarchy2D-transformations - -The operation is done in an @f$ \mathcal{O}(m + n) @f$ execution time and -memory complexity, with @f$ m @f$ being size of the @ref Trade::SceneField::Mesh -field and @f$ n @f$ being @ref Trade::SceneData::mappingBound(). The function -calls @ref orderClusterParents() internally. - -The returned data are in the same order as the @ref Trade::SceneField::Mesh -attribute. Meshes attached to objects without a @ref Trade::SceneField::Parent -or to objects in loose hierarchy subtrees will have their transformation set to -an unspecified value. - -@experimental - -@see @ref flattenMeshHierarchy2DInto(), @ref flattenMeshHierarchy3D(), - @ref Trade::SceneData::hasField(), @ref Trade::SceneData::is2D(), - @ref MeshTools::concatenate() + +@m_deprecated_since_latest Use @ref flattenTransformationHierarchy2D(const Trade::SceneData&, Trade::SceneField, const Matrix3& globalTransformation) + with @ref Trade::SceneField::Mesh together with + @ref Trade::SceneData::meshesMaterialsAsArray() instead. */ #ifdef DOXYGEN_GENERATING_OUTPUT MAGNUM_SCENETOOLS_EXPORT Containers::Array> flattenMeshHierarchy2D(const Trade::SceneData& scene, const Matrix3& globalTransformation = {}); #else /* To avoid including Matrix3 */ -MAGNUM_SCENETOOLS_EXPORT Containers::Array> flattenMeshHierarchy2D(const Trade::SceneData& scene, const Matrix3& globalTransformation); -MAGNUM_SCENETOOLS_EXPORT Containers::Array> flattenMeshHierarchy2D(const Trade::SceneData& scene); +CORRADE_DEPRECATED("use flattenTransformationHierarchy2D() instead") MAGNUM_SCENETOOLS_EXPORT Containers::Array> flattenMeshHierarchy2D(const Trade::SceneData& scene, const Matrix3& globalTransformation); +CORRADE_DEPRECATED("use flattenTransformationHierarchy2D() instead") MAGNUM_SCENETOOLS_EXPORT Containers::Array> flattenMeshHierarchy2D(const Trade::SceneData& scene); #endif /** @@ -80,65 +70,31 @@ MAGNUM_SCENETOOLS_EXPORT Containers::Array&, const Matrix3&) + with @ref Trade::SceneField::Mesh instead. */ #ifdef DOXYGEN_GENERATING_OUTPUT MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy2DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations, const Matrix3& globalTransformation = {}); #else /* To avoid including Matrix3 */ -MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy2DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations, const Matrix3& globalTransformation); -MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy2DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations); +CORRADE_DEPRECATED("use flattenTransformationHierarchy2DInto() instead") MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy2DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations, const Matrix3& globalTransformation); +CORRADE_DEPRECATED("use flattenTransformationHierarchy2DInto() instead") MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy2DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations); #endif /** @brief Flatten a 3D mesh hierarchy -@m_since_latest - -For all @ref Trade::SceneField::Mesh entries returns a triple of mesh ID, -@ref Trade::SceneField::MeshMaterial and its absolute transformation in the -scene with @p globalTransformation prepended. The -@ref Trade::SceneField::Parent field is expected to be contained in the scene, -having no cycles or duplicates, and the scene is expected to be 3D. If -@ref Trade::SceneField::Mesh is not present or is empty, returns an empty -array. You can then use @ref MeshTools::transform3D() to apply the -transformations to actual meshes: - -@snippet MagnumSceneTools.cpp flattenMeshHierarchy3D-transformations - -The operation is done in an @f$ \mathcal{O}(m + n) @f$ execution time and -memory complexity, with @f$ m @f$ being size of the @ref Trade::SceneField::Mesh -field and @f$ n @f$ being @ref Trade::SceneData::mappingBound(). The function -calls @ref orderClusterParents() internally. - -The returned data are in the same order as the @ref Trade::SceneField::Mesh -attribute. Meshes attached to objects without a @ref Trade::SceneField::Parent -or to objects in loose hierarchy subtrees will have their transformation set to -an unspecified value. - -@experimental - -@see @ref flattenMeshHierarchy3DInto(), @ref flattenMeshHierarchy2D(), - @ref Trade::SceneData::hasField(), @ref Trade::SceneData::is3D(), - @ref MeshTools::concatenate() + +@m_deprecated_since_latest Use @ref flattenTransformationHierarchy3D(const Trade::SceneData&, Trade::SceneField, const Matrix4&) + with @ref Trade::SceneField::Mesh together with + @ref Trade::SceneData::meshesMaterialsAsArray() instead. */ #ifdef DOXYGEN_GENERATING_OUTPUT MAGNUM_SCENETOOLS_EXPORT Containers::Array> flattenMeshHierarchy3D(const Trade::SceneData& scene, const Matrix4& globalTransformation = {}); #else /* To avoid including Matrix4 */ -MAGNUM_SCENETOOLS_EXPORT Containers::Array> flattenMeshHierarchy3D(const Trade::SceneData& scene, const Matrix4& globalTransformation); -MAGNUM_SCENETOOLS_EXPORT Containers::Array> flattenMeshHierarchy3D(const Trade::SceneData& scene); +CORRADE_DEPRECATED("use flattenTransformationHierarchy3D() instead") MAGNUM_SCENETOOLS_EXPORT Containers::Array> flattenMeshHierarchy3D(const Trade::SceneData& scene, const Matrix4& globalTransformation); +CORRADE_DEPRECATED("use flattenTransformationHierarchy3D() instead") MAGNUM_SCENETOOLS_EXPORT Containers::Array> flattenMeshHierarchy3D(const Trade::SceneData& scene); #endif /** @@ -146,28 +102,21 @@ MAGNUM_SCENETOOLS_EXPORT Containers::Array&, const Matrix4&) + with @ref Trade::SceneField::Mesh instead. */ #ifdef DOXYGEN_GENERATING_OUTPUT -MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations, const Matrix4& globalTransformation = {}); +CORRADE_DEPRECATED("use flattenTransformationHierarchy3DInto() instead") MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations, const Matrix4& globalTransformation = {}); #else /* To avoid including Matrix3 */ -MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations, const Matrix4& globalTransformation); -MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations); +CORRADE_DEPRECATED("use flattenTransformationHierarchy3DInto() instead") MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations, const Matrix4& globalTransformation); +CORRADE_DEPRECATED("use flattenTransformationHierarchy3DInto() instead") MAGNUM_SCENETOOLS_EXPORT void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations); #endif }} +#else +#error use Magnum/SceneTools/FlattenTransformationHierarchy.h and the SceneTools::flattenTransformationHierarchy*D() functions instead +#endif #endif diff --git a/src/Magnum/SceneTools/FlattenTransformationHierarchy.cpp b/src/Magnum/SceneTools/FlattenTransformationHierarchy.cpp new file mode 100644 index 000000000..014de3ea3 --- /dev/null +++ b/src/Magnum/SceneTools/FlattenTransformationHierarchy.cpp @@ -0,0 +1,214 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "FlattenTransformationHierarchy.h" + +#include +#include +#include +#include +#include + +#include "Magnum/DimensionTraits.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" +#include "Magnum/Trade/SceneData.h" +#include "Magnum/SceneTools/OrderClusterParents.h" + +namespace Magnum { namespace SceneTools { + +namespace { + +template struct SceneDataDimensionTraits; +template<> struct SceneDataDimensionTraits<2> { + static bool isDimensions(const Trade::SceneData& scene) { + return scene.is2D(); + } + static void transformationsInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& transformationDestination) { + return scene.transformations2DInto(mappingDestination, transformationDestination); + } +}; +template<> struct SceneDataDimensionTraits<3> { + static bool isDimensions(const Trade::SceneData& scene) { + return scene.is3D(); + } + static void transformationsInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& mappingDestination, const Containers::StridedArrayView1D& transformationDestination) { + return scene.transformations3DInto(mappingDestination, transformationDestination); + } +}; + +template void flattenTransformationHierarchyIntoImplementation(const Trade::SceneData& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D>& outputTransformations, const MatrixTypeFor& globalTransformation) { + CORRADE_ASSERT(SceneDataDimensionTraits::isDimensions(scene), + "SceneTools::flattenTransformationHierarchy(): the scene is not" << dimensions << Debug::nospace << "D", ); + CORRADE_ASSERT(fieldId < scene.fieldCount(), + "SceneTools::flattenTransformationHierarchy(): index" << fieldId << "out of range for" << scene.fieldCount() << "fields", ); + const Containers::Optional parentFieldId = scene.findFieldId(Trade::SceneField::Parent); + CORRADE_ASSERT(parentFieldId, + "SceneTools::flattenTransformationHierarchy(): the scene has no hierarchy", ); + CORRADE_ASSERT(outputTransformations.size() == scene.fieldSize(fieldId), + "SceneTools::flattenTransformationHierarchyInto(): bad output size, expected" << scene.fieldSize(fieldId) << "but got" << outputTransformations.size(), ); + + /* Allocate a single storage for all temporary data */ + Containers::ArrayView> orderedClusteredParents; + Containers::ArrayView>> transformations; + Containers::ArrayView> absoluteTransformations; + Containers::ArrayTuple storage{ + /* Output of orderClusterParentsInto() */ + {NoInit, scene.fieldSize(*parentFieldId), orderedClusteredParents}, + /* Output of scene.transformationsXDInto() */ + {NoInit, scene.transformationFieldSize(), transformations}, + /* Above transformations but indexed by object ID */ + {ValueInit, std::size_t(scene.mappingBound() + 1), absoluteTransformations} + }; + orderClusterParentsInto(scene, + stridedArrayView(orderedClusteredParents).slice(&decltype(orderedClusteredParents)::Type::first), + stridedArrayView(orderedClusteredParents).slice(&decltype(orderedClusteredParents)::Type::second)); + SceneDataDimensionTraits::transformationsInto(scene, + stridedArrayView(transformations).slice(&decltype(transformations)::Type::first), + stridedArrayView(transformations).slice(&decltype(transformations)::Type::second)); + + /* Retrieve transformations of all objects, indexed by object ID. Since not + all nodes in the hierarchy may have a transformation assigned, the whole + array got initialized to identity first. */ + /** @todo switch to a hashmap eventually? */ + absoluteTransformations[0] = globalTransformation; + for(const Containers::Pair>& transformation: transformations) { + CORRADE_INTERNAL_ASSERT(transformation.first() < scene.mappingBound()); + absoluteTransformations[transformation.first() + 1] = transformation.second(); + } + + /* Turn the transformations into absolute */ + for(const Containers::Pair& parentOffset: orderedClusteredParents) { + absoluteTransformations[parentOffset.first() + 1] = + absoluteTransformations[parentOffset.second() + 1]* + absoluteTransformations[parentOffset.first() + 1]; + } + + /* Allocate the output array, retrieve mesh & material IDs and assign + absolute transformations to each. The matrix location is abused for + object mapping, which is subsequently replaced by the absolute object + transformation for given mesh. */ + const auto mapping = Containers::arrayCast(outputTransformations); + scene.mappingInto(fieldId, mapping); + for(std::size_t i = 0; i != mapping.size(); ++i) { + CORRADE_INTERNAL_ASSERT(mapping[i] < scene.mappingBound()); + outputTransformations[i] = absoluteTransformations[mapping[i] + 1]; + } +} + +template void flattenTransformationHierarchyIntoImplementation(const Trade::SceneData& scene, const Trade::SceneField field, const Containers::StridedArrayView1D>& outputTransformations, const MatrixTypeFor& globalTransformation) { + const Containers::Optional fieldId = scene.findFieldId(field); + CORRADE_ASSERT(fieldId, + "SceneTools::flattenTransformationHierarchyInto(): field" << field << "not found", ); + + flattenTransformationHierarchyIntoImplementation(scene, *fieldId, outputTransformations, globalTransformation); +} + +template Containers::Array> flattenTransformationHierarchyImplementation(const Trade::SceneData& scene, const UnsignedInt fieldId, const MatrixTypeFor& globalTransformation) { + CORRADE_ASSERT(fieldId < scene.fieldCount(), + "SceneTools::flattenTransformationHierarchy(): index" << fieldId << "out of range for" << scene.fieldCount() << "fields", {}); + + Containers::Array> out{NoInit, scene.fieldSize(fieldId)}; + flattenTransformationHierarchyIntoImplementation(scene, fieldId, out, globalTransformation); + return out; +} + +template Containers::Array> flattenTransformationHierarchyImplementation(const Trade::SceneData& scene, const Trade::SceneField field, const MatrixTypeFor& globalTransformation) { + const Containers::Optional fieldId = scene.findFieldId(field); + CORRADE_ASSERT(fieldId, + "SceneTools::flattenTransformationHierarchy(): field" << field << "not found", {}); + + Containers::Array> out{NoInit, scene.fieldSize(*fieldId)}; + flattenTransformationHierarchyIntoImplementation(scene, *fieldId, out, globalTransformation); + return out; +} + +} + +Containers::Array flattenTransformationHierarchy2D(const Trade::SceneData& scene, const Trade::SceneField field, const Matrix3& globalTransformation) { + return flattenTransformationHierarchyImplementation<2>(scene, field, globalTransformation); +} + +Containers::Array flattenTransformationHierarchy2D(const Trade::SceneData& scene, const Trade::SceneField field) { + return flattenTransformationHierarchyImplementation<2>(scene, field, {}); +} + +Containers::Array flattenTransformationHierarchy2D(const Trade::SceneData& scene, const UnsignedInt fieldId, const Matrix3& globalTransformation) { + return flattenTransformationHierarchyImplementation<2>(scene, fieldId, globalTransformation); +} + +Containers::Array flattenTransformationHierarchy2D(const Trade::SceneData& scene, const UnsignedInt fieldId) { + return flattenTransformationHierarchyImplementation<2>(scene, fieldId, {}); +} + +void flattenTransformationHierarchy2DInto(const Trade::SceneData& scene, const Trade::SceneField field, const Containers::StridedArrayView1D& transformations, const Matrix3& globalTransformation) { + return flattenTransformationHierarchyIntoImplementation<2>(scene, field, transformations, globalTransformation); +} + +void flattenTransformationHierarchy2DInto(const Trade::SceneData& scene, const Trade::SceneField field, const Containers::StridedArrayView1D& transformations) { + return flattenTransformationHierarchyIntoImplementation<2>(scene, field, transformations, {}); +} + +void flattenTransformationHierarchy2DInto(const Trade::SceneData& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D& transformations, const Matrix3& globalTransformation) { + return flattenTransformationHierarchyIntoImplementation<2>(scene, fieldId, transformations, globalTransformation); +} + +void flattenTransformationHierarchy2DInto(const Trade::SceneData& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D& transformations) { + return flattenTransformationHierarchyIntoImplementation<2>(scene, fieldId, transformations, {}); +} + +Containers::Array flattenTransformationHierarchy3D(const Trade::SceneData& scene, const Trade::SceneField field, const Matrix4& globalTransformation) { + return flattenTransformationHierarchyImplementation<3>(scene, field, globalTransformation); +} + +Containers::Array flattenTransformationHierarchy3D(const Trade::SceneData& scene, const Trade::SceneField field) { + return flattenTransformationHierarchyImplementation<3>(scene, field, {}); +} + +Containers::Array flattenTransformationHierarchy3D(const Trade::SceneData& scene, const UnsignedInt fieldId, const Matrix4& globalTransformation) { + return flattenTransformationHierarchyImplementation<3>(scene, fieldId, globalTransformation); +} + +Containers::Array flattenTransformationHierarchy3D(const Trade::SceneData& scene, const UnsignedInt fieldId) { + return flattenTransformationHierarchyImplementation<3>(scene, fieldId, {}); +} + +void flattenTransformationHierarchy3DInto(const Trade::SceneData& scene, const Trade::SceneField field, const Containers::StridedArrayView1D& transformations, const Matrix4& globalTransformation) { + return flattenTransformationHierarchyIntoImplementation<3>(scene, field, transformations, globalTransformation); +} + +void flattenTransformationHierarchy3DInto(const Trade::SceneData& scene, const Trade::SceneField field, const Containers::StridedArrayView1D& transformations) { + return flattenTransformationHierarchyIntoImplementation<3>(scene, field, transformations, {}); +} + +void flattenTransformationHierarchy3DInto(const Trade::SceneData& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D& transformations, const Matrix4& globalTransformation) { + return flattenTransformationHierarchyIntoImplementation<3>(scene, fieldId, transformations, globalTransformation); +} + +void flattenTransformationHierarchy3DInto(const Trade::SceneData& scene, const UnsignedInt fieldId, const Containers::StridedArrayView1D& transformations) { + return flattenTransformationHierarchyIntoImplementation<3>(scene, fieldId, transformations, {}); +} + +}} diff --git a/src/Magnum/SceneTools/FlattenTransformationHierarchy.h b/src/Magnum/SceneTools/FlattenTransformationHierarchy.h new file mode 100644 index 000000000..b3d0b9710 --- /dev/null +++ b/src/Magnum/SceneTools/FlattenTransformationHierarchy.h @@ -0,0 +1,243 @@ +#ifndef Magnum_SceneTools_FlattenTransformationHierarchy_h +#define Magnum_SceneTools_FlattenTransformationHierarchy_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Function @ref Magnum::SceneTools::flattenTransformationHierarchy2D(), @ref Magnum::SceneTools::flattenTransformationHierarchy2DInto(), @ref Magnum::SceneTools::flattenTransformationHierarchy3D(), @ref Magnum::SceneTools::flattenTransformationHierarchy3DInto() + * @m_since_latest + */ + +#include "Magnum/Magnum.h" +#include "Magnum/SceneTools/visibility.h" +#include "Magnum/Trade/Trade.h" + +namespace Magnum { namespace SceneTools { + +/** +@brief Flatten a 2D transformation hierarchy for given field +@m_since_latest + +For all entries of given @p field in @p scene returns an absolute +transformation of the object they're attached to in the scene with +@p globalTransformation prepended. The @ref Trade::SceneField::Parent field is +expected to be contained in the scene, having no cycles or duplicates, and the +scene is expected to be 2D. The @p field is expected to be present, if it's +empty, the function returns an empty array. + +The operation is done in an @f$ \mathcal{O}(m + n) @f$ execution time and +memory complexity, with @f$ m @f$ being size of @p field and @f$ n @f$ being +@ref Trade::SceneData::mappingBound(). The function calls +@ref orderClusterParents() internally. + +The returned data are in the same order as object mapping entries in @p field. +Fields attached to objects without a @ref Trade::SceneField::Parent or to +objects in loose hierarchy subtrees will have their transformation set to an +unspecified value. + +This function can be used for example to flatten a mesh hierarchy, bake +the transformations to actual meshes and then concatenate them together into a +single mesh: + +@snippet MagnumSceneTools.cpp flattenTransformationHierarchy2D-mesh-concatenate + +@experimental + +@see @ref flattenTransformationHierarchy2D(const Trade::SceneData&, UnsignedInt, const Matrix3&), + @ref flattenTransformationHierarchy2DInto(), + @ref flattenTransformationHierarchy3D(), @ref Trade::SceneData::hasField(), + @ref Trade::SceneData::is2D() +*/ +#ifdef DOXYGEN_GENERATING_OUTPUT +MAGNUM_SCENETOOLS_EXPORT Containers::Array flattenTransformationHierarchy2D(const Trade::SceneData& scene, Trade::SceneField field, const Matrix3& globalTransformation = {}); +#else +/* To avoid including Matrix3 */ +MAGNUM_SCENETOOLS_EXPORT Containers::Array flattenTransformationHierarchy2D(const Trade::SceneData& scene, Trade::SceneField field, const Matrix3& globalTransformation); +MAGNUM_SCENETOOLS_EXPORT Containers::Array flattenTransformationHierarchy2D(const Trade::SceneData& scene, Trade::SceneField field); +#endif + +/** +@brief Flatten a 2D transformation hierarchy for given field ID +@m_since_latest + +A variant of @ref flattenTransformationHierarchy2D(const Trade::SceneData&, Trade::SceneField, const Matrix3&) +that takes a field ID instead of name. Useful for example in combination with +@ref Trade::SceneData::findFieldId() to avoid a double lookup if it isn't clear +if a field exists at all. The @p fieldId is expected to be smaller than +@ref Trade::SceneData::fieldCount(). +@experimental +*/ +#ifdef DOXYGEN_GENERATING_OUTPUT +MAGNUM_SCENETOOLS_EXPORT Containers::Array flattenTransformationHierarchy2D(const Trade::SceneData& scene, UnsignedInt fieldId, const Matrix3& globalTransformation = {}); +#else +/* To avoid including Matrix3 */ +MAGNUM_SCENETOOLS_EXPORT Containers::Array flattenTransformationHierarchy2D(const Trade::SceneData& scene, UnsignedInt fieldId, const Matrix3& globalTransformation); +MAGNUM_SCENETOOLS_EXPORT Containers::Array flattenTransformationHierarchy2D(const Trade::SceneData& scene, UnsignedInt fieldId); +#endif + +/** +@brief Flatten a 2D transformation hierarchy for given field into an existing array +@param[in] scene Input scene +@param[in] field Field to calculate the transformations for +@param[out] transformations Where to put the calculated transformations +@param[in] globalTransformation Global transformation to prepend +@m_since_latest + +A variant of @ref flattenTransformationHierarchy2D(const Trade::SceneData&, Trade::SceneField, const Matrix3&) +that fills existing memory instead of allocating a new array. The +@p transformations array is expected to have the same size as the @p field. +@experimental +*/ +#ifdef DOXYGEN_GENERATING_OUTPUT +MAGNUM_SCENETOOLS_EXPORT void flattenTransformationHierarchy2DInto(const Trade::SceneData& scene, Trade::SceneField field, const Containers::StridedArrayView1D& transformations, const Matrix3& globalTransformation = {}); +#else +/* To avoid including Matrix3 */ +MAGNUM_SCENETOOLS_EXPORT void flattenTransformationHierarchy2DInto(const Trade::SceneData& scene, Trade::SceneField field, const Containers::StridedArrayView1D& transformations, const Matrix3& globalTransformation); +MAGNUM_SCENETOOLS_EXPORT void flattenTransformationHierarchy2DInto(const Trade::SceneData& scene, Trade::SceneField field, const Containers::StridedArrayView1D& transformations); +#endif + +/** +@brief Flatten a 2D transformation hierarchy for given field ID into an existing array +@m_since_latest + +A variant of @ref flattenTransformationHierarchy2DInto(const Trade::SceneData&, Trade::SceneField, const Containers::StridedArrayView1D&, const Matrix3&) +that takes a field ID instead of name. Useful for example in combination with +@ref Trade::SceneData::findFieldId() to avoid a double lookup if it isn't clear +if a field exists at all. The @p fieldId is expected to be smaller than +@ref Trade::SceneData::fieldCount(). +@experimental +*/ +#ifdef DOXYGEN_GENERATING_OUTPUT +MAGNUM_SCENETOOLS_EXPORT void flattenTransformationHierarchy2DInto(const Trade::SceneData& scene, UnsignedInt fieldId, const Containers::StridedArrayView1D& transformations, const Matrix3& globalTransformation = {}); +#else +/* To avoid including Matrix3 */ +MAGNUM_SCENETOOLS_EXPORT void flattenTransformationHierarchy2DInto(const Trade::SceneData& scene, UnsignedInt fieldId, const Containers::StridedArrayView1D& transformations, const Matrix3& globalTransformation); +MAGNUM_SCENETOOLS_EXPORT void flattenTransformationHierarchy2DInto(const Trade::SceneData& scene, UnsignedInt fieldId, const Containers::StridedArrayView1D& transformations); +#endif + +/** +@brief Flatten a 3D transformation hierarchy for given field +@m_since_latest + +For all entries of given @p field in @p scene returns an absolute +transformation of the object they're attached to in the scene with +@p globalTransformation prepended. The @ref Trade::SceneField::Parent field is +expected to be contained in the scene, having no cycles or duplicates, and the +scene is expected to be 3D. The @p field is expected to be present, if it's +empty, the function returns an empty array. + +The operation is done in an @f$ \mathcal{O}(m + n) @f$ execution time and +memory complexity, with @f$ m @f$ being size of @p field and @f$ n @f$ being +@ref Trade::SceneData::mappingBound(). The function calls +@ref orderClusterParents() internally. + +The returned data are in the same order as object mapping entries in @p field. +Fields attached to objects without a @ref Trade::SceneField::Parent or to +objects in loose hierarchy subtrees will have their transformation set to an +unspecified value. + +This function can be used for example to flatten a mesh hierarchy, bake +the transformations to actual meshes and then concatenate them together into a +single mesh: + +@snippet MagnumSceneTools.cpp flattenTransformationHierarchy3D-mesh-concatenate + +@experimental + +@see @ref flattenTransformationHierarchy3D(const Trade::SceneData&, UnsignedInt, const Matrix4&), + @ref flattenTransformationHierarchy3DInto(), + @ref flattenTransformationHierarchy2D(), @ref Trade::SceneData::hasField(), + @ref Trade::SceneData::is3D() +*/ +#ifdef DOXYGEN_GENERATING_OUTPUT +MAGNUM_SCENETOOLS_EXPORT Containers::Array flattenTransformationHierarchy3D(const Trade::SceneData& scene, Trade::SceneField field, const Matrix4& globalTransformation = {}); +#else +/* To avoid including Matrix4 */ +MAGNUM_SCENETOOLS_EXPORT Containers::Array flattenTransformationHierarchy3D(const Trade::SceneData& scene, Trade::SceneField field, const Matrix4& globalTransformation); +MAGNUM_SCENETOOLS_EXPORT Containers::Array flattenTransformationHierarchy3D(const Trade::SceneData& scene, Trade::SceneField field); +#endif + +/** +@brief Flatten a 3D transformation hierarchy for given field ID +@m_since_latest + +A variant of @ref flattenTransformationHierarchy3D(const Trade::SceneData&, Trade::SceneField, const Matrix4&) +that takes a field ID instead of name. Useful for example in combination with +@ref Trade::SceneData::findFieldId() to avoid a double lookup if it isn't clear +if a field exists at all. The @p fieldId is expected to be smaller than +@ref Trade::SceneData::fieldCount(). +@experimental +*/ +#ifdef DOXYGEN_GENERATING_OUTPUT +MAGNUM_SCENETOOLS_EXPORT Containers::Array flattenTransformationHierarchy3D(const Trade::SceneData& scene, UnsignedInt fieldId, const Matrix4& globalTransformation = {}); +#else +/* To avoid including Matrix34 */ +MAGNUM_SCENETOOLS_EXPORT Containers::Array flattenTransformationHierarchy3D(const Trade::SceneData& scene, UnsignedInt fieldId, const Matrix4& globalTransformation); +MAGNUM_SCENETOOLS_EXPORT Containers::Array flattenTransformationHierarchy3D(const Trade::SceneData& scene, UnsignedInt fieldId); +#endif + +/** +@brief Flatten a 3D transformation hierarchy for given field into an existing array +@param[in] scene Input scene +@param[in] field Field to calculate the transformations for +@param[out] transformations Where to put the calculated transformations +@param[in] globalTransformation Global transformation to prepend +@m_since_latest + +A variant of @ref flattenTransformationHierarchy3D(const Trade::SceneData&, Trade::SceneField, const Matrix4&) +that fills existing memory instead of allocating a new array. The +@p transformations array is expected to have the same size as the @p field. +@experimental +*/ +#ifdef DOXYGEN_GENERATING_OUTPUT +MAGNUM_SCENETOOLS_EXPORT void flattenTransformationHierarchy3DInto(const Trade::SceneData& scene, Trade::SceneField field, const Containers::StridedArrayView1D& transformations, const Matrix4& globalTransformation = {}); +#else +/* To avoid including Matrix4 */ +MAGNUM_SCENETOOLS_EXPORT void flattenTransformationHierarchy3DInto(const Trade::SceneData& scene, Trade::SceneField field, const Containers::StridedArrayView1D& transformations, const Matrix4& globalTransformation); +MAGNUM_SCENETOOLS_EXPORT void flattenTransformationHierarchy3DInto(const Trade::SceneData& scene, Trade::SceneField field, const Containers::StridedArrayView1D& transformations); +#endif + +/** +@brief Flatten a 3D transformation hierarchy for given field into an existing array +@m_since_latest + +A variant of @ref flattenTransformationHierarchy3DInto(const Trade::SceneData&, Trade::SceneField, const Containers::StridedArrayView1D&, const Matrix4&) +that takes a field ID instead of name. Useful for example in combination with +@ref Trade::SceneData::findFieldId() to avoid a double lookup if it isn't clear +if a field exists at all. The @p fieldId is expected to be smaller than +@ref Trade::SceneData::fieldCount(). +@experimental +*/ +#ifdef DOXYGEN_GENERATING_OUTPUT +MAGNUM_SCENETOOLS_EXPORT void flattenTransformationHierarchy3DInto(const Trade::SceneData& scene, UnsignedInt fieldId, const Containers::StridedArrayView1D& transformations, const Matrix4& globalTransformation = {}); +#else +/* To avoid including Matrix4 */ +MAGNUM_SCENETOOLS_EXPORT void flattenTransformationHierarchy3DInto(const Trade::SceneData& scene, UnsignedInt fieldId, const Containers::StridedArrayView1D& transformations, const Matrix4& globalTransformation); +MAGNUM_SCENETOOLS_EXPORT void flattenTransformationHierarchy3DInto(const Trade::SceneData& scene, UnsignedInt fieldId, const Containers::StridedArrayView1D& transformations); +#endif + +}} + +#endif diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index eeaab8930..633b5aaef 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -52,7 +52,7 @@ file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h corrade_add_test(SceneToolsCombineTest CombineTest.cpp LIBRARIES MagnumTrade) corrade_add_test(SceneToolsConvertToSingleFunc___Test ConvertToSingleFunctionObjectsTest.cpp LIBRARIES MagnumTrade) -corrade_add_test(SceneToolsFlattenMeshHierarchyTest FlattenMeshHierarchyTest.cpp LIBRARIES MagnumSceneToolsTestLib) +corrade_add_test(SceneToolsFlattenTra___HierarchyTest FlattenTransformationHierarchyTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsOrderClusterParentsTest OrderClusterParentsTest.cpp LIBRARIES MagnumSceneToolsTestLib) corrade_add_test(SceneToolsSceneConverterImple___Test SceneConverterImplementationTest.cpp @@ -82,6 +82,10 @@ if(MAGNUM_WITH_ANYSCENECONVERTER) endif() endif() +if(MAGNUM_BUILD_DEPRECATED) + corrade_add_test(SceneToolsFlattenMeshHierarchyTest FlattenMeshHierarchyTest.cpp LIBRARIES MagnumSceneToolsTestLib) +endif() + # Executable testing is implemented on Unix platforms only at the moment if(CORRADE_TARGET_UNIX AND NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT) corrade_add_test(SceneToolsSceneConverterTest SceneConverterTest.cpp diff --git a/src/Magnum/SceneTools/Test/FlattenMeshHierarchyTest.cpp b/src/Magnum/SceneTools/Test/FlattenMeshHierarchyTest.cpp index 95781fcd9..7ff6efd7b 100644 --- a/src/Magnum/SceneTools/Test/FlattenMeshHierarchyTest.cpp +++ b/src/Magnum/SceneTools/Test/FlattenMeshHierarchyTest.cpp @@ -29,6 +29,9 @@ #include #include +/* There's no better way to disable file deprecation warnings */ +#define _MAGNUM_NO_DEPRECATED_FLATTENMESHHIERARCHY + #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" #include "Magnum/SceneTools/FlattenMeshHierarchy.h" @@ -219,10 +222,12 @@ void FlattenMeshHierarchyTest::test2D() { Containers::Array> out; /* To test the parameter-less overload also */ + CORRADE_IGNORE_DEPRECATED_PUSH if(data.globalTransformation2D != Matrix3{}) out = flattenMeshHierarchy2D(scene, data.globalTransformation2D); else out = flattenMeshHierarchy2D(scene); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE_AS(out, (Containers::arrayView>({ {113, 96, data.globalTransformation2D* @@ -278,10 +283,12 @@ void FlattenMeshHierarchyTest::test3D() { Containers::Array> out; /* To test the parameter-less overload also */ + CORRADE_IGNORE_DEPRECATED_PUSH if(data.globalTransformation3D != Matrix4{}) out = flattenMeshHierarchy3D(scene, data.globalTransformation3D); else out = flattenMeshHierarchy3D(scene); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE_AS(out, (Containers::arrayView>({ {113, 96, data.globalTransformation3D* @@ -303,29 +310,42 @@ void FlattenMeshHierarchyTest::test3D() { void FlattenMeshHierarchyTest::not2DNot3D() { CORRADE_SKIP_IF_NO_ASSERT(); - Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 0, nullptr, {}}; + /* Used to assert even on an empty scene, now it does an early-out if the + mesh field doesn't exist because flattenTransformationHierarchy() would + assert instead. That behavioral change is fine for a deprecated API. */ + Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 0, nullptr, { + Trade::SceneFieldData{Trade::SceneField::Mesh, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::UnsignedInt, nullptr}, + }}; std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH flattenMeshHierarchy2D(scene); flattenMeshHierarchy3D(scene); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), - "SceneTools::flattenMeshHierarchy(): the scene is not 2D\n" - "SceneTools::flattenMeshHierarchy(): the scene is not 3D\n"); + "SceneTools::flattenTransformationHierarchy(): the scene is not 2D\n" + "SceneTools::flattenTransformationHierarchy(): the scene is not 3D\n"); } void FlattenMeshHierarchyTest::noParentField() { CORRADE_SKIP_IF_NO_ASSERT(); + /* Used to assert even on an empty scene, now it does an early-out if the + mesh field doesn't exist because flattenTransformationHierarchy() would + assert instead. That behavioral change is fine for a deprecated API. */ Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 0, nullptr, { + Trade::SceneFieldData{Trade::SceneField::Mesh, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::UnsignedInt, nullptr}, Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix3x3, nullptr} }}; std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH flattenMeshHierarchy2D(scene); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), - "SceneTools::flattenMeshHierarchy(): the scene has no hierarchy\n"); + "SceneTools::flattenTransformationHierarchy(): the scene has no hierarchy\n"); } void FlattenMeshHierarchyTest::noMeshField() { @@ -335,9 +355,11 @@ void FlattenMeshHierarchyTest::noMeshField() { }}; /* This should not blow up, just return nothing */ + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE_AS(flattenMeshHierarchy2D(scene), (Containers::ArrayView>{}), TestSuite::Compare::Container); + CORRADE_IGNORE_DEPRECATED_POP } void FlattenMeshHierarchyTest::into2D() { @@ -368,10 +390,12 @@ void FlattenMeshHierarchyTest::into2D() { Containers::Array out{NoInit, scene.fieldSize(Trade::SceneField::Mesh)}; /* To test the parameter-less overload also */ + CORRADE_IGNORE_DEPRECATED_PUSH if(data.globalTransformation2D != Matrix3{}) flattenMeshHierarchy2DInto(scene, out, data.globalTransformation2D); else flattenMeshHierarchy2DInto(scene, out); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE_AS(out, Containers::arrayView({ data.globalTransformation2D* @@ -418,10 +442,12 @@ void FlattenMeshHierarchyTest::into3D() { Containers::Array out{NoInit, scene.fieldSize(Trade::SceneField::Mesh)}; /* To test the parameter-less overload also */ + CORRADE_IGNORE_DEPRECATED_PUSH if(data.globalTransformation3D != Matrix4{}) flattenMeshHierarchy3DInto(scene, out, data.globalTransformation3D); else flattenMeshHierarchy3DInto(scene, out); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE_AS(out, Containers::arrayView({ data.globalTransformation3D* @@ -468,11 +494,13 @@ void FlattenMeshHierarchyTest::intoInvalidSize() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH flattenMeshHierarchy2DInto(scene2D, transformations2D); flattenMeshHierarchy3DInto(scene3D, transformations3D); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), - "SceneTools::flattenMeshHierarchyInto(): bad output size, expected 5 but got 6\n" - "SceneTools::flattenMeshHierarchyInto(): bad output size, expected 5 but got 4\n"); + "SceneTools::flattenTransformationHierarchyInto(): bad output size, expected 5 but got 6\n" + "SceneTools::flattenTransformationHierarchyInto(): bad output size, expected 5 but got 4\n"); } }}}} diff --git a/src/Magnum/SceneTools/Test/FlattenTransformationHierarchyTest.cpp b/src/Magnum/SceneTools/Test/FlattenTransformationHierarchyTest.cpp new file mode 100644 index 000000000..0567ab2fa --- /dev/null +++ b/src/Magnum/SceneTools/Test/FlattenTransformationHierarchyTest.cpp @@ -0,0 +1,531 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, + 2020, 2021, 2022 Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include + +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" +#include "Magnum/SceneTools/FlattenTransformationHierarchy.h" +#include "Magnum/Trade/SceneData.h" + +namespace Magnum { namespace SceneTools { namespace Test { namespace { + +struct FlattenTransformationHierarchyTest: TestSuite::Tester { + explicit FlattenTransformationHierarchyTest(); + + void test2D(); + void test3D(); + + void fieldNotFound(); + void not2DNot3D(); + void noParentField(); + + void into2D(); + void into3D(); + void intoInvalidSize(); +}; + +using namespace Math::Literals; + +const struct { + const char* name; + Matrix3 globalTransformation2D; + Matrix4 globalTransformation3D; + bool fieldIdInsteadOfName; + std::size_t transformationsToExclude, meshesToExclude; + std::size_t expectedOutputSize; +} TestData[]{ + {"", {}, {}, false, + 2, 0, + 5}, + {"field ID", {}, {}, true, + 2, 0, + 5}, + {"global transformation", + Matrix3::scaling(Vector2{0.5f}), Matrix4::scaling(Vector3{0.5f}), false, + 2, 0, + 5}, + {"global transformation, field ID", + Matrix3::scaling(Vector2{0.5f}), Matrix4::scaling(Vector3{0.5f}), true, + 2, 0, + 5}, + {"transformations not part of the hierarchy", {}, {}, false, + 0, 0, + 5}, + {"empty field", {}, {}, false, + 2, 5, + 0}, +}; + +const struct { + const char* name; + Matrix3 globalTransformation2D; + Matrix4 globalTransformation3D; + bool fieldIdInsteadOfName; + std::size_t expectedOutputSize; +} IntoData[]{ + {"", {}, {}, false, + 5}, + {"field ID", {}, {}, true, + 5}, + {"global transformation", + Matrix3::scaling(Vector2{0.5f}), Matrix4::scaling(Vector3{0.5f}), false, + 5}, + {"global transformation, field ID", + Matrix3::scaling(Vector2{0.5f}), Matrix4::scaling(Vector3{0.5f}), true, + 5}, +}; + +FlattenTransformationHierarchyTest::FlattenTransformationHierarchyTest() { + addInstancedTests({&FlattenTransformationHierarchyTest::test2D, + &FlattenTransformationHierarchyTest::test3D}, + Containers::arraySize(TestData)); + + addTests({&FlattenTransformationHierarchyTest::fieldNotFound, + &FlattenTransformationHierarchyTest::not2DNot3D, + &FlattenTransformationHierarchyTest::noParentField}); + + addInstancedTests({&FlattenTransformationHierarchyTest::into2D, + &FlattenTransformationHierarchyTest::into3D}, + Containers::arraySize(IntoData)); + + addTests({&FlattenTransformationHierarchyTest::intoInvalidSize}); +} + +const struct Scene { + /* Using smaller types to verify we don't have unnecessarily hardcoded + 32-bit types */ + struct Parent { + UnsignedShort object; + Byte parent; + } parents[9]; + + struct Transformation { + UnsignedShort object; + Matrix3 transformation2D; + Matrix4 transformation3D; + } transforms[7]; + + struct Mesh { + UnsignedShort object; + UnsignedShort mesh; + } meshes[5]; +} Data[]{{ + /* + Cases to test: + + - leaf paths with no attachments which don't contribute to the + output in any way + - nodes with transforms but no meshes + - nodes with meshes but no transforms + - nodes with multiple meshes + - nodes with neither transforms nor meshes + - object 4 has a mesh with identity transform (or, rather, no + transformation entry at all) + - objects 2 and 16 have the same mesh attached with the exact + same transform -- this is a nonsense (they would overlap) and + as such isn't deduplicated in any way + - objects 0, 32 and 17 have transformations/meshes, but not part + of the hierarchy; these are cut away from the views in the + first test case to keep it simple + + 1T 4M + / \ | 32M 0MM + 5T 2TM 11 + / \ \ | 32T 17T + 3MM 7T 6 16TM + */ + {{3, 5}, + {11, 4}, + {5, 1}, + {1, -1}, + {7, 5}, + {6, 2}, + {2, 1}, + {4, -1}, + {16, 11}}, + {{2, Matrix3::scaling({3.0f, 5.0f}), + Matrix4::scaling({3.0f, 5.0f, 2.0f})}, + {1, Matrix3::translation({1.0f, -1.5f}), + Matrix4::translation({1.0f, -1.5f, 0.5f})}, + /* Same absolute transform as node 2 */ + {16, Matrix3::translation({1.0f, -1.5f})* + Matrix3::scaling({3.0f, 5.0f}), + Matrix4::translation({1.0f, -1.5f, 0.5f})* + Matrix4::scaling({3.0f, 5.0f, 2.0f})}, + {7, Matrix3::scaling({2.0f, 1.0f}), + Matrix4::scaling({2.0f, 1.0f, 0.5f})}, + {5, Matrix3::rotation(35.0_degf), + Matrix4::rotationZ(35.0_degf)}, + /* These are not part of the hierarchy */ + {32, Matrix3::translation({1.0f, 0.5f}), + Matrix4::translation({1.0f, 0.5f, 2.0f})}, + {17, Matrix3::translation({2.0f, 1.0f}), + Matrix4::translation({2.0f, 1.0f, 4.0f})}, + }, + /* The mesh IDs aren't used for anything, just setting them to something + random (and high) to avoid their misuses as some offsets / IDs */ + {{2, 113}, + {3, 266}, + {4, 525}, + {3, 422}, + {16, 113}} +}}; + +void FlattenTransformationHierarchyTest::test2D() { + auto&& data = TestData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 33, {}, Data, { + /* To verify it doesn't just pick the first field ever */ + Trade::SceneFieldData{Trade::SceneField::Camera, Trade::SceneMappingType::UnsignedShort, nullptr, Trade::SceneFieldType::UnsignedInt, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Parent, + Containers::stridedArrayView(Data->parents) + .slice(&Scene::Parent::object), + Containers::stridedArrayView(Data->parents) + .slice(&Scene::Parent::parent)}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::stridedArrayView(Data->meshes) + .slice(&Scene::Mesh::object) + .exceptSuffix(data.meshesToExclude), + Containers::stridedArrayView(Data->meshes) + .slice(&Scene::Mesh::mesh) + .exceptSuffix(data.meshesToExclude)}, + Trade::SceneFieldData{Trade::SceneField::Transformation, + Containers::stridedArrayView(Data->transforms) + .slice(&Scene::Transformation::object) + .exceptSuffix(data.transformationsToExclude), + Containers::stridedArrayView(Data->transforms) + .slice(&Scene::Transformation::transformation2D) + .exceptSuffix(data.transformationsToExclude)}, + }}; + + Containers::Array out; + /* To test all overloads */ + if(data.globalTransformation2D != Matrix3{}) { + if(data.fieldIdInsteadOfName) + out = flattenTransformationHierarchy2D(scene, 2, data.globalTransformation2D); + else + out = flattenTransformationHierarchy2D(scene, Trade::SceneField::Mesh, data.globalTransformation2D); + } else { + if(data.fieldIdInsteadOfName) + out = flattenTransformationHierarchy2D(scene, 2); + else + out = flattenTransformationHierarchy2D(scene, Trade::SceneField::Mesh); + } + + CORRADE_COMPARE_AS(out, Containers::arrayView({ + data.globalTransformation2D* + Matrix3::translation({1.0f, -1.5f})* + Matrix3::scaling({3.0f, 5.0f}), + data.globalTransformation2D* + Matrix3::translation({1.0f, -1.5f})* + Matrix3::rotation(35.0_degf), + data.globalTransformation2D, + data.globalTransformation2D* + Matrix3::translation({1.0f, -1.5f})* + Matrix3::rotation(35.0_degf), + data.globalTransformation2D* + Matrix3::translation({1.0f, -1.5f})* + Matrix3::scaling({3.0f, 5.0f}) + }).prefix(data.expectedOutputSize), TestSuite::Compare::Container); +} + +void FlattenTransformationHierarchyTest::test3D() { + auto&& data = TestData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 33, {}, Data, { + /* To verify it doesn't just pick the first field ever */ + Trade::SceneFieldData{Trade::SceneField::Camera, Trade::SceneMappingType::UnsignedShort, nullptr, Trade::SceneFieldType::UnsignedInt, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Parent, + Containers::stridedArrayView(Data->parents) + .slice(&Scene::Parent::object), + Containers::stridedArrayView(Data->parents) + .slice(&Scene::Parent::parent)}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::stridedArrayView(Data->meshes) + .slice(&Scene::Mesh::object) + .exceptSuffix(data.meshesToExclude), + Containers::stridedArrayView(Data->meshes) + .slice(&Scene::Mesh::mesh) + .exceptSuffix(data.meshesToExclude)}, + Trade::SceneFieldData{Trade::SceneField::Transformation, + Containers::stridedArrayView(Data->transforms) + .slice(&Scene::Transformation::object) + .exceptSuffix(data.transformationsToExclude), + Containers::stridedArrayView(Data->transforms) + .slice(&Scene::Transformation::transformation3D) + .exceptSuffix(data.transformationsToExclude)}, + }}; + + Containers::Array out; + /* To test all overloads */ + if(data.globalTransformation3D != Matrix4{}) { + if(data.fieldIdInsteadOfName) + out = flattenTransformationHierarchy3D(scene, 2, data.globalTransformation3D); + else + out = flattenTransformationHierarchy3D(scene, Trade::SceneField::Mesh, data.globalTransformation3D); + } else { + if(data.fieldIdInsteadOfName) + out = flattenTransformationHierarchy3D(scene, 2); + else + out = flattenTransformationHierarchy3D(scene, Trade::SceneField::Mesh); + } + + CORRADE_COMPARE_AS(out, Containers::arrayView({ + data.globalTransformation3D* + Matrix4::translation({1.0f, -1.5f, 0.5f})* + Matrix4::scaling({3.0f, 5.0f, 2.0f}), + data.globalTransformation3D* + Matrix4::translation({1.0f, -1.5f, 0.5f})* + Matrix4::rotationZ(35.0_degf), + data.globalTransformation3D, + data.globalTransformation3D* + Matrix4::translation({1.0f, -1.5f, 0.5f})* + Matrix4::rotationZ(35.0_degf), + data.globalTransformation3D* + Matrix4::translation({1.0f, -1.5f, 0.5f})* + Matrix4::scaling({3.0f, 5.0f, 2.0f}) + }).prefix(data.expectedOutputSize), TestSuite::Compare::Container); +} + +void FlattenTransformationHierarchyTest::fieldNotFound() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 0, nullptr, { + Trade::SceneFieldData{Trade::SceneField::Parent, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Int, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix3x3, nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + flattenTransformationHierarchy2D(scene, Trade::SceneField::Mesh); + flattenTransformationHierarchy3D(scene, Trade::SceneField::Mesh); + flattenTransformationHierarchy2D(scene, 2); + flattenTransformationHierarchy3D(scene, 2); + CORRADE_COMPARE(out.str(), + "SceneTools::flattenTransformationHierarchy(): field Trade::SceneField::Mesh not found\n" + "SceneTools::flattenTransformationHierarchy(): field Trade::SceneField::Mesh not found\n" + "SceneTools::flattenTransformationHierarchy(): index 2 out of range for 2 fields\n" + "SceneTools::flattenTransformationHierarchy(): index 2 out of range for 2 fields\n"); +} + +void FlattenTransformationHierarchyTest::not2DNot3D() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 0, nullptr, { + Trade::SceneFieldData{Trade::SceneField::Parent, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Int, nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + flattenTransformationHierarchy2D(scene, Trade::SceneField::Parent); + flattenTransformationHierarchy2D(scene, 0); + flattenTransformationHierarchy3D(scene, Trade::SceneField::Parent); + flattenTransformationHierarchy3D(scene, 0); + CORRADE_COMPARE(out.str(), + "SceneTools::flattenTransformationHierarchy(): the scene is not 2D\n" + "SceneTools::flattenTransformationHierarchy(): the scene is not 2D\n" + "SceneTools::flattenTransformationHierarchy(): the scene is not 3D\n" + "SceneTools::flattenTransformationHierarchy(): the scene is not 3D\n"); +} + +void FlattenTransformationHierarchyTest::noParentField() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedInt, 0, nullptr, { + Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix3x3, nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + flattenTransformationHierarchy2D(scene, Trade::SceneField::Transformation); + flattenTransformationHierarchy2D(scene, 0); + CORRADE_COMPARE(out.str(), + "SceneTools::flattenTransformationHierarchy(): the scene has no hierarchy\n" + "SceneTools::flattenTransformationHierarchy(): the scene has no hierarchy\n"); +} + +void FlattenTransformationHierarchyTest::into2D() { + auto&& data = IntoData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* The *Into() variant is the actual base implementation, so just verify + that the data get correctly propagated through. Everything else is + tested above already. */ + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 33, {}, Data, { + Trade::SceneFieldData{Trade::SceneField::Parent, + Containers::stridedArrayView(Data->parents) + .slice(&Scene::Parent::object), + Containers::stridedArrayView(Data->parents) + .slice(&Scene::Parent::parent)}, + Trade::SceneFieldData{Trade::SceneField::Transformation, + Containers::stridedArrayView(Data->transforms) + .slice(&Scene::Transformation::object), + Containers::stridedArrayView(Data->transforms) + .slice(&Scene::Transformation::transformation2D)}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::stridedArrayView(Data->meshes) + .slice(&Scene::Mesh::object), + Containers::stridedArrayView(Data->meshes) + .slice(&Scene::Mesh::mesh)} + }}; + + Containers::Array out{NoInit, scene.fieldSize(Trade::SceneField::Mesh)}; + /* To test all overloads */ + if(data.globalTransformation2D != Matrix3{}) { + if(data.fieldIdInsteadOfName) + flattenTransformationHierarchy2DInto(scene, 2, out, data.globalTransformation2D); + else + flattenTransformationHierarchy2DInto(scene, Trade::SceneField::Mesh, out, data.globalTransformation2D); + } else { + if(data.fieldIdInsteadOfName) + flattenTransformationHierarchy2DInto(scene, 2, out); + else + flattenTransformationHierarchy2DInto(scene, Trade::SceneField::Mesh, out); + } + + CORRADE_COMPARE_AS(out, Containers::arrayView({ + data.globalTransformation2D* + Matrix3::translation({1.0f, -1.5f})* + Matrix3::scaling({3.0f, 5.0f}), + data.globalTransformation2D* + Matrix3::translation({1.0f, -1.5f})* + Matrix3::rotation(35.0_degf), + data.globalTransformation2D, + data.globalTransformation2D* + Matrix3::translation({1.0f, -1.5f})* + Matrix3::rotation(35.0_degf), + data.globalTransformation2D* + Matrix3::translation({1.0f, -1.5f})* + Matrix3::scaling({3.0f, 5.0f}) + }), TestSuite::Compare::Container); +} + +void FlattenTransformationHierarchyTest::into3D() { + auto&& data = IntoData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* The *Into() variant is the actual base implementation, so just verify + that the data get correctly propagated through. Everything else is + tested above already. */ + + Trade::SceneData scene{Trade::SceneMappingType::UnsignedShort, 33, {}, Data, { + Trade::SceneFieldData{Trade::SceneField::Parent, + Containers::stridedArrayView(Data->parents) + .slice(&Scene::Parent::object), + Containers::stridedArrayView(Data->parents) + .slice(&Scene::Parent::parent)}, + Trade::SceneFieldData{Trade::SceneField::Transformation, + Containers::stridedArrayView(Data->transforms) + .slice(&Scene::Transformation::object), + Containers::stridedArrayView(Data->transforms) + .slice(&Scene::Transformation::transformation3D)}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::stridedArrayView(Data->meshes) + .slice(&Scene::Mesh::object), + Containers::stridedArrayView(Data->meshes) + .slice(&Scene::Mesh::mesh)} + }}; + + Containers::Array out{NoInit, scene.fieldSize(Trade::SceneField::Mesh)}; + /* To test all overloads */ + if(data.globalTransformation3D != Matrix4{}) { + if(data.fieldIdInsteadOfName) + flattenTransformationHierarchy3DInto(scene, 2, out, data.globalTransformation3D); + else + flattenTransformationHierarchy3DInto(scene, Trade::SceneField::Mesh, out, data.globalTransformation3D); + } else { + if(data.fieldIdInsteadOfName) + flattenTransformationHierarchy3DInto(scene, 2, out); + else + flattenTransformationHierarchy3DInto(scene, Trade::SceneField::Mesh, out); + } + + CORRADE_COMPARE_AS(out, Containers::arrayView({ + data.globalTransformation3D* + Matrix4::translation({1.0f, -1.5f, 0.5f})* + Matrix4::scaling({3.0f, 5.0f, 2.0f}), + data.globalTransformation3D* + Matrix4::translation({1.0f, -1.5f, 0.5f})* + Matrix4::rotationZ(35.0_degf), + data.globalTransformation3D, + data.globalTransformation3D* + Matrix4::translation({1.0f, -1.5f, 0.5f})* + Matrix4::rotationZ(35.0_degf), + data.globalTransformation3D* + Matrix4::translation({1.0f, -1.5f, 0.5f})* + Matrix4::scaling({3.0f, 5.0f, 2.0f}) + }), TestSuite::Compare::Container); +} + +void FlattenTransformationHierarchyTest::intoInvalidSize() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct Data { + UnsignedInt mapping; + UnsignedInt mesh; + } data[5]{}; + + Trade::SceneData scene2D{Trade::SceneMappingType::UnsignedInt, 1, {}, data, { + Trade::SceneFieldData{Trade::SceneField::Parent, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Int, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::stridedArrayView(data).slice(&Data::mapping), + Containers::stridedArrayView(data).slice(&Data::mesh)}, + Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix3x3, nullptr} + }}; + Trade::SceneData scene3D{Trade::SceneMappingType::UnsignedInt, 1, {}, data, { + Trade::SceneFieldData{Trade::SceneField::Parent, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Int, nullptr}, + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::stridedArrayView(data).slice(&Data::mapping), + Containers::stridedArrayView(data).slice(&Data::mesh)}, + Trade::SceneFieldData{Trade::SceneField::Transformation, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Matrix4x4, nullptr} + }}; + + Matrix3 transformations2D[6]; + Matrix4 transformations3D[4]; + + std::ostringstream out; + Error redirectError{&out}; + flattenTransformationHierarchy2DInto(scene2D, Trade::SceneField::Mesh, transformations2D); + flattenTransformationHierarchy2DInto(scene2D, 1, transformations2D); + flattenTransformationHierarchy3DInto(scene3D, Trade::SceneField::Mesh, transformations3D); + flattenTransformationHierarchy3DInto(scene3D, 1, transformations3D); + CORRADE_COMPARE(out.str(), + "SceneTools::flattenTransformationHierarchyInto(): bad output size, expected 5 but got 6\n" + "SceneTools::flattenTransformationHierarchyInto(): bad output size, expected 5 but got 6\n" + "SceneTools::flattenTransformationHierarchyInto(): bad output size, expected 5 but got 4\n" + "SceneTools::flattenTransformationHierarchyInto(): bad output size, expected 5 but got 4\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::SceneTools::Test::FlattenTransformationHierarchyTest)