diff --git a/doc/changelog.dox b/doc/changelog.dox index 8137075f4..10cecb1ce 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -155,7 +155,8 @@ See also: @ref MeshTools::generateTriangleStripIndices() and @ref MeshTools::generateTriangleFanIndices() utilities for converting various mesh types to plain indexed @ref MeshPrimitive::Lines and - @ref MeshPrimitive::Triangles + @ref MeshPrimitive::Triangles, as well as @ref MeshTools::generateIndices() + operating directly on a @ref Trade::MeshData @subsubsection changelog-latest-new-platform Platform libraries diff --git a/src/Magnum/MeshTools/Concatenate.h b/src/Magnum/MeshTools/Concatenate.h index 9e78af4a8..7623ac391 100644 --- a/src/Magnum/MeshTools/Concatenate.h +++ b/src/Magnum/MeshTools/Concatenate.h @@ -50,7 +50,11 @@ namespace Implementation { The returned mesh contains vertices from all meshes concatenated together. If any mesh is indexed, the resulting mesh is indexed as well, with indices adjusted for vertex offsets of particular meshes. The behavior is undefined if -any mesh has indices out of bounds for its particular vertex count. +any mesh has indices out of bounds for its particular vertex count. Meshes with +@ref MeshPrimitive::LineStrip, @ref MeshPrimitive::LineLoop, +@ref MeshPrimitive::TriangleStrip and @ref MeshPrimitive::TriangleFan can't be +concatenated --- use @ref generateIndices() to turn them into +@ref MeshPrimitive::Lines or @ref MeshPrimitive::Triangles first. All attributes from the @p first mesh are taken; for each mesh in @p next, attributes present in @p first are copied, superfluous attributes ignored and diff --git a/src/Magnum/MeshTools/GenerateIndices.cpp b/src/Magnum/MeshTools/GenerateIndices.cpp index 1b01c5342..25ca1d63b 100644 --- a/src/Magnum/MeshTools/GenerateIndices.cpp +++ b/src/Magnum/MeshTools/GenerateIndices.cpp @@ -27,6 +27,9 @@ #include #include +#include + +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { @@ -143,4 +146,68 @@ Containers::Array generateTriangleFanIndices(const UnsignedInt vert return indices; } +Trade::MeshData generateIndices(Trade::MeshData&& data) { + CORRADE_ASSERT(!data.isIndexed(), + "MeshTools::generateIndices(): mesh data already indexed", + (Trade::MeshData{MeshPrimitive::Triangles, 0})); + + /* Transfer vertex / attribute data as-is, as those don't need any changes. + Release if possible. */ + Containers::Array vertexData; + const UnsignedInt vertexCount = data.vertexCount(); + if(data.vertexDataFlags() & Trade::DataFlag::Owned) + vertexData = data.releaseVertexData(); + else { + vertexData = Containers::Array{Containers::NoInit, data.vertexData().size()}; + Utility::copy(data.vertexData(), vertexData); + } + + /* Recreate the attribute array with views on the new vertexData */ + /** @todo if the vertex data were moved and this array is owned, it + wouldn't need to be recreated, but the logic is a bit complex */ + Containers::Array attributeData{data.attributeCount()}; + for(UnsignedInt i = 0, max = attributeData.size(); i != max; ++i) { + attributeData[i] = Trade::MeshAttributeData{data.attributeName(i), + data.attributeFormat(i), + Containers::StridedArrayView1D{vertexData, vertexData.data() + data.attributeOffset(i), vertexCount, data.attributeStride(i)}}; + } + + /* Generate the index array */ + MeshPrimitive primitive; + Containers::Array indexData; + if(data.primitive() == MeshPrimitive::LineStrip) { + primitive = MeshPrimitive::Lines; + indexData = Containers::Array{Containers::NoInit, 2*(vertexCount - 1)*sizeof(UnsignedInt)}; + generateLineStripIndicesInto(vertexCount, Containers::arrayCast(indexData)); + } else if(data.primitive() == MeshPrimitive::LineLoop) { + primitive = MeshPrimitive::Lines; + indexData = Containers::Array{Containers::NoInit, 2*vertexCount*sizeof(UnsignedInt)}; + generateLineLoopIndicesInto(vertexCount, Containers::arrayCast(indexData)); + } else if(data.primitive() == MeshPrimitive::TriangleStrip) { + primitive = MeshPrimitive::Triangles; + indexData = Containers::Array{Containers::NoInit, 3*(vertexCount - 2)*sizeof(UnsignedInt)}; + generateTriangleStripIndicesInto(vertexCount, Containers::arrayCast(indexData)); + } else if(data.primitive() == MeshPrimitive::TriangleFan) { + primitive = MeshPrimitive::Triangles; + indexData = Containers::Array{Containers::NoInit, 3*(vertexCount - 2)*sizeof(UnsignedInt)}; + generateTriangleFanIndicesInto(vertexCount, Containers::arrayCast(indexData)); + } else CORRADE_ASSERT(false, + "MeshTools::generateIndices(): invalid primitive" << data.primitive(), + (Trade::MeshData{MeshPrimitive::Triangles, 0})); + + Trade::MeshIndexData indices{MeshIndexType::UnsignedInt, indexData}; + return Trade::MeshData{primitive, std::move(indexData), indices, + std::move(vertexData), std::move(attributeData)}; +} + +Trade::MeshData generateIndices(const Trade::MeshData& data) { + CORRADE_ASSERT(!data.isIndexed(), + "MeshTools::generateIndices(): mesh data already indexed", + (Trade::MeshData{MeshPrimitive::Triangles, 0})); + + return generateIndices(Trade::MeshData{data.primitive(), + {}, data.vertexData(), Trade::meshAttributeDataNonOwningArray(data.attributeData()), + data.vertexCount()}); +} + }} diff --git a/src/Magnum/MeshTools/GenerateIndices.h b/src/Magnum/MeshTools/GenerateIndices.h index 28d981723..ca4a1edda 100644 --- a/src/Magnum/MeshTools/GenerateIndices.h +++ b/src/Magnum/MeshTools/GenerateIndices.h @@ -26,12 +26,13 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::generateLineStripIndices(), @ref Magnum::MeshTools::generateLineStripIndicesInto(), @ref Magnum::MeshTools::generateLineLoopIndices(), @ref Magnum::MeshTools::generateLineLoopIndicesInto(), @ref Magnum::MeshTools::generateTriangleStripIndices(), @ref Magnum::MeshTools::generateTriangleStripIndicesInto(), @ref Magnum::MeshTools::generateTriangleFanIndices(), @ref Magnum::MeshTools::generateTriangleFanIndicesInto() + * @brief Function @ref Magnum::MeshTools::generateLineStripIndices(), @ref Magnum::MeshTools::generateLineStripIndicesInto(), @ref Magnum::MeshTools::generateLineLoopIndices(), @ref Magnum::MeshTools::generateLineLoopIndicesInto(), @ref Magnum::MeshTools::generateTriangleStripIndices(), @ref Magnum::MeshTools::generateTriangleStripIndicesInto(), @ref Magnum::MeshTools::generateTriangleFanIndices(), @ref Magnum::MeshTools::generateTriangleFanIndicesInto(), @ref Magnum::MeshTools::generateIndices() * @m_since_latest */ #include "Magnum/Magnum.h" #include "Magnum/MeshTools/visibility.h" +#include "Magnum/Trade/Trade.h" namespace Magnum { namespace MeshTools { @@ -43,7 +44,8 @@ Can be used to convert a @ref MeshPrimitive::LineStrip mesh to @ref MeshPrimitive::Lines. The @p vertexCount is expected to be at least @cpp 2 @ce. Primitive restart is not supported. @see @ref generateLineStripIndicesInto(), @ref generateLineLoopIndices(), - @ref generateTriangleStripIndices(), @ref generateTriangleFanIndices() + @ref generateTriangleStripIndices(), @ref generateTriangleFanIndices(), + @ref generateIndices() */ MAGNUM_MESHTOOLS_EXPORT Containers::Array generateLineStripIndices(UnsignedInt vertexCount); @@ -66,7 +68,8 @@ Can be used to convert a @ref MeshPrimitive::LineLoop mesh to @ref MeshPrimitive::Lines. The @p vertexCount is expected to be at least @cpp 2 @ce. Primitive restart is not supported. @see @ref generateLineLoopIndicesInto(), @ref generateLineStripIndices(), - @ref generateTriangleStripIndices(), @ref generateTriangleFanIndices() + @ref generateTriangleStripIndices(), @ref generateTriangleFanIndices(), + @ref generateIndices() */ MAGNUM_MESHTOOLS_EXPORT Containers::Array generateLineLoopIndices(UnsignedInt vertexCount); @@ -89,7 +92,8 @@ Can be used to convert a @ref MeshPrimitive::TriangleStrip mesh to @ref MeshPrimitive::Triangles. The @p vertexCount is expected to be at least @cpp 3 @ce. Primitive restart is not supported. @see @ref generateTriangleStripIndicesInto(), @ref generateLineStripIndices(), - @ref generateLineLoopIndices(), @ref generateTriangleFanIndices() + @ref generateLineLoopIndices(), @ref generateTriangleFanIndices(), + @ref generateIndices() */ MAGNUM_MESHTOOLS_EXPORT Containers::Array generateTriangleStripIndices(UnsignedInt vertexCount); @@ -112,7 +116,8 @@ Can be used to convert a @ref MeshPrimitive::TriangleFan mesh to @ref MeshPrimitive::Triangles. The @p vertexCount is expected to be at least @cpp 3 @ce. Primitive restart is not supported. @see @ref generateTriangleFanIndicesInto(), @ref generateLineStripIndices(), - @ref generateLineLoopIndices(), @ref generateTriangleStripIndices() + @ref generateLineLoopIndices(), @ref generateTriangleStripIndices(), + @ref generateIndices() */ MAGNUM_MESHTOOLS_EXPORT Containers::Array generateTriangleFanIndices(UnsignedInt vertexCount); @@ -127,6 +132,36 @@ least @cpp 3 @ce, the @p indices array is expected to have a size of */ MAGNUM_MESHTOOLS_EXPORT void generateTriangleFanIndicesInto(UnsignedInt vertexCount, const Containers::StridedArrayView1D& into); +/** +@brief Convert a mesh to plain indexed lines or triangles +@m_since_latest + +Expects that @p mesh is not indexed and is one of +@ref MeshPrimitive::LineStrip, @ref MeshPrimitive::LineLoop, +@ref MeshPrimitive::TriangleStrip, @ref MeshPrimitive::TriangleFan primitives. +If your mesh is indexed, call @ref duplicate(const Trade::MeshData& data, Containers::ArrayView) +on it first. + +The resulting mesh always has @ref MeshIndexType::UnsignedInt, call +@ref compressIndices(const Trade::MeshData&, MeshIndexType) on the result to +compress it to a smaller type, if desired. This function will unconditionally +make a copy of all vertex data, use @ref generateIndices(Trade::MeshData&&) to +avoid that copy. +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData generateIndices(const Trade::MeshData& mesh); + +/** +@brief Convert a mesh to plain indexed lines or triangles +@m_since_latest + +Compared to @ref generateIndices(const Trade::MeshData&) this function can +transfer ownership of @p data vertex buffer (in case it is owned) to the +returned instance instead of making a copy of it. Attribute data is copied +always. +@see @ref Trade::MeshData::vertexDataFlags() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData generateIndices(Trade::MeshData&& data); + }} #endif diff --git a/src/Magnum/MeshTools/Test/GenerateIndicesTest.cpp b/src/Magnum/MeshTools/Test/GenerateIndicesTest.cpp index 14b038da2..4042eef6f 100644 --- a/src/Magnum/MeshTools/Test/GenerateIndicesTest.cpp +++ b/src/Magnum/MeshTools/Test/GenerateIndicesTest.cpp @@ -30,7 +30,9 @@ #include #include +#include "Magnum/Math/Vector2.h" #include "Magnum/MeshTools/GenerateIndices.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { @@ -52,6 +54,40 @@ struct GenerateIndicesTest: TestSuite::Tester { void generateTriangleFanIndices(); void generateTriangleFanIndicesWrongVertexCount(); void generateTriangleFanIndicesIntoWrongSize(); + + void generateIndicesMeshData(); + void generateIndicesMeshDataMove(); + void generateIndicesMeshDataIndexed(); + void generateIndicesMeshDataInvalidPrimitive(); +}; + +const struct { + MeshPrimitive primitive; + Containers::Array indices; +} MeshDataData[] { + {MeshPrimitive::LineStrip, Containers::array({ + 0, 1, + 1, 2, + 2, 3, + 3, 4 + })}, + {MeshPrimitive::LineLoop, Containers::array({ + 0, 1, + 1, 2, + 2, 3, + 3, 4, + 4, 0 + })}, + {MeshPrimitive::TriangleStrip, Containers::array({ + 0, 1, 2, + 2, 1, 3, /* Reversed */ + 2, 3, 4 + })}, + {MeshPrimitive::TriangleFan, Containers::array({ + 0, 1, 2, + 0, 2, 3, + 0, 3, 4 + })} }; GenerateIndicesTest::GenerateIndicesTest() { @@ -70,6 +106,13 @@ GenerateIndicesTest::GenerateIndicesTest() { &GenerateIndicesTest::generateTriangleFanIndices, &GenerateIndicesTest::generateTriangleFanIndicesWrongVertexCount, &GenerateIndicesTest::generateTriangleFanIndicesIntoWrongSize}); + + addInstancedTests({&GenerateIndicesTest::generateIndicesMeshData}, + Containers::arraySize(MeshDataData)); + + addTests({&GenerateIndicesTest::generateIndicesMeshDataMove, + &GenerateIndicesTest::generateIndicesMeshDataIndexed, + &GenerateIndicesTest::generateIndicesMeshDataInvalidPrimitive}); } void GenerateIndicesTest::generateLineStripIndices() { @@ -259,6 +302,129 @@ void GenerateIndicesTest::generateTriangleFanIndicesIntoWrongSize() { "MeshTools::generateTriangleFanIndicesInto(): bad output size, expected 9 but got 8\n"); } +void GenerateIndicesTest::generateIndicesMeshData() { + auto&& data = MeshDataData[testCaseInstanceId()]; + { + std::ostringstream out; + Debug{&out, Debug::Flag::NoNewlineAtTheEnd} << data.primitive; + setTestCaseDescription(out.str()); + } + + const struct Vertex { + Vector2 position; + Vector2 textureCoordinates; + } vertexData[] { + {{1.5f, 0.3f}, {0.2f, 0.8f}}, + {{2.5f, 1.3f}, {0.3f, 0.7f}}, + {{3.5f, 2.3f}, {0.4f, 0.6f}}, + {{4.5f, 3.3f}, {0.5f, 0.5f}}, + {{5.5f, 4.3f}, {0.6f, 0.4f}} + }; + + Trade::MeshData mesh{data.primitive, + {}, vertexData, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(vertexData, + &vertexData[0].position, 5, sizeof(Vertex))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexData, + &vertexData[0].textureCoordinates, 5, sizeof(Vertex))} + }}; + + Trade::MeshData out = generateIndices(mesh); + CORRADE_VERIFY(out.isIndexed()); + CORRADE_COMPARE(out.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(out.indices(), data.indices, + TestSuite::Compare::Container); + + CORRADE_COMPARE(out.attributeCount(), 2); + CORRADE_COMPARE_AS(out.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.5f, 0.3f}, {2.5f, 1.3f}, {3.5f, 2.3f}, {4.5f, 3.3f}, {5.5f, 4.3f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(out.attribute(Trade::MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {0.2f, 0.8f}, {0.3f, 0.7f}, {0.4f, 0.6f}, {0.5f, 0.5f}, {0.6f, 0.4f} + }), TestSuite::Compare::Container); +} + +void GenerateIndicesTest::generateIndicesMeshDataMove() { + struct Vertex { + Vector2 position; + Vector2 textureCoordinates; + }; + + Containers::Array vertexData{5*sizeof(Vertex)}; + auto vertices = Containers::arrayCast(vertexData); + vertices[0].position = {1.5f, 0.3f}; + vertices[1].position = {2.5f, 1.3f}; + vertices[2].position = {3.5f, 2.3f}; + vertices[3].position = {4.5f, 3.3f}; + vertices[4].position = {5.5f, 4.3f}; + vertices[0].textureCoordinates = {0.2f, 0.8f}; + vertices[1].textureCoordinates = {0.3f, 0.7f}; + vertices[2].textureCoordinates = {0.4f, 0.6f}; + vertices[3].textureCoordinates = {0.5f, 0.5f}; + vertices[4].textureCoordinates = {0.6f, 0.4f}; + + Trade::MeshData out = generateIndices(Trade::MeshData{ + MeshPrimitive::TriangleFan, std::move(vertexData), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(vertices, + &vertices[0].position, 5, sizeof(Vertex))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertices, + &vertices[0].textureCoordinates, 5, sizeof(Vertex))} + }}); + CORRADE_VERIFY(out.isIndexed()); + CORRADE_COMPARE(out.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(out.indices(), + Containers::arrayView({ + 0, 1, 2, + 0, 2, 3, + 0, 3, 4 + }), TestSuite::Compare::Container); + + CORRADE_COMPARE(out.attributeCount(), 2); + CORRADE_COMPARE_AS(out.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.5f, 0.3f}, {2.5f, 1.3f}, {3.5f, 2.3f}, {4.5f, 3.3f}, {5.5f, 4.3f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(out.attribute(Trade::MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {0.2f, 0.8f}, {0.3f, 0.7f}, {0.4f, 0.6f}, {0.5f, 0.5f}, {0.6f, 0.4f} + }), TestSuite::Compare::Container); + + /* The vertex data should be moved, not copied */ + CORRADE_COMPARE(out.vertexData().data(), static_cast(vertices.data())); +} + +void GenerateIndicesTest::generateIndicesMeshDataIndexed() { + UnsignedByte indices[]{0}; + Trade::MeshData mesh{MeshPrimitive::TriangleFan, + {}, indices, Trade::MeshIndexData{indices}, 0}; + + /* Test both r-value and l-value overload */ + std::ostringstream out; + Error redirectError{&out}; + generateIndices(mesh); + generateIndices(Trade::MeshData{MeshPrimitive::TriangleFan, + {}, indices, Trade::MeshIndexData{indices}, 0}); + CORRADE_COMPARE(out.str(), + "MeshTools::generateIndices(): mesh data already indexed\n" + "MeshTools::generateIndices(): mesh data already indexed\n"); +} + +void GenerateIndicesTest::generateIndicesMeshDataInvalidPrimitive() { + Trade::MeshData mesh{MeshPrimitive::Triangles, 2}; + + std::ostringstream out; + Error redirectError{&out}; + generateIndices(mesh); + CORRADE_COMPARE(out.str(), + "MeshTools::generateIndices(): invalid primitive MeshPrimitive::Triangles\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::GenerateIndicesTest)