From ba8a3e6f3f65f452b22e46a46852a8cfd765c73b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 8 Nov 2022 22:02:27 +0100 Subject: [PATCH] SceneTools: add flattenMeshHierarchyInto(). Now that the data order is pinned down, it's possible to hammer a more efficient underlying API from out under the convenience utility. --- doc/snippets/MagnumSceneTools.cpp | 39 +++++ .../SceneTools/FlattenMeshHierarchy.cpp | 58 +++++-- src/Magnum/SceneTools/FlattenMeshHierarchy.h | 60 ++++++- .../Test/FlattenMeshHierarchyTest.cpp | 158 ++++++++++++++++++ 4 files changed, 300 insertions(+), 15 deletions(-) diff --git a/doc/snippets/MagnumSceneTools.cpp b/doc/snippets/MagnumSceneTools.cpp index 2d5a3190c..49eed2a5f 100644 --- a/doc/snippets/MagnumSceneTools.cpp +++ b/doc/snippets/MagnumSceneTools.cpp @@ -71,6 +71,45 @@ for(const Containers::Triple& meshTransformation: /* [flattenMeshHierarchy3D-transformations] */ } +{ +/* [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] */ +} + + { /* [orderClusterParents-transformations] */ Trade::SceneData scene = DOXYGEN_ELLIPSIS(Trade::SceneData{{}, 0, nullptr, {}}); diff --git a/src/Magnum/SceneTools/FlattenMeshHierarchy.cpp b/src/Magnum/SceneTools/FlattenMeshHierarchy.cpp index 9c6edd490..b54856fd6 100644 --- a/src/Magnum/SceneTools/FlattenMeshHierarchy.cpp +++ b/src/Magnum/SceneTools/FlattenMeshHierarchy.cpp @@ -60,18 +60,20 @@ template<> struct SceneDataDimensionTraits<3> { } }; -template -Containers::Array>> flattenMeshHierarchyImplementation(const Trade::SceneData& scene, const MatrixTypeFor& 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", {}); + "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", {}); + "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. */ - if(!scene.hasField(Trade::SceneField::Mesh)) return {}; + if(!meshFieldId) return; /* Allocate a single storage for all temporary data */ Containers::ArrayView> orderedClusteredParents; @@ -113,16 +115,30 @@ Containers::Array>> out{NoInit, scene.fieldSize(Trade::SceneField::Mesh)}; - const auto matrices = stridedArrayView(out).slice(&decltype(out)::Type::third); - const auto mapping = Containers::arrayCast(matrices); - scene.meshesMaterialsInto(mapping, - stridedArrayView(out).slice(&decltype(out)::Type::first), - stridedArrayView(out).slice(&decltype(out)::Type::second)); - for(std::size_t i = 0; i != out.size(); ++i) { + 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()); - matrices[i] = absoluteTransformations[mapping[i] + 1]; + outputTransformations[i] = absoluteTransformations[mapping[i] + 1]; } +} + +template Containers::Array>> flattenMeshHierarchyImplementation(const Trade::SceneData& scene, const MatrixTypeFor& globalTransformation) { + const Containers::Optional meshFieldId = scene.findFieldId(Trade::SceneField::Mesh); + + /* 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, + 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, + stridedArrayView(out).slice(&decltype(out)::Type::first), + stridedArrayView(out).slice(&decltype(out)::Type::second)); return out; } @@ -137,6 +153,14 @@ Containers::Array> flattenMeshHier return flattenMeshHierarchyImplementation<2>(scene, {}); } +void flattenMeshHierarchy2DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations, const Matrix3& globalTransformation) { + return flattenMeshHierarchyIntoImplementation<2>(scene, transformations, globalTransformation); +} + +void flattenMeshHierarchy2DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations) { + return flattenMeshHierarchyIntoImplementation<2>(scene, transformations, {}); +} + Containers::Array> flattenMeshHierarchy3D(const Trade::SceneData& scene, const Matrix4& globalTransformation) { return flattenMeshHierarchyImplementation<3>(scene, globalTransformation); } @@ -145,4 +169,12 @@ Containers::Array> flattenMeshHier return flattenMeshHierarchyImplementation<3>(scene, {}); } +void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations, const Matrix4& globalTransformation) { + return flattenMeshHierarchyIntoImplementation<3>(scene, transformations, globalTransformation); +} + +void flattenMeshHierarchy3DInto(const Trade::SceneData& scene, const Containers::StridedArrayView1D& transformations) { + return flattenMeshHierarchyIntoImplementation<3>(scene, transformations, {}); +} + }} diff --git a/src/Magnum/SceneTools/FlattenMeshHierarchy.h b/src/Magnum/SceneTools/FlattenMeshHierarchy.h index 363150f5d..e8c5c1ac1 100644 --- a/src/Magnum/SceneTools/FlattenMeshHierarchy.h +++ b/src/Magnum/SceneTools/FlattenMeshHierarchy.h @@ -63,7 +63,8 @@ an unspecified value. @experimental -@see @ref Trade::SceneData::hasField(), @ref Trade::SceneData::is2D(), +@see @ref flattenMeshHierarchy2DInto(), @ref flattenMeshHierarchy3D(), + @ref Trade::SceneData::hasField(), @ref Trade::SceneData::is2D(), @ref MeshTools::concatenate() */ #ifdef DOXYGEN_GENERATING_OUTPUT @@ -74,6 +75,33 @@ MAGNUM_SCENETOOLS_EXPORT Containers::Array> flattenMeshHierarchy2D(const Trade::SceneData& scene); #endif +/** +@brief Flatten a 2D mesh hierarchy into an existing array +@param[in] scene Input scene +@param[out] transformations Where to put the calculated transformations +@param[in] globalTransformation Global transformation to prepend +@m_since_latest + +A variant of @ref flattenMeshHierarchy2D() that fills existing memory instead +of allocating a new array. The @p transformations array is expected to have the +same size as the @ref Trade::SceneField::Mesh field. Corresponding mesh and +material IDs as well as object ID mapping can be retrieved directly with +@ref Trade::SceneData::meshesMaterialsInto(), as the returned transformations +are matching their order. The snippet below shows retrieving absolute +transformations together with object and mesh IDs, but ignoring materials: + +@snippet MagnumSceneTools.cpp flattenMeshHierarchy2DInto + +@experimental +*/ +#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); +#endif + /** @brief Flatten a 3D mesh hierarchy @m_since_latest @@ -101,7 +129,8 @@ an unspecified value. @experimental -@see @ref Trade::SceneData::hasField(), @ref Trade::SceneData::is3D(), +@see @ref flattenMeshHierarchy3DInto(), @ref flattenMeshHierarchy2D(), + @ref Trade::SceneData::hasField(), @ref Trade::SceneData::is3D(), @ref MeshTools::concatenate() */ #ifdef DOXYGEN_GENERATING_OUTPUT @@ -112,6 +141,33 @@ MAGNUM_SCENETOOLS_EXPORT Containers::Array> flattenMeshHierarchy3D(const Trade::SceneData& scene); #endif +/** +@brief Flatten a 3D mesh hierarchy into an existing array +@param[in] scene Input scene +@param[out] transformations Where to put the calculated transformations +@param[in] globalTransformation Global transformation to prepend +@m_since_latest + +A variant of @ref flattenMeshHierarchy3D() that fills existing memory instead +of allocating a new array. The @p transformations array is expected to have the +same size as the @ref Trade::SceneField::Mesh field. Corresponding mesh and +material IDs as well as object ID mapping can be retrieved directly with +@ref Trade::SceneData::meshesMaterialsInto(), as the returned transformations +are matching their order. The snippet below shows retrieving absolute +transformations together with object and mesh IDs, but ignoring materials: + +@snippet MagnumSceneTools.cpp flattenMeshHierarchy3DInto + +@experimental +*/ +#ifdef DOXYGEN_GENERATING_OUTPUT +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); +#endif + }} #endif diff --git a/src/Magnum/SceneTools/Test/FlattenMeshHierarchyTest.cpp b/src/Magnum/SceneTools/Test/FlattenMeshHierarchyTest.cpp index 47e6559cd..95781fcd9 100644 --- a/src/Magnum/SceneTools/Test/FlattenMeshHierarchyTest.cpp +++ b/src/Magnum/SceneTools/Test/FlattenMeshHierarchyTest.cpp @@ -44,6 +44,10 @@ struct FlattenMeshHierarchyTest: TestSuite::Tester { void not2DNot3D(); void noParentField(); void noMeshField(); + + void into2D(); + void into3D(); + void intoInvalidSize(); }; using namespace Math::Literals; @@ -70,6 +74,19 @@ const struct { 0}, }; +const struct { + const char* name; + Matrix3 globalTransformation2D; + Matrix4 globalTransformation3D; + std::size_t expectedOutputSize; +} IntoData[]{ + {"", {}, {}, + 5}, + {"global transformation", + Matrix3::scaling(Vector2{0.5f}), Matrix4::scaling(Vector3{0.5f}), + 5}, +}; + FlattenMeshHierarchyTest::FlattenMeshHierarchyTest() { addInstancedTests({&FlattenMeshHierarchyTest::test2D, &FlattenMeshHierarchyTest::test3D}, @@ -78,6 +95,12 @@ FlattenMeshHierarchyTest::FlattenMeshHierarchyTest() { addTests({&FlattenMeshHierarchyTest::not2DNot3D, &FlattenMeshHierarchyTest::noParentField, &FlattenMeshHierarchyTest::noMeshField}); + + addInstancedTests({&FlattenMeshHierarchyTest::into2D, + &FlattenMeshHierarchyTest::into3D}, + Containers::arraySize(IntoData)); + + addTests({&FlattenMeshHierarchyTest::intoInvalidSize}); } const struct Scene { @@ -317,6 +340,141 @@ void FlattenMeshHierarchyTest::noMeshField() { TestSuite::Compare::Container); } +void FlattenMeshHierarchyTest::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 the parameter-less overload also */ + if(data.globalTransformation2D != Matrix3{}) + flattenMeshHierarchy2DInto(scene, out, data.globalTransformation2D); + else + flattenMeshHierarchy2DInto(scene, 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 FlattenMeshHierarchyTest::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 the parameter-less overload also */ + if(data.globalTransformation3D != Matrix4{}) + flattenMeshHierarchy3DInto(scene, out, data.globalTransformation3D); + else + flattenMeshHierarchy3DInto(scene, 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 FlattenMeshHierarchyTest::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::Mesh, + Containers::stridedArrayView(data).slice(&Data::mapping), + Containers::stridedArrayView(data).slice(&Data::mesh)}, + 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} + }}; + Trade::SceneData scene3D{Trade::SceneMappingType::UnsignedInt, 1, {}, data, { + Trade::SceneFieldData{Trade::SceneField::Mesh, + Containers::stridedArrayView(data).slice(&Data::mapping), + Containers::stridedArrayView(data).slice(&Data::mesh)}, + Trade::SceneFieldData{Trade::SceneField::Parent, Trade::SceneMappingType::UnsignedInt, nullptr, Trade::SceneFieldType::Int, nullptr}, + 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}; + flattenMeshHierarchy2DInto(scene2D, transformations2D); + flattenMeshHierarchy3DInto(scene3D, transformations3D); + 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"); +} + }}}} CORRADE_TEST_MAIN(Magnum::SceneTools::Test::FlattenMeshHierarchyTest)