From 785738894972ee90d01eb9239a1e6ef5222e61fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 2 Mar 2020 20:54:37 +0100 Subject: [PATCH] MeshTools: implemented combineFaceAttributes(). --- doc/changelog.dox | 4 +- src/Magnum/MeshTools/Combine.cpp | 158 +++++++++++----- src/Magnum/MeshTools/Combine.h | 21 ++- src/Magnum/MeshTools/Test/CombineTest.cpp | 211 ++++++++++++++++++++++ 4 files changed, 347 insertions(+), 47 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index ebabb2f28..0de8669d0 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -130,7 +130,9 @@ See also: - New @ref MeshTools::removeDuplicatesInPlace() variant that works on discrete data in addition to floating-point - New @ref MeshTools::combineIndexedAttributes() tool for combining - differently indexed attributes into a single index buffer + differently indexed attributes into a single index buffer, and + @ref MeshTools::combineFaceAttributes() for converting per-face attributes + into per-vertex @subsubsection changelog-latest-new-platform Platform libraries diff --git a/src/Magnum/MeshTools/Combine.cpp b/src/Magnum/MeshTools/Combine.cpp index 8ec259769..749295c75 100644 --- a/src/Magnum/MeshTools/Combine.cpp +++ b/src/Magnum/MeshTools/Combine.cpp @@ -30,64 +30,25 @@ #include #include +#include "Magnum/MeshTools/Interleave.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { -Trade::MeshData combineIndexedAttributes(const Containers::ArrayView> data) { - CORRADE_ASSERT(!data.empty(), "MeshTools::combineIndexedAttributes(): no meshes passed", (Trade::MeshData{MeshPrimitive{}, 0})); +namespace { - /* Decide on the output primitive and index count, calculated total - combined index type size and also the count and stride of all - attributes */ - MeshPrimitive primitive; - UnsignedInt indexCount; - std::size_t indexStride = 0; - std::size_t attributeCount = 0; +Trade::MeshData combineIndexedImplementation(const MeshPrimitive primitive, Containers::Array& combinedIndices, const UnsignedInt indexCount, const UnsignedInt indexStride, const Containers::ArrayView> data) { + /* Calculate attribute count and vertex stride */ + UnsignedInt attributeCount = 0; UnsignedInt vertexStride = 0; for(std::size_t i = 0; i != data.size(); ++i) { - CORRADE_ASSERT(data[i]->isIndexed(), - "MeshTools::combineIndexedAttributes(): data" << i << "is not indexed", - (Trade::MeshData{MeshPrimitive{}, 0})); - if(i == 0) { - primitive = data[i]->primitive(); - indexCount = data[i]->indexCount(); - } else { - CORRADE_ASSERT(data[i]->primitive() == primitive, - "MeshTools::combineIndexedAttributes(): data" << i << "is" << data[i]->primitive() << "but expected" << primitive, (Trade::MeshData{MeshPrimitive{}, 0})); - CORRADE_ASSERT(data[i]->indexCount() == indexCount, - "MeshTools::combineIndexedAttributes(): data" << i << "has" << data[i]->indexCount() << "indices but expected" << indexCount, (Trade::MeshData{MeshPrimitive{}, 0})); - } - indexStride += meshIndexTypeSize(data[i]->indexType()); attributeCount += data[i]->attributeCount(); - for(std::size_t j = 0; j != data[i]->attributeCount(); ++j) + for(UnsignedInt j = 0; j != data[i]->attributeCount(); ++j) vertexStride += vertexFormatSize(data[i]->attributeFormat(j)); } - /* Create a combined index array */ - Containers::Array combinedIndices{Containers::NoInit, - indexCount*indexStride}; - { - std::size_t indexOffset = 0; - for(const Trade::MeshData& mesh: data) { - const UnsignedInt indexSize = meshIndexTypeSize(mesh.indexType()); - Containers::StridedArrayView2D dst{combinedIndices, - combinedIndices.data() + indexOffset, - {indexCount, indexSize}, - {std::ptrdiff_t(indexStride), 1}}; - Utility::copy(mesh.indices(), dst); - indexOffset += indexSize; - } - - /* Check we pre-calculated correctly */ - CORRADE_INTERNAL_ASSERT(indexOffset == indexStride); - } - - /** @todo handle alignment in the above somehow (duplicate() will fail when - reading 32-bit values from odd addresses on some platforms) */ - /* Make the combined index array unique */ Containers::Array indexData{indexCount*sizeof(UnsignedInt)}; const auto indexDataI = Containers::arrayCast(indexData); @@ -136,8 +97,115 @@ Trade::MeshData combineIndexedAttributes(const Containers::ArrayView> data) { + CORRADE_ASSERT(!data.empty(), + "MeshTools::combineIndexedAttributes(): no meshes passed", + (Trade::MeshData{MeshPrimitive{}, 0})); + + /* Decide on the output primitive and index count, calculated total + combined index type size */ + MeshPrimitive primitive; + UnsignedInt indexCount; + UnsignedInt indexStride = 0; + for(std::size_t i = 0; i != data.size(); ++i) { + CORRADE_ASSERT(data[i]->isIndexed(), + "MeshTools::combineIndexedAttributes(): data" << i << "is not indexed", + (Trade::MeshData{MeshPrimitive{}, 0})); + if(i == 0) { + primitive = data[i]->primitive(); + indexCount = data[i]->indexCount(); + } else { + CORRADE_ASSERT(data[i]->primitive() == primitive, + "MeshTools::combineIndexedAttributes(): data" << i << "is" << data[i]->primitive() << "but expected" << primitive, (Trade::MeshData{MeshPrimitive{}, 0})); + CORRADE_ASSERT(data[i]->indexCount() == indexCount, + "MeshTools::combineIndexedAttributes(): data" << i << "has" << data[i]->indexCount() << "indices but expected" << indexCount, (Trade::MeshData{MeshPrimitive{}, 0})); + } + indexStride += meshIndexTypeSize(data[i]->indexType()); + } + + /** @todo handle alignment in the above somehow (duplicate() will fail when + reading 32-bit values from odd addresses on some platforms) */ + + /* Create a combined index array */ + Containers::Array combinedIndices{Containers::NoInit, + indexCount*indexStride}; + { + std::size_t indexOffset = 0; + for(const Trade::MeshData& mesh: data) { + const UnsignedInt indexSize = meshIndexTypeSize(mesh.indexType()); + Containers::StridedArrayView2D dst{combinedIndices, + combinedIndices.data() + indexOffset, + {indexCount, indexSize}, + {std::ptrdiff_t(indexStride), 1}}; + Utility::copy(mesh.indices(), dst); + indexOffset += indexSize; + } + + /* Check we pre-calculated correctly */ + CORRADE_INTERNAL_ASSERT(indexOffset == indexStride); + } + + return combineIndexedImplementation(primitive, combinedIndices, indexCount, indexStride, data); +} + Trade::MeshData combineIndexedAttributes(std::initializer_list> data) { return combineIndexedAttributes(Containers::arrayView(data)); } +Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, const Trade::MeshData& faceAttributes) { + CORRADE_ASSERT(mesh.isIndexed(), + "MeshTools::combineFaceAttributes(): vertex mesh is not indexed", + (Trade::MeshData{MeshPrimitive{}, 0})); + CORRADE_ASSERT(mesh.primitive() == MeshPrimitive::Triangles && faceAttributes.primitive() == MeshPrimitive::Faces, + "MeshTools::combineFaceAttributes(): expected a MeshPrimitive::Triangles mesh and a MeshPrimitive::Faces mesh but got" << mesh.primitive() << "and" << faceAttributes.primitive(), + (Trade::MeshData{MeshPrimitive{}, 0})); + const UnsignedInt meshIndexCount = mesh.indexCount(); + const UnsignedInt faceIndexCount = faceAttributes.isIndexed() ? + faceAttributes.indexCount() : faceAttributes.vertexCount(); + CORRADE_ASSERT(faceIndexCount*3 == meshIndexCount, + "MeshTools::combineFaceAttributes(): expected" << meshIndexCount/3 << "face entries for" << meshIndexCount << "indices but got" << faceIndexCount, + (Trade::MeshData{MeshPrimitive{}, 0})); + + /* Make a combined index array. First copy the mesh indices as-is. */ + const UnsignedInt meshIndexSize = meshIndexTypeSize(mesh.indexType()); + const UnsignedInt faceIndexSize = faceAttributes.isIndexed() ? + meshIndexTypeSize(faceAttributes.indexType()) : 4; + const UnsignedInt indexStride = meshIndexSize + faceIndexSize; + Containers::Array combinedIndices{meshIndexCount*indexStride}; + Utility::copy(mesh.indices(), + Containers::StridedArrayView2D{combinedIndices, {meshIndexCount, meshIndexSize}, {std::ptrdiff_t(indexStride), 1}}); + + /* Then, if the face attributes are not indexed, remove duplicates and put + the resulting indices into the combined array above. For simplicity + assume face data are interleaved. */ + Containers::StridedArrayView3D combinedFaceIndices{combinedIndices, + combinedIndices.data() + meshIndexSize, + {3, faceIndexCount, faceIndexSize}, + {std::ptrdiff_t(indexStride), 3*std::ptrdiff_t(indexStride), 1}}; + if(!faceAttributes.isIndexed()) { + /** @todo this could go into a dedicated removeDuplicates(MeshData) + feature at some point, which would handle everything including + in-place / non-in-place, indexed / non-indexed etc. */ + CORRADE_ASSERT(isInterleaved(faceAttributes), + "MeshTools::combineFaceAttributes(): face attributes are not interleaved", + (Trade::MeshData{MeshPrimitive{}, 0})); + removeDuplicatesInto(interleavedData(faceAttributes), Containers::arrayCast<1, UnsignedInt>(combinedFaceIndices[0])); + + /* Otherwise, simply copy the indices directly */ + } else Utility::copy(faceAttributes.indices(), combinedFaceIndices[0]); + + /* Duplicate the vertex index to the other two vertices of each triangle */ + Utility::copy(combinedFaceIndices[0], combinedFaceIndices[1]); + Utility::copy(combinedFaceIndices[0], combinedFaceIndices[2]); + + /* Then combine the two into a single buffer */ + return combineIndexedImplementation(mesh.primitive(), combinedIndices, + meshIndexCount, indexStride, + Containers::arrayView>({ + mesh, faceAttributes + })); +} + }} diff --git a/src/Magnum/MeshTools/Combine.h b/src/Magnum/MeshTools/Combine.h index a2c544171..45f68cf36 100644 --- a/src/Magnum/MeshTools/Combine.h +++ b/src/Magnum/MeshTools/Combine.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::combineIndexedAttributes() + * @brief Function @ref Magnum::MeshTools::combineIndexedAttributes(), @ref Magnum::MeshTools::combineFaceAttributes() * @m_since_latest */ @@ -89,6 +89,25 @@ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(const Container */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(std::initializer_list> data); +/** +@brief Combine per-face attributes into an existing mesh +@m_since_latest + +The resulting mesh will have all per-face attributes turned into per-vertex +attributes, leaving only unique combinations and adjusting the index buffer +accordingly. The resulting mesh has the same amount of indices, but likely +more vertices. + +Expects that @p mesh is indexed @ref MeshPrimitive::Triangles and +@p faceAttributes is indexed @ref MeshPrimitive::Faces, index count of the +latter corresponding to index count of the former. If @p faceAttributes is +indexed, it's assumed to have the data unique; if it's not indexed, it's first +made unique using @ref removeDuplicates() and in that case it's expected to +be interleaved. +@see @ref isInterleaved() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, const Trade::MeshData& faceAttributes); + }} #endif diff --git a/src/Magnum/MeshTools/Test/CombineTest.cpp b/src/Magnum/MeshTools/Test/CombineTest.cpp index 4be6f8967..3d6b8f0e6 100644 --- a/src/Magnum/MeshTools/Test/CombineTest.cpp +++ b/src/Magnum/MeshTools/Test/CombineTest.cpp @@ -29,6 +29,7 @@ #include #include +#include "Magnum/Math/Color.h" #include "Magnum/MeshTools/Combine.h" #include "Magnum/Trade/MeshData.h" @@ -44,6 +45,20 @@ struct CombineTest: TestSuite::Tester { void combineIndexedAttributesNotIndexed(); void combineIndexedAttributesDifferentPrimitive(); void combineIndexedAttributesDifferentIndexCount(); + + void combineFaceAttributes(); + void combineFaceAttributesMeshNotIndexed(); + void combineFaceAttributesUnexpectedPrimitive(); + void combineFaceAttributesUnexpectedFaceCount(); + void combineFaceAttributesFacesNotInterleaved(); +}; + +constexpr struct { + const char* name; + bool indexed; +} CombineFaceAttributesData[] { + {"", false}, + {"indexed faces", true} }; CombineTest::CombineTest() { @@ -54,6 +69,14 @@ CombineTest::CombineTest() { &CombineTest::combineIndexedAttributesNotIndexed, &CombineTest::combineIndexedAttributesDifferentPrimitive, &CombineTest::combineIndexedAttributesDifferentIndexCount}); + + addInstancedTests({&CombineTest::combineFaceAttributes}, + Containers::arraySize(CombineFaceAttributesData)); + + addTests({&CombineTest::combineFaceAttributesMeshNotIndexed, + &CombineTest::combineFaceAttributesUnexpectedPrimitive, + &CombineTest::combineFaceAttributesUnexpectedFaceCount, + &CombineTest::combineFaceAttributesFacesNotInterleaved}); } void CombineTest::combineIndexedAttributes() { @@ -174,6 +197,194 @@ void CombineTest::combineIndexedAttributesDifferentIndexCount() { CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): data 2 has 4 indices but expected 5\n"); } +void CombineTest::combineFaceAttributes() { + auto&& data = CombineFaceAttributesData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + using namespace Math::Literals; + + /* + 9 ------- 8 + 5 ------- 4 \ / 6 + \ / \ \ C / / \ + \ C / \ \ / / \ + \ / B \ \ / / B \ + \ / \ 7 / \ + 1 ------- 3 ==> 3 ------- 5 + / \ / 2 \ / + / \ B / / \ \ B / + / A \ / / \ \ / + / \ / / A \ \ / + 0 ------- 2 / \ 4 + 0 ------- 1 + */ + const UnsignedShort indices[]{ + 0, 2, 1, + 1, 2, 3, + 1, 3, 4, + 1, 4, 5 + }; + const Vector2 positions[] { + {0.0f, 0.0f}, + {0.5f, 1.0f}, + {1.0f, 0.0f}, + {1.5f, 1.0f}, + {1.0f, 2.0f}, + {0.0f, 2.0f} + }; + + const struct FaceData { + Color3 color; + Byte id; + } faceData[] { + {0xaaaaaa_rgbf, 'A'}, + {0xbbbbbb_rgbf, 'B'}, + {0xbbbbbb_rgbf, 'B'}, + {0xcccccc_rgbf, 'C'} + }; + + const UnsignedByte faceIndices[] { 0, 1, 1, 2 }; + const FaceData faceDataIndexed[] { + {0xaaaaaa_rgbf, 'A'}, + {0xbbbbbb_rgbf, 'B'}, + {0xcccccc_rgbf, 'C'} + }; + + const Trade::MeshData mesh{MeshPrimitive::Triangles, + {}, indices, Trade::MeshIndexData{indices}, + {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positions)} + }}; + const Trade::MeshData faceAttributes{MeshPrimitive::Faces, + {}, faceData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::StridedArrayView1D{faceData, + &faceData[0].color, 4, sizeof(FaceData)}}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(25), + Containers::StridedArrayView1D{faceData, + &faceData[0].id, 4, sizeof(FaceData)}}, + }}; + const Trade::MeshData faceAttributesIndexed{MeshPrimitive::Faces, + {}, faceIndices, Trade::MeshIndexData{faceIndices}, + {}, faceDataIndexed, { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::StridedArrayView1D{faceDataIndexed, + &faceDataIndexed[0].color, 3, sizeof(FaceData)}}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(25), + Containers::StridedArrayView1D{faceDataIndexed, + &faceDataIndexed[0].id, 3, sizeof(FaceData)}}, + }}; + + Trade::MeshData combined = data.indexed ? + MeshTools::combineFaceAttributes(mesh, faceAttributesIndexed) : + MeshTools::combineFaceAttributes(mesh, faceAttributes); + CORRADE_COMPARE(combined.attributeCount(), 3); + CORRADE_COMPARE(combined.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(combined.indices(), + Containers::arrayView({ + 0, 1, 2, + 3, 4, 5, + 3, 5, 6, + 7, 8, 9 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(combined.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {0.0f, 0.0f}, + {1.0f, 0.0f}, + {0.5f, 1.0f}, + {0.5f, 1.0f}, + {1.0f, 0.0f}, + {1.5f, 1.0f}, + {1.0f, 2.0f}, + {0.5f, 1.0f}, + {1.0f, 2.0f}, + {0.0f, 2.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(combined.attribute(Trade::MeshAttribute::Color), + Containers::arrayView({ + 0xaaaaaa_rgbf, 0xaaaaaa_rgbf, 0xaaaaaa_rgbf, + 0xbbbbbb_rgbf, 0xbbbbbb_rgbf, 0xbbbbbb_rgbf, 0xbbbbbb_rgbf, + 0xcccccc_rgbf, 0xcccccc_rgbf, 0xcccccc_rgbf + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(combined.attribute(Trade::meshAttributeCustom(25)), + Containers::arrayView({ + 'A', 'A', 'A', + 'B', 'B', 'B', 'B', + 'C', 'C', 'C' + }), TestSuite::Compare::Container); +} + +void CombineTest::combineFaceAttributesMeshNotIndexed() { + const Trade::MeshData mesh{MeshPrimitive::Triangles, 3}; + const Trade::MeshData faceAttributes{MeshPrimitive::Faces, 0}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineFaceAttributes(mesh, faceAttributes); + CORRADE_COMPARE(out.str(), + "MeshTools::combineFaceAttributes(): vertex mesh is not indexed\n"); +} + +void CombineTest::combineFaceAttributesUnexpectedPrimitive() { + const UnsignedInt indices[] { 0, 0, 0 }; + const Trade::MeshData a{MeshPrimitive::Triangles, + {}, indices, Trade::MeshIndexData{indices}, 1}; + const Trade::MeshData b{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, 1}; + const Trade::MeshData faceA{MeshPrimitive::Instances, 0}; + const Trade::MeshData faceB{MeshPrimitive::Faces, 0}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineFaceAttributes(a, faceA); + MeshTools::combineFaceAttributes(b, faceB); + CORRADE_COMPARE(out.str(), + "MeshTools::combineFaceAttributes(): expected a MeshPrimitive::Triangles mesh and a MeshPrimitive::Faces mesh but got MeshPrimitive::Triangles and MeshPrimitive::Instances\n" + "MeshTools::combineFaceAttributes(): expected a MeshPrimitive::Triangles mesh and a MeshPrimitive::Faces mesh but got MeshPrimitive::Lines and MeshPrimitive::Faces\n"); +} + +void CombineTest::combineFaceAttributesUnexpectedFaceCount() { + const UnsignedInt indices[] { 0, 0, 0 }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, + {}, indices, Trade::MeshIndexData{indices}, 1}; + const Trade::MeshData faceAttributes{MeshPrimitive::Faces, 2}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineFaceAttributes(mesh, faceAttributes); + CORRADE_COMPARE(out.str(), + "MeshTools::combineFaceAttributes(): expected 1 face entries for 3 indices but got 2\n"); +} + +void CombineTest::combineFaceAttributesFacesNotInterleaved() { + using namespace Math::Literals; + + const UnsignedInt indices[] { 0, 0, 0, 0, 0, 0 }; + const Trade::MeshData mesh{MeshPrimitive::Triangles, + {}, indices, Trade::MeshIndexData{indices}, 1}; + const struct { + Color3 color[2]; + Byte id[2]; + } faceData[]{{ + {0xaaaaaa_rgbf, 0xbbbbbb_rgbf}, + {'A', 'B'} + }}; + const Trade::MeshData faceAttributes{MeshPrimitive::Faces, + {}, faceData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::arrayView(faceData[0].color)}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(25), + Containers::arrayView(faceData[0].id)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineFaceAttributes(mesh, faceAttributes); + CORRADE_COMPARE(out.str(), + "MeshTools::combineFaceAttributes(): face attributes are not interleaved\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::CombineTest)