diff --git a/doc/changelog.dox b/doc/changelog.dox index 77eaf7603..44acd36f8 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -113,6 +113,8 @@ See also: @ref Trade::MeshData is interleaved - 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 - Added @ref MeshTools::subdivideInPlace() for allocation-less mesh subdivision - New @ref MeshTools::removeDuplicatesInPlace() variant that works on diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp index 75db8a67f..525c5cabe 100644 --- a/src/Magnum/MeshTools/Interleave.cpp +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -25,6 +25,8 @@ #include "Interleave.h" +#include + #include "Magnum/Math/Functions.h" #include "Magnum/Trade/MeshData.h" @@ -134,4 +136,105 @@ Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt return interleavedLayout(data, vertexCount, Containers::arrayView(extra)); } +Trade::MeshData interleave(Trade::MeshData&& data, const Containers::ArrayView extra) { + /* If there are no attributes and no index buffer, bail -- the vertex count + is the only property we can transfer. If this wouldn't be done, the + return at the end would assert as vertex count is only passed implicitly + via attributes (which there are none). */ + if(!data.attributeCount() && extra.empty() && !data.isIndexed()) + return Trade::MeshData{data.primitive(), data.vertexCount()}; + + /* Transfer the indices unchanged, in case the mesh is indexed */ + Containers::Array indexData; + Trade::MeshIndexData indices; + if(data.isIndexed()) { + /* If we can steal the data, do it */ + if(data.indexDataFlags() & Trade::DataFlag::Owned) { + indices = Trade::MeshIndexData{data.indices()}; + indexData = data.releaseIndexData(); + } else { + indexData = Containers::Array{data.indexData().size()}; + Utility::copy(data.indexData(), indexData); + indices = Trade::MeshIndexData{data.indexType(), + Containers::ArrayView{indexData + data.indexOffset(), data.indices().size()[0]*data.indices().size()[1]}}; + } + } + + const bool interleaved = isInterleaved(data); + + /* If the mesh is already interleaved and we don't have anything extra, + steal that data as well */ + Containers::Array vertexData; + Containers::Array attributeData; + if(interleaved && extra.empty() && (data.vertexDataFlags() & Trade::DataFlag::Owned)) { + attributeData = data.releaseAttributeData(); + vertexData = data.releaseVertexData(); + + /* Otherwise do it the hard way */ + } else { + /* Calculate the layout */ + Trade::MeshData layout = interleavedLayout(data, data.vertexCount(), extra); + + /* Copy existing attributes to new locations */ + for(UnsignedInt i = 0; i != data.attributeCount(); ++i) + Utility::copy(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::interleave(): extra attribute" << i << "expected to have" << data.vertexCount() << "items but got" << extra[i].data().size(), + (Trade::MeshData{MeshPrimitive::Triangles, 0})); + const Containers::StridedArrayView2D attribute = + Containers::arrayCast<2, const char>(extra[i].data(), vertexFormatSize(extra[i].format())); + Utility::copy(attribute, layout.mutableAttribute(attributeIndex)); + } + + ++attributeIndex; + } + + /* Release the data from the layout to pack them into the output */ + vertexData = layout.releaseVertexData(); + attributeData = layout.releaseAttributeData(); + } + + return Trade::MeshData{data.primitive(), std::move(indexData), indices, + std::move(vertexData), std::move(attributeData)}; +} + +Trade::MeshData interleave(Trade::MeshData&& data, const std::initializer_list extra) { + return interleave(std::move(data), Containers::arrayView(extra)); +} + +Trade::MeshData interleave(const Trade::MeshData& data, const Containers::ArrayView extra) { + Containers::ArrayView indexData; + Trade::MeshIndexData indices; + if(data.isIndexed()) { + indexData = data.indexData(); + indices = Trade::MeshIndexData{data.indices()}; + + /* If there's neither an index array nor any attributes in the original + mesh, we need to pass vertex count explicitly (MeshData asserts on that + to avoid it getting lost.) */ + } else if(!data.attributeCount()) { + return interleave(Trade::MeshData{data.primitive(), data.vertexCount()}, extra); + } + + return interleave(Trade::MeshData{data.primitive(), + {}, indexData, indices, + {}, data.vertexData(), Trade::meshAttributeDataNonOwningArray(data.attributeData()) + }, extra); + +} + +Trade::MeshData interleave(const Trade::MeshData& data, const std::initializer_list extra) { + return interleave(std::move(data), Containers::arrayView(extra)); +} + }} diff --git a/src/Magnum/MeshTools/Interleave.h b/src/Magnum/MeshTools/Interleave.h index 81dd6ee71..1f30c40bd 100644 --- a/src/Magnum/MeshTools/Interleave.h +++ b/src/Magnum/MeshTools/Interleave.h @@ -145,7 +145,12 @@ would be 21 bytes, causing possible performance loss. @see @ref interleaveInto() */ -template Containers::Array interleave(const T& first, const U&... next) +template::value>::type + #endif +> Containers::Array interleave(const T& first, const U&... next) { /* Compute buffer size and stride */ const std::size_t attributeCount = Implementation::AttributeCount{}(first, next...); @@ -234,6 +239,51 @@ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(const Trade::MeshData& */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(const Trade::MeshData& data, UnsignedInt vertexCount, std::initializer_list extra); +/** +@brief Interleave mesh data +@m_since_latest + +Returns a copy of @p data with all attributes interleaved but everything else +(indices, primitive type, ...) kept as-is. The @p extra attributes, if any, are +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 each attribute in @p extra has either the same amount of elements +as @p data vertex count or has none. +@see @ref isInterleaved(), @ref Trade::MeshData::attributeData() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(const Trade::MeshData& data, Containers::ArrayView extra = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(const Trade::MeshData& data, std::initializer_list extra); + +/** +@brief Interleave mesh data +@m_since_latest + +Compared to @ref interleave(const Trade::MeshData&, Containers::ArrayView) +this function can transfer ownership of @p data index buffer (in case it is +owned) and vertex buffer (in case it is owned, already interleaved and there's +no @p extra attributes) to the returned instance instead of making copies of +them. +@see @ref isInterleaved(), @ref Trade::MeshData::indexDataFlags(), + @ref Trade::MeshData::vertexDataFlags(), + @ref Trade::MeshData::attributeData() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(Trade::MeshData&& data, Containers::ArrayView extra = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(Trade::MeshData&& data, std::initializer_list extra); + }} #endif diff --git a/src/Magnum/MeshTools/Test/InterleaveTest.cpp b/src/Magnum/MeshTools/Test/InterleaveTest.cpp index f962a6c47..4be0d805d 100644 --- a/src/Magnum/MeshTools/Test/InterleaveTest.cpp +++ b/src/Magnum/MeshTools/Test/InterleaveTest.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -65,6 +66,16 @@ struct InterleaveTest: Corrade::TestSuite::Tester { void interleavedLayoutAlreadyInterleavedAliased(); void interleavedLayoutAlreadyInterleavedExtra(); void interleavedLayoutNothing(); + + void interleaveMeshData(); + void interleaveMeshDataIndexed(); + void interleaveMeshDataExtra(); + void interleaveMeshDataExtraEmpty(); + void interleaveMeshDataExtraOriginalEmpty(); + void interleaveMeshDataExtraWrongCount(); + void interleaveMeshDataAlreadyInterleavedMove(); + void interleaveMeshDataAlreadyInterleavedMoveNonOwned(); + void interleaveMeshDataNothing(); }; InterleaveTest::InterleaveTest() { @@ -93,7 +104,17 @@ InterleaveTest::InterleaveTest() { &InterleaveTest::interleavedLayoutAlreadyInterleaved, &InterleaveTest::interleavedLayoutAlreadyInterleavedAliased, &InterleaveTest::interleavedLayoutAlreadyInterleavedExtra, - &InterleaveTest::interleavedLayoutNothing}); + &InterleaveTest::interleavedLayoutNothing, + + &InterleaveTest::interleaveMeshData, + &InterleaveTest::interleaveMeshDataIndexed, + &InterleaveTest::interleaveMeshDataExtra, + &InterleaveTest::interleaveMeshDataExtraEmpty, + &InterleaveTest::interleaveMeshDataExtraOriginalEmpty, + &InterleaveTest::interleaveMeshDataExtraWrongCount, + &InterleaveTest::interleaveMeshDataAlreadyInterleavedMove, + &InterleaveTest::interleaveMeshDataAlreadyInterleavedMoveNonOwned, + &InterleaveTest::interleaveMeshDataNothing}); } void InterleaveTest::attributeCount() { @@ -566,6 +587,221 @@ void InterleaveTest::interleavedLayoutNothing() { CORRADE_COMPARE(layout.vertexData().size(), 0); } +void InterleaveTest::interleaveMeshData() { + 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, {}, + Containers::arrayView(&vertexData, sizeof(vertexData)), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(vertexData.positions)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(vertexData.normals)} + }}; + + Trade::MeshData interleaved = MeshTools::interleave(data); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!interleaved.isIndexed()); + /* No reason to not be like this */ + CORRADE_COMPARE(interleaved.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned); + CORRADE_COMPARE(interleaved.attributeCount(), 2); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Position), + Containers::stridedArrayView(vertexData.positions), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Normal), + Containers::stridedArrayView(vertexData.normals), + TestSuite::Compare::Container); +} + +void InterleaveTest::interleaveMeshDataIndexed() { + /* Testing also offset */ + UnsignedShort indexData[50 + 3]; + indexData[50] = 0; + indexData[51] = 2; + indexData[52] = 1; + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, Containers::arrayView(indexData), Trade::MeshIndexData{Containers::arrayView(indexData).suffix(50)}, + {}, Containers::arrayView(positions), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Trade::MeshData interleaved = MeshTools::interleave(data); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(interleaved.isIndexed()); + CORRADE_COMPARE(interleaved.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(interleaved.indexData().size(), 106); + CORRADE_COMPARE_AS(interleaved.indices(), + Containers::arrayView(indexData).suffix(50), + TestSuite::Compare::Container); + CORRADE_COMPARE(interleaved.attributeCount(), 1); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Position), + Containers::stridedArrayView(positions), + TestSuite::Compare::Container); +} + +void InterleaveTest::interleaveMeshDataExtra() { + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, Containers::arrayView(positions), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + const Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}; + Trade::MeshData interleaved = MeshTools::interleave(data, { + Trade::MeshAttributeData{10}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(normals)} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!interleaved.isIndexed()); + /* No reason to not be like this */ + CORRADE_COMPARE(interleaved.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned); + CORRADE_COMPARE(interleaved.attributeCount(), 2); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Position), + Containers::stridedArrayView(positions), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Normal), + Containers::stridedArrayView(normals), + TestSuite::Compare::Container); +} + +void InterleaveTest::interleaveMeshDataExtraEmpty() { + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, Containers::arrayView(positions), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + + Trade::MeshData interleaved = MeshTools::interleave(data, { + Trade::MeshAttributeData{4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, nullptr} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!interleaved.isIndexed()); + /* No reason to not be like this */ + CORRADE_COMPARE(interleaved.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned); + CORRADE_COMPARE(interleaved.attributeCount(), 2); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Position), + Containers::stridedArrayView(positions), + TestSuite::Compare::Container); + CORRADE_COMPARE(interleaved.attributeStride(Trade::MeshAttribute::Normal), 24); + CORRADE_COMPARE(interleaved.attributeOffset(Trade::MeshAttribute::Normal), 12); +} + +void InterleaveTest::interleaveMeshDataExtraOriginalEmpty() { + Trade::MeshData data{MeshPrimitive::TriangleFan, 3}; + + /* Verify the original vertex count gets passed through */ + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData interleaved = MeshTools::interleave(data, { + Trade::MeshAttributeData{4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }); + + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!interleaved.isIndexed()); + /* No reason to not be like this */ + CORRADE_COMPARE(interleaved.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned); + CORRADE_COMPARE(interleaved.attributeCount(), 1); + CORRADE_COMPARE_AS(interleaved.attribute(Trade::MeshAttribute::Position), + Containers::stridedArrayView(positions), + TestSuite::Compare::Container); +} + +void InterleaveTest::interleaveMeshDataExtraWrongCount() { + Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, Containers::arrayView(positions), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)} + }}; + const Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis()}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::interleave(data, { + Trade::MeshAttributeData{10}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, Containers::arrayView(normals)} + }); + CORRADE_COMPARE(out.str(), "MeshTools::interleave(): extra attribute 1 expected to have 3 items but got 2\n"); +} + +void InterleaveTest::interleaveMeshDataAlreadyInterleavedMove() { + Containers::Array indexData{4}; + auto indexView = Containers::arrayCast(indexData); + Containers::Array vertexData{3*24}; + Containers::StridedArrayView1D positionView{vertexData, + reinterpret_cast(vertexData.data()), 3, 24}; + Containers::StridedArrayView1D normalView{vertexData, + reinterpret_cast(vertexData.data() + 10), 3, 24}; + auto attributeData = Containers::array({ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positionView}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normalView} + }); + const Trade::MeshAttributeData* attributePointer = attributeData; + + Trade::MeshData data{MeshPrimitive::TriangleFan, + std::move(indexData), Trade::MeshIndexData{indexView}, + std::move(vertexData), std::move(attributeData)}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + + /* {} just to cover the initializer_list overload :P */ + Trade::MeshData interleaved = MeshTools::interleave(std::move(data), {}); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.indexCount(), 2); + CORRADE_COMPARE(interleaved.attributeCount(), 2); + CORRADE_COMPARE(interleaved.vertexCount(), 3); + /* Things got just moved without copying */ + CORRADE_VERIFY(interleaved.indexData().data() == static_cast(indexView.data())); + CORRADE_VERIFY(interleaved.attributeData().data() == attributePointer); + CORRADE_VERIFY(interleaved.vertexData().data() == positionView.data()); +} + +void InterleaveTest::interleaveMeshDataAlreadyInterleavedMoveNonOwned() { + Containers::Array indexData{4}; + auto indexView = Containers::arrayCast(indexData); + Containers::Array vertexData{3*24}; + Containers::StridedArrayView1D positionView{vertexData, + reinterpret_cast(vertexData.data()), 3, 24}; + Containers::StridedArrayView1D normalView{vertexData, + reinterpret_cast(vertexData.data() + 10), 3, 24}; + auto attributeData = Containers::array({ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positionView}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normalView} + }); + const Trade::MeshAttributeData* attributePointer = attributeData; + + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, indexData, Trade::MeshIndexData{indexView}, + {}, vertexData, std::move(attributeData)}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + + Trade::MeshData interleaved = MeshTools::interleave(std::move(data)); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.indexCount(), 2); + CORRADE_COMPARE(interleaved.attributeCount(), 2); + CORRADE_COMPARE(interleaved.vertexCount(), 3); + /* The moved data array doesn't own these so things got copied */ + CORRADE_VERIFY(interleaved.indexData().data() != static_cast(indexView.data())); + CORRADE_VERIFY(interleaved.attributeData().data() != attributePointer); + CORRADE_VERIFY(interleaved.vertexData().data() != positionView.data()); +} + +void InterleaveTest::interleaveMeshDataNothing() { + Trade::MeshData interleaved = MeshTools::interleave(Trade::MeshData{MeshPrimitive::Points, 2}); + CORRADE_VERIFY(MeshTools::isInterleaved(interleaved)); + CORRADE_COMPARE(interleaved.attributeCount(), 0); + CORRADE_COMPARE(interleaved.vertexCount(), 2); + CORRADE_VERIFY(!interleaved.vertexData()); + CORRADE_COMPARE(interleaved.vertexData().size(), 0); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::InterleaveTest)