diff --git a/doc/changelog.dox b/doc/changelog.dox index 44acd36f8..c1150095e 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -114,7 +114,8 @@ See also: - Added @ref MeshTools::interleavedLayout() for convenient creation of an interleaved mesh layout using the new @ref Trade::MeshData API - Added @ref MeshTools::interleave(const Trade::MeshData&, Containers::ArrayView) - that works directly on the new @ref Trade::MeshData API + and @ref MeshTools::duplicate(const Trade::MeshData&, Containers::ArrayView) + that work directly on the new @ref Trade::MeshData API - Added @ref MeshTools::subdivideInPlace() for allocation-less mesh subdivision - New @ref MeshTools::removeDuplicatesInPlace() variant that works on diff --git a/src/Magnum/MeshTools/Duplicate.cpp b/src/Magnum/MeshTools/Duplicate.cpp index a640755be..4d6b2cb45 100644 --- a/src/Magnum/MeshTools/Duplicate.cpp +++ b/src/Magnum/MeshTools/Duplicate.cpp @@ -26,6 +26,10 @@ #include "Duplicate.h" #include +#include + +#include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { @@ -73,4 +77,41 @@ void duplicateInto(const Containers::StridedArrayView2D& indices, co } } +Trade::MeshData duplicate(const Trade::MeshData& data, const Containers::ArrayView extra) { + CORRADE_ASSERT(data.isIndexed(), "MeshTools::duplicate(): mesh data not indexed", (Trade::MeshData{MeshPrimitive::Triangles, 0})); + + /* Calculate the layout */ + Trade::MeshData layout = interleavedLayout(data, data.indexCount(), extra); + + /* Copy existing attributes to new locations */ + for(UnsignedInt i = 0; i != data.attributeCount(); ++i) + duplicateInto(data.indices(), data.attribute(i), layout.mutableAttribute(i)); + + /* Mix in the extra attributes */ + UnsignedInt attributeIndex = data.attributeCount(); + for(UnsignedInt i = 0; i != extra.size(); ++i) { + /* Padding, ignore */ + if(extra[i].format() == VertexFormat{}) continue; + + /* Copy the attribute in, if it is non-empty, otherwise keep the + memory uninitialized */ + if(extra[i].data()) { + CORRADE_ASSERT(extra[i].data().size() == data.vertexCount(), + "MeshTools::duplicate(): extra attribute" << i << "expected to have" << data.vertexCount() << "items but got" << extra[i].data().size(), + (Trade::MeshData{MeshPrimitive::Triangles, 0})); + const Containers::StridedArrayView2D attributeData = + Containers::arrayCast<2, const char>(extra[i].data(), vertexFormatSize(extra[i].format())); + duplicateInto(data.indices(), attributeData, layout.mutableAttribute(attributeIndex)); + } + + ++attributeIndex; + } + + return layout; +} + +Trade::MeshData duplicate(const Trade::MeshData& data, std::initializer_list extra) { + return duplicate(data, Containers::arrayView(extra)); +} + }} diff --git a/src/Magnum/MeshTools/Duplicate.h b/src/Magnum/MeshTools/Duplicate.h index 8b58cd13d..a1bf32b8a 100644 --- a/src/Magnum/MeshTools/Duplicate.h +++ b/src/Magnum/MeshTools/Duplicate.h @@ -36,6 +36,7 @@ #include "Magnum/Magnum.h" #include "Magnum/MeshTools/visibility.h" +#include "Magnum/Trade/Trade.h" namespace Magnum { namespace MeshTools { @@ -126,6 +127,31 @@ etc. overloads. */ MAGNUM_MESHTOOLS_EXPORT void duplicateInto(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out); +/** +@brief Duplicate indexed mesh data +@m_since_latest + +Returns a copy of @p data that's not indexed and has all attributes interleaved +and duplicated according to @p data's index buffer. The @p extra attributes, if +any, are duplicated and interleaved together with existing attributes (or, in +case the attribute view is empty, only the corresponding space for given +attribute type is reserved, with memory left uninitialized). The data layouting +is done by @ref interleavedLayout(), see its documentation for detailed +behavior description. + +Expects that @p data is indexed and each attribute in @p extra has either the +same amount of elements as @p data vertex count (*not* index count) or has +none. +@see @ref Trade::MeshData::attributeData() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData duplicate(const Trade::MeshData& data, Containers::ArrayView extra = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData duplicate(const Trade::MeshData& data, std::initializer_list extra); + template inline void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& data, const Containers::StridedArrayView1D& out) { duplicateInto(indices, Containers::arrayCast<2, const char>(data), Containers::arrayCast<2, char>(out)); } diff --git a/src/Magnum/MeshTools/Test/DuplicateTest.cpp b/src/Magnum/MeshTools/Test/DuplicateTest.cpp index a715b4239..d93612567 100644 --- a/src/Magnum/MeshTools/Test/DuplicateTest.cpp +++ b/src/Magnum/MeshTools/Test/DuplicateTest.cpp @@ -27,10 +27,13 @@ #include #include #include +#include #include "Magnum/Magnum.h" -#include "Magnum/Math/TypeTraits.h" +#include "Magnum/Math/Vector3.h" #include "Magnum/MeshTools/Duplicate.h" +#include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { @@ -51,6 +54,13 @@ struct DuplicateTest: TestSuite::Tester { template void duplicateErasedIndicesIntoErased(); void duplicateErasedIndicesIntoErasedNonContiguous(); void duplicateErasedIndicesIntoErasedWrongTypeSize(); + + template void duplicateMeshData(); + void duplicateMeshDataNotIndexed(); + void duplicateMeshDataExtra(); + void duplicateMeshDataExtraEmpty(); + void duplicateMeshDataExtraWrongCount(); + void duplicateMeshDataNoAttributes(); }; DuplicateTest::DuplicateTest() { @@ -71,7 +81,16 @@ DuplicateTest::DuplicateTest() { &DuplicateTest::duplicateErasedIndicesIntoErased, &DuplicateTest::duplicateErasedIndicesIntoErased, &DuplicateTest::duplicateErasedIndicesIntoErasedNonContiguous, - &DuplicateTest::duplicateErasedIndicesIntoErasedWrongTypeSize}); + &DuplicateTest::duplicateErasedIndicesIntoErasedWrongTypeSize, + + &DuplicateTest::duplicateMeshData, + &DuplicateTest::duplicateMeshData, + &DuplicateTest::duplicateMeshData, + &DuplicateTest::duplicateMeshDataNotIndexed, + &DuplicateTest::duplicateMeshDataExtra, + &DuplicateTest::duplicateMeshDataExtraEmpty, + &DuplicateTest::duplicateMeshDataExtraWrongCount, + &DuplicateTest::duplicateMeshDataNoAttributes}); } void DuplicateTest::duplicate() { @@ -220,6 +239,139 @@ void DuplicateTest::duplicateErasedIndicesIntoErasedNonContiguous() { "MeshTools::duplicateInto(): second index view dimension is not contiguous\n"); } +template void DuplicateTest::duplicateMeshData() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[]{0, 1, 2, 2, 1, 0}; + struct { + Vector2 positions[3]; + Vector3 normals[3]; + } vertexData{ + {{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}, + {Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()} + }; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, indices, Trade::MeshIndexData{indices}, + {}, Containers::arrayView(&vertexData, 1), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(vertexData.positions)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(vertexData.normals)} + }}; + + Trade::MeshData duplicated = MeshTools::duplicate(data); + CORRADE_VERIFY(MeshTools::isInterleaved(duplicated)); + CORRADE_COMPARE(duplicated.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!duplicated.isIndexed()); + CORRADE_COMPARE(duplicated.vertexCount(), 6); + CORRADE_COMPARE(duplicated.attributeCount(), 2); + CORRADE_COMPARE_AS(duplicated.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}, + {1.0f, -0.5f}, {0.87f, 1.1f}, {1.3f, 0.3f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(duplicated.attribute(Trade::MeshAttribute::Normal), + Containers::arrayView({ + Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis(), + Vector3::zAxis(), Vector3::yAxis(), Vector3::xAxis() + }), TestSuite::Compare::Container); +} + +void DuplicateTest::duplicateMeshDataNotIndexed() { + std::ostringstream out; + Error redirectError{&out}; + MeshTools::duplicate(Trade::MeshData{MeshPrimitive::Points, 0}); + CORRADE_COMPARE(out.str(), "MeshTools::duplicate(): mesh data not indexed\n"); +} + +void DuplicateTest::duplicateMeshDataExtra() { + UnsignedByte indices[]{0, 1, 2, 2, 1, 0}; + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, + {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + const Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}; + Trade::MeshData duplicated = MeshTools::duplicate(data, { + Trade::MeshAttributeData{4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(normals)} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(duplicated)); + CORRADE_COMPARE(duplicated.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(!duplicated.isIndexed()); + CORRADE_COMPARE(duplicated.vertexCount(), 6); + CORRADE_COMPARE(duplicated.attributeCount(), 2); + CORRADE_COMPARE_AS(duplicated.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}, + {1.0f, -0.5f}, {0.87f, 1.1f}, {1.3f, 0.3f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(duplicated.attribute(Trade::MeshAttribute::Normal), + Containers::arrayView({ + Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis(), + Vector3::zAxis(), Vector3::yAxis(), Vector3::xAxis() + }), TestSuite::Compare::Container); +} + +void DuplicateTest::duplicateMeshDataExtraEmpty() { + UnsignedByte indices[]{0, 1, 2, 2, 1, 0}; + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, + {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Trade::MeshData duplicated = MeshTools::duplicate(data, { + Trade::MeshAttributeData{4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3, nullptr} + }); + CORRADE_COMPARE(duplicated.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(!duplicated.isIndexed()); + CORRADE_COMPARE(duplicated.vertexCount(), 6); + CORRADE_COMPARE(duplicated.attributeCount(), 2); + CORRADE_COMPARE_AS(duplicated.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}, + {1.0f, -0.5f}, {0.87f, 1.1f}, {1.3f, 0.3f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE(duplicated.attributeStride(Trade::MeshAttribute::Normal), 24); + CORRADE_COMPARE(duplicated.attributeOffset(Trade::MeshAttribute::Normal), 12); +} + +void DuplicateTest::duplicateMeshDataExtraWrongCount() { + UnsignedByte indices[]{0, 1, 2, 2, 1, 0}; + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}, + {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + const Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis()}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::duplicate(data, { + Trade::MeshAttributeData{10}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(normals)} + }); + CORRADE_COMPARE(out.str(), "MeshTools::duplicate(): extra attribute 1 expected to have 3 items but got 2\n"); +} + +void DuplicateTest::duplicateMeshDataNoAttributes() { + UnsignedByte indices[]{0, 1, 2, 2, 1, 0}; + Trade::MeshData data{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}}; + + Trade::MeshData duplicated = MeshTools::duplicate(data, {}); + CORRADE_COMPARE(duplicated.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(!duplicated.isIndexed()); + CORRADE_COMPARE(duplicated.vertexCount(), 6); + CORRADE_COMPARE(duplicated.attributeCount(), 0); + CORRADE_VERIFY(!duplicated.vertexData()); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::DuplicateTest)