From 274759c0be7148a94f58b64e5f8d7574322a3435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 31 Dec 2019 00:23:16 +0100 Subject: [PATCH 001/107] MeshTools: clean up and clarify removeDuplicates(). I spent a week (!) thinking the extra remapping array is not necessary. Actually, it is (though with a non-shitty hashmap the allocation could be done for both) -- this was an university assignment almost a decade ago and it wouldn't pass if it would be wasting time. But the english of past me was horrible. Yes. --- src/Magnum/MeshTools/RemoveDuplicates.h | 51 +++++++++++++------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/src/Magnum/MeshTools/RemoveDuplicates.h b/src/Magnum/MeshTools/RemoveDuplicates.h index 2f5278c9ed..dc9524d2e4 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.h +++ b/src/Magnum/MeshTools/RemoveDuplicates.h @@ -63,15 +63,15 @@ no interpolation is done. Note that this function is meant to be used for floating-point data (or generally with non-zero @p epsilon), for discrete data the usual sorting method is much more efficient. -If you want to remove duplicate data from already indexed array, first remove -duplicates as if the array wasn't indexed at all and then use @ref duplicate() -to combine the two index arrays: +If you want to remove duplicate data from an already indexed array, first +remove duplicates as if the array wasn't indexed at all and then use +@ref duplicate() to combine the two index arrays: @snippet MagnumMeshTools.cpp removeDuplicates1 Removing duplicates in multiple indcidental arrays is also possible --- first remove duplicates in each array separately and then use @ref combineIndexedArrays() -to combine the resulting index arrays to single index array and reorder the +to combine the resulting index arrays to single index array, and reorder the data accordingly: @snippet MagnumMeshTools.cpp removeDuplicates2 @@ -86,35 +86,42 @@ template std::vector removeDuplicates(std::vector resultIndices(data.size()); - std::iota(resultIndices.begin(), resultIndices.end(), 0); + /* Resulting index array. Because we'll be remapping these, we need to + start from a 0..n sequence. */ + std::vector indices(data.size()); + std::iota(indices.begin(), indices.end(), 0); /* Table containing original vector index for each discretized vector. Reserving more buckets than necessary (i.e. as if each vector was unique). */ std::unordered_map, UnsignedInt, Implementation::VectorHash> table(data.size()); - /* Index array for each pass, new data array */ - std::vector indices; - indices.reserve(data.size()); + /* Index array that'll be filled in each pass and then used for remapping + the `indices` */ + std::vector remapping(data.size()); /* First go with original coordinates, then move them by epsilon/2 in each direction. */ Vector moved; for(std::size_t moving = 0; moving <= Vector::Size; ++moving) { + /* Clear the table for this pass */ + table.clear(); + /* Go through all vectors */ for(std::size_t i = 0; i != data.size(); ++i) { - /* Try to insert new vertex to the table */ - const Math::Vector v((data[i] + moved - minmax.first)/epsilon); + /* Try to insert new vertex into the table */ + const Math::Vector v{(data[i] + moved - minmax.first)/epsilon}; const auto result = table.emplace(v, table.size()); - /* Add the (either new or already existing) index to index array */ - indices.push_back(result.first->second); + /* Add the (either new or already existing) index into index array */ + remapping[i] = result.first->second; - /* If this is new combination, copy the data to new (earlier) - possition in the array */ - if(result.second && i != table.size()-1) data[table.size()-1] = data[i]; + /* If this is a new combination, copy the data to new (earlier) + position in the array. Data in [table.size()-1, i) are already + present in the [0, table.size()-1) range from previous + iterations so we aren't overwriting anything. */ + if(result.second && i != table.size() - 1) + data[table.size()-1] = data[i]; } /* Shrink the data array */ @@ -122,21 +129,17 @@ template std::vector removeDuplicates(std::vector Date: Sat, 4 Jan 2020 23:09:17 +0100 Subject: [PATCH 002/107] MeshTools: add STL-free removeDuplicatesInPlace() & an indexed variant also. --- doc/changelog.dox | 3 + doc/snippets/MagnumMeshTools.cpp | 13 +- src/Magnum/MeshTools/GenerateNormals.h | 2 +- src/Magnum/MeshTools/RemoveDuplicates.h | 114 +++++++++++++----- src/Magnum/MeshTools/Test/CMakeLists.txt | 1 + .../MeshTools/Test/RemoveDuplicatesTest.cpp | 103 ++++++++++++++-- 6 files changed, 184 insertions(+), 52 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 5634955948..a206e7593e 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -207,6 +207,9 @@ See also: - Added @ref MeshTools::subdivideInPlace() that operates on a partially filled array view instead of a @ref std::vector +- Added @ref MeshTools::removeDuplicatesIndexedInPlace() that operates + in-place on an indexed array view and a STL-less + @ref MeshTools::removeDuplicatesInPlace() variant @subsubsection changelog-latest-changes-platform Platform libraries diff --git a/doc/snippets/MagnumMeshTools.cpp b/doc/snippets/MagnumMeshTools.cpp index c57ee65b88..a34dcc1224 100644 --- a/doc/snippets/MagnumMeshTools.cpp +++ b/doc/snippets/MagnumMeshTools.cpp @@ -85,16 +85,7 @@ auto data = MeshTools::interleave(positions, weights, 2, vertexColors, 1); } { -/* [removeDuplicates1] */ -std::vector indices; -std::vector positions; - -indices = MeshTools::duplicate(indices, MeshTools::removeDuplicates(positions)); -/* [removeDuplicates1] */ -} - -{ -/* [removeDuplicates2] */ +/* [removeDuplicates-multiple] */ std::vector positions; std::vector texCoords; @@ -105,7 +96,7 @@ std::vector indices = MeshTools::combineIndexedArrays( std::make_pair(std::cref(positionIndices), std::ref(positions)), std::make_pair(std::cref(texCoordIndices), std::ref(texCoords)) ); -/* [removeDuplicates2] */ +/* [removeDuplicates-multiple] */ } { diff --git a/src/Magnum/MeshTools/GenerateNormals.h b/src/Magnum/MeshTools/GenerateNormals.h index 5d73a461c4..b515d4f95f 100644 --- a/src/Magnum/MeshTools/GenerateNormals.h +++ b/src/Magnum/MeshTools/GenerateNormals.h @@ -47,7 +47,7 @@ namespace Magnum { namespace MeshTools { All vertices in each triangle face get the same normal vector. Expects that the position count is divisible by 3. If you need to generate flat normals for an indexed mesh, @ref duplicate() the vertices first, after the operation you -might want to remove the duplicates again using @ref removeDuplicates(). +might want to remove the duplicates again using @ref removeDuplicatesInPlace(). Example usage: @snippet MagnumMeshTools.cpp generateFlatNormals diff --git a/src/Magnum/MeshTools/RemoveDuplicates.h b/src/Magnum/MeshTools/RemoveDuplicates.h index dc9524d2e4..a256bf4125 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.h +++ b/src/Magnum/MeshTools/RemoveDuplicates.h @@ -26,13 +26,14 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::removeDuplicates() + * @brief Function @ref Magnum::MeshTools::removeDuplicatesInPlace(), @ref Magnum::MeshTools::removeDuplicatesIndexedInPlace(), @ref Magnum::MeshTools::removeDuplicates() */ #include #include #include #include +#include #include #include @@ -51,11 +52,14 @@ namespace Implementation { } /** -@brief Remove duplicate floating-point vector data from given array -@param[in,out] data Input data array -@param[in] epsilon Epsilon value, vertices nearer than this distance will be +@brief Remove duplicate floating-point vector data from given array in-place +@param[in,out] data Data array, duplicate items will be cut away with order + preserved +@param[in] epsilon Epsilon value, vertices closer than this distance will be melt together -@return Index array and unique data +@return Size of unique prefix in the cleaned up @p data array and the resulting + index array +@m_since_latest Removes duplicate data from the array by collapsing them into buckets of size @p epsilon. First vector in given bucket is used, other ones are thrown away, @@ -63,57 +67,82 @@ no interpolation is done. Note that this function is meant to be used for floating-point data (or generally with non-zero @p epsilon), for discrete data the usual sorting method is much more efficient. -If you want to remove duplicate data from an already indexed array, first -remove duplicates as if the array wasn't indexed at all and then use -@ref duplicate() to combine the two index arrays: +If you want to remove duplicate data from an already indexed array, use +@ref removeDuplicatesIndexedInPlace() instead. See also +@ref removeDuplicates(std::vector&, typename Vector::Type) for a +variant operating on a STL vector. +*/ +template std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView1D& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()); + +/** +@brief Remove duplicate floating-point vector data from a STL vector in-place +@param[in,out] data Data array, duplicate items will be cut away with order + preserved and the size shrunk to just the unique prefix +@param[in] epsilon Epsilon value, vertices closer than this distance will be + melt together +@return Resulting index array -@snippet MagnumMeshTools.cpp removeDuplicates1 +Similar to the above, except that it's operating on a @ref std::vector, which +gets shrunk as a result (instead of the prefix size being returned). This +variant is useful together with @ref combineIndexedArrays() to remove +duplicates in multiple incidental arrays --- first remove duplicates in each +array separately and then combine the resulting index arrays to single index +array, and reorder the data accordingly: -Removing duplicates in multiple indcidental arrays is also possible --- first -remove duplicates in each array separately and then use @ref combineIndexedArrays() -to combine the resulting index arrays to single index array, and reorder the -data accordingly: +@snippet MagnumMeshTools.cpp removeDuplicates-multiple +*/ +template std::vector removeDuplicates(std::vector& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()); -@snippet MagnumMeshTools.cpp removeDuplicates2 +/** +@brief Remove duplicates from indexed floating-point vector data in-place +@param[in,out] indices Index array, which will get remapped to list just + unique vertices +@param[in,out] data Data array, duplicate items will be cut away with order + preserved +@param[in] epsilon Epsilon value, vertices closer than this distance will + be melt together +@return Size of unique prefix in the cleaned up @p data array +@m_since_latest + +Compared to @ref removeDuplicatesInPlace() this variant is more suited for data +that is already indexed as it works on the existing index array instead of +allocating a new one. */ -template std::vector removeDuplicates(std::vector& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()) { +template std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()) { + /* Somehow ~IndexType{} doesn't work for < 4byte types, as the result is + int(-1) instead of the type I want */ + CORRADE_ASSERT(data.size() <= IndexType(-1), "MeshTools::removeDuplicatesIndexedInPlace(): a" << sizeof(IndexType) << Debug::nospace << "-byte index type is too small for" << data.size() << "vertices", {}); + /* Get bounds. When NaNs appear, those will get collapsed together when you're lucky, or cause the whole data to disappear when you're not -- it needs a much more specialized handling to be robust. */ - std::pair minmax = Math::minmax(data); + std::pair minmax = Math::minmax(data); /* Make epsilon so large that std::size_t can index all vectors inside the bounds. */ epsilon = Math::max(epsilon, typename Vector::Type((minmax.second-minmax.first).max()/~std::size_t{})); - /* Resulting index array. Because we'll be remapping these, we need to - start from a 0..n sequence. */ - std::vector indices(data.size()); - std::iota(indices.begin(), indices.end(), 0); - /* Table containing original vector index for each discretized vector. Reserving more buckets than necessary (i.e. as if each vector was unique). */ - std::unordered_map, UnsignedInt, Implementation::VectorHash> table(data.size()); + std::size_t dataSize = data.size(); + std::unordered_map, UnsignedInt, Implementation::VectorHash> table(dataSize); /* Index array that'll be filled in each pass and then used for remapping the `indices` */ - std::vector remapping(data.size()); + Containers::Array remapping{Containers::NoInit, dataSize}; /* First go with original coordinates, then move them by epsilon/2 in each direction. */ Vector moved; for(std::size_t moving = 0; moving <= Vector::Size; ++moving) { - /* Clear the table for this pass */ - table.clear(); - /* Go through all vectors */ - for(std::size_t i = 0; i != data.size(); ++i) { + for(std::size_t i = 0; i != dataSize; ++i) { /* Try to insert new vertex into the table */ const Math::Vector v{(data[i] + moved - minmax.first)/epsilon}; const auto result = table.emplace(v, table.size()); - /* Add the (either new or already existing) index into index array */ + /* Add the (either new or already existing) index into the array */ remapping[i] = result.first->second; /* If this is a new combination, copy the data to new (earlier) @@ -121,13 +150,9 @@ template std::vector removeDuplicates(std::vector= table.size()); - data.resize(table.size()); - /* Remap the resulting index array */ for(auto& i: indices) i = remapping[i]; @@ -137,8 +162,31 @@ template std::vector removeDuplicates(std::vector= dataSize); + return dataSize; +} + +template std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView1D& data, typename Vector::Type epsilon) { + /* A trivial index array that'll be remapped and returned after */ + Containers::Array indices{Containers::NoInit, data.size()}; + std::iota(indices.begin(), indices.end(), 0); + const std::size_t size = removeDuplicatesIndexedInPlace(Containers::stridedArrayView(indices), data, epsilon); + return {std::move(indices), size}; +} + +template std::vector removeDuplicates(std::vector& data, typename Vector::Type epsilon) { + /* A trivial index array that'll be remapped and returned after */ + std::vector indices(data.size()); + std::iota(indices.begin(), indices.end(), 0); + const std::size_t size = removeDuplicatesIndexedInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(data), epsilon); + data.resize(size); return indices; } diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index 10e54e1144..e03c7c8934 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -40,6 +40,7 @@ set_property(TARGET MeshToolsCombineIndexedArraysTest MeshToolsDuplicateTest MeshToolsInterleaveTest + MeshToolsRemoveDuplicatesTest MeshToolsSubdivideTest APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") diff --git a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp index e10373f8eb..b349addd10 100644 --- a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp +++ b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp @@ -23,7 +23,10 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include +#include #include "Magnum/Math/Vector2.h" #include "Magnum/MeshTools/RemoveDuplicates.h" @@ -33,17 +36,47 @@ namespace Magnum { namespace MeshTools { namespace Test { namespace { struct RemoveDuplicatesTest: TestSuite::Tester { explicit RemoveDuplicatesTest(); - void removeDuplicates(); + void removeDuplicatesInPlace(); + void removeDuplicatesStl(); + template void removeDuplicatesIndexedInPlace(); + void removeDuplicatesIndexedInPlaceSmallType(); + void removeDuplicatesIndexedInPlaceEmptyIndices(); + void removeDuplicatesIndexedInPlaceEmptyIndicesVertices(); }; RemoveDuplicatesTest::RemoveDuplicatesTest() { - addTests({&RemoveDuplicatesTest::removeDuplicates}); + addTests({&RemoveDuplicatesTest::removeDuplicatesInPlace, + &RemoveDuplicatesTest::removeDuplicatesStl, + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceSmallType, + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndices, + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndicesVertices}); } -void RemoveDuplicatesTest::removeDuplicates() { +void RemoveDuplicatesTest::removeDuplicatesInPlace() { /* Numbers with distance 1 should be merged, numbers with distance 2 should be kept. Testing both even-odd and odd-even sequence to verify that half-epsilon translations are applied properly. */ + Vector2i data[]{ + {1, 0}, + {2, 1}, + {0, 4}, + {1, 5} + }; + + std::pair, std::size_t> result = MeshTools::removeDuplicatesInPlace(Containers::stridedArrayView(data), 2); + CORRADE_COMPARE_AS(Containers::arrayView(result.first), + Containers::arrayView({0, 0, 1, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(result.second), + Containers::arrayView({{1, 0}, {0, 4}}), + TestSuite::Compare::Container); +} + +void RemoveDuplicatesTest::removeDuplicatesStl() { + /* Same but with implicit bloat. HEH HEH */ std::vector data{ {1, 0}, {2, 1}, @@ -52,11 +85,67 @@ void RemoveDuplicatesTest::removeDuplicates() { }; const std::vector indices = MeshTools::removeDuplicates(data, 2); - CORRADE_COMPARE(indices, (std::vector{0, 0, 1, 1})); - CORRADE_COMPARE(data, (std::vector{ + CORRADE_COMPARE_AS(indices, + (std::vector{0, 0, 1, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data, + (std::vector{{1, 0}, {0, 4}}), + TestSuite::Compare::Container); +} + +template void RemoveDuplicatesTest::removeDuplicatesIndexedInPlace() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + /* Same as above, but with an explicit index buffer */ + T indices[]{3, 2, 0, 1, 2, 3}; + Vector2i data[]{ {1, 0}, - {0, 4} - })); + {2, 1}, + {0, 4}, + {1, 5} + }; + + std::size_t count = MeshTools::removeDuplicatesIndexedInPlace( + Containers::stridedArrayView(indices), + Containers::stridedArrayView(data), 2); + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({1, 1, 0, 0, 1, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count), + Containers::arrayView({{1, 0}, {0, 4}}), + TestSuite::Compare::Container); +} + +void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceSmallType() { + std::stringstream out; + Error redirectError{&out}; + + UnsignedByte indices[1]; + Vector2i data[256]{}; + MeshTools::removeDuplicatesIndexedInPlace( + Containers::stridedArrayView(indices), + Containers::stridedArrayView(data), 2); + CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesIndexedInPlace(): a 1-byte index type is too small for 256 vertices\n"); +} + +void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndices() { + Vector2i data[]{ + {1, 0}, + {2, 1}, + {0, 4}, + {1, 5} + }; + + std::size_t count = MeshTools::removeDuplicatesIndexedInPlace( + Containers::StridedArrayView1D{}, + Containers::stridedArrayView(data), 2); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count), + Containers::arrayView({{1, 0}, {0, 4}}), + TestSuite::Compare::Container); +} + +void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndicesVertices() { + CORRADE_COMPARE((MeshTools::removeDuplicatesIndexedInPlace({}, {}, 2)), 0); } }}}} From 1c74a87f2478fab9f920fc8b2878c26464178983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 9 Jan 2020 11:32:01 +0100 Subject: [PATCH 003/107] Primitives: use a more efficient duplicate removal approach in Icosphere. It's still extremely bad, but at least something. --- src/Magnum/Primitives/Icosphere.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Magnum/Primitives/Icosphere.cpp b/src/Magnum/Primitives/Icosphere.cpp index f43ab09d41..80ee71b661 100644 --- a/src/Magnum/Primitives/Icosphere.cpp +++ b/src/Magnum/Primitives/Icosphere.cpp @@ -27,7 +27,6 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Subdivide.h" #include "Magnum/Trade/MeshData3D.h" @@ -78,7 +77,7 @@ Trade::MeshData3D icosphereSolid(const UnsignedInt subdivisions) { return (a+b).normalized(); }); - indices = MeshTools::duplicate(indices, MeshTools::removeDuplicates(positions)); + positions.resize(MeshTools::removeDuplicatesIndexedInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(positions))); std::vector normals(positions); return Trade::MeshData3D{MeshPrimitive::Triangles, std::move(indices), {std::move(positions)}, {std::move(normals)}, {}, {}, nullptr}; From a1924f55c43c5a8cac66e0de4a880277ed6de067 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 4 Jan 2020 23:10:01 +0100 Subject: [PATCH 004/107] MeshTools: subdivideInPlace() accepts strided indices of any type. Also vastly improved tests and docs. --- src/Magnum/MeshTools/Subdivide.h | 55 +++++++++++---- src/Magnum/MeshTools/Test/SubdivideTest.cpp | 77 +++++++++++++++++---- 2 files changed, 104 insertions(+), 28 deletions(-) diff --git a/src/Magnum/MeshTools/Subdivide.h b/src/Magnum/MeshTools/Subdivide.h index 41b7e42d86..1796c16f4d 100644 --- a/src/Magnum/MeshTools/Subdivide.h +++ b/src/Magnum/MeshTools/Subdivide.h @@ -31,12 +31,15 @@ #include #include +#include #include +#include "Magnum/Magnum.h" + namespace Magnum { namespace MeshTools { #ifndef DOXYGEN_GENERATING_OUTPUT -template void subdivideInPlace(Containers::ArrayView indices, Containers::ArrayView vertices, Interpolator interpolator); +template void subdivideInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& vertices, Interpolator interpolator); #endif /** @@ -50,14 +53,14 @@ template void subdivideInPlace(Containers::Arr Goes through all triangle faces and subdivides them into four new. Removing duplicate vertices in the mesh is up to the user. -@see @ref subdivideInPlace() +@see @ref subdivideInPlace(), @ref removeDuplicatesInPlace() */ template void subdivide(std::vector& indices, std::vector& vertices, Interpolator interpolator) { - CORRADE_ASSERT(!(indices.size()%3), "MeshTools::subdivide(): index count is not divisible by 3!", ); + CORRADE_ASSERT(!(indices.size()%3), "MeshTools::subdivide(): index count is not divisible by 3", ); vertices.resize(vertices.size() + indices.size()); indices.resize(indices.size()*4); - subdivideInPlace(Containers::arrayView(indices), Containers::arrayView(vertices), interpolator); + subdivideInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(vertices), interpolator); } /** @@ -68,16 +71,32 @@ template void subdivide(std::vector void subdivideInPlace(Containers::ArrayView indices, Containers::ArrayView vertices, Interpolator interpolator) { - CORRADE_ASSERT(!(indices.size()%12), "MeshTools::subdivideInto(): can't divide" << indices.size() << "indices to four parts with each having triangle faces", ); +template void subdivideInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& vertices, Interpolator interpolator) { + CORRADE_ASSERT(!(indices.size()%12), "MeshTools::subdivideInPlace(): can't divide" << indices.size() << "indices to four parts with each having triangle faces", ); + /* Somehow ~IndexType{} doesn't work for < 4byte types, as the result is + int(-1) instead of the type I want */ + CORRADE_ASSERT(vertices.size() <= IndexType(-1), "MeshTools::subdivideInPlace(): a" << sizeof(IndexType) << Debug::nospace << "-byte index type is too small for" << vertices.size() << "vertices", ); /* Subdivide each face to four new */ const std::size_t indexCount = indices.size()/4; @@ -85,7 +104,7 @@ template void subdivideInPlace(Containers::Arr std::size_t vertexOffset = vertices.size() - indexCount; for(std::size_t i = 0; i != indexCount; i += 3) { /* Interpolate each side */ - UnsignedInt newVertices[3]; + IndexType newVertices[3]; for(int j = 0; j != 3; ++j) { newVertices[j] = vertexOffset; vertices[vertexOffset++] = interpolator(vertices[indices[i+j]], vertices[indices[i+(j+1)%3]]); @@ -120,6 +139,14 @@ template void subdivideInPlace(Containers::Arr } } +/** + * @overload + * @m_since_latest + */ +template void subdivideInPlace(const Containers::ArrayView& indices, const Containers::StridedArrayView1D& vertices, Interpolator interpolator) { + subdivideInPlace(Containers::stridedArrayView(indices), vertices, interpolator); +} + }} #endif diff --git a/src/Magnum/MeshTools/Test/SubdivideTest.cpp b/src/Magnum/MeshTools/Test/SubdivideTest.cpp index 34665591f7..9617af2218 100644 --- a/src/Magnum/MeshTools/Test/SubdivideTest.cpp +++ b/src/Magnum/MeshTools/Test/SubdivideTest.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include "Magnum/MeshTools/RemoveDuplicates.h" @@ -35,8 +36,11 @@ namespace Magnum { namespace MeshTools { namespace Test { namespace { struct SubdivideTest: TestSuite::Tester { explicit SubdivideTest(); - void wrongIndexCount(); void subdivide(); + void subdivideWrongIndexCount(); + template void subdivideInPlace(); + void subdivideInPlaceWrongIndexCount(); + void subdivideInPlaceSmallIndexType(); }; typedef Math::Vector<1, Int> Vector1; @@ -44,29 +48,74 @@ typedef Math::Vector<1, Int> Vector1; inline Vector1 interpolator(Vector1 a, Vector1 b) { return (a[0]+b[0])/2; } SubdivideTest::SubdivideTest() { - addTests({&SubdivideTest::wrongIndexCount, - &SubdivideTest::subdivide}); + addTests({&SubdivideTest::subdivide, + &SubdivideTest::subdivideWrongIndexCount, + &SubdivideTest::subdivideInPlace, + &SubdivideTest::subdivideInPlace, + &SubdivideTest::subdivideInPlace, + &SubdivideTest::subdivideInPlaceWrongIndexCount, + &SubdivideTest::subdivideInPlaceSmallIndexType}); } -void SubdivideTest::wrongIndexCount() { - std::stringstream ss; - Error redirectError{&ss}; +void SubdivideTest::subdivide() { + std::vector positions{0, 2, 6, 8}; + std::vector indices{0, 1, 2, 1, 2, 3}; + MeshTools::subdivide(indices, positions, interpolator); + + CORRADE_COMPARE_AS(indices, + (std::vector{4, 5, 6, 7, 8, 9, 0, 4, 6, 4, 1, 5, 6, 5, 2, 1, 7, 9, 7, 2, 8, 9, 8, 3}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(positions, + (std::vector{0, 2, 6, 8, 1, 4, 3, 4, 7, 5}), + TestSuite::Compare::Container); +} + +void SubdivideTest::subdivideWrongIndexCount() { + std::stringstream out; + Error redirectError{&out}; std::vector positions; std::vector indices{0, 1}; MeshTools::subdivide(indices, positions, interpolator); - CORRADE_COMPARE(ss.str(), "MeshTools::subdivide(): index count is not divisible by 3!\n"); + CORRADE_COMPARE(out.str(), "MeshTools::subdivide(): index count is not divisible by 3\n"); } -void SubdivideTest::subdivide() { - std::vector positions{0, 2, 6, 8}; - std::vector indices{0, 1, 2, 1, 2, 3}; - MeshTools::subdivide(indices, positions, interpolator); +template void SubdivideTest::subdivideInPlace() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[6*4]{0, 1, 2, 1, 2, 3, /* and 18 more */}; + Vector1 positions[4 + 6]{0, 2, 6, 8, /* and 6 more */}; + MeshTools::subdivideInPlace(Containers::stridedArrayView(indices), + Containers::stridedArrayView(positions), interpolator); + + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({4, 5, 6, 7, 8, 9, 0, 4, 6, 4, 1, 5, 6, 5, 2, 1, 7, 9, 7, 2, 8, 9, 8, 3}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(positions), + Containers::arrayView({0, 2, 6, 8, 1, 4, 3, 4, 7, 5}), + TestSuite::Compare::Container); +} + +void SubdivideTest::subdivideInPlaceWrongIndexCount() { + std::stringstream out; + Error redirectError{&out}; + + UnsignedInt indices[6*4 + 1]{0, 1, 2, 1, 2, 3, /* and 18+1 more */}; + Vector1 positions[]{0}; + MeshTools::subdivideInPlace(Containers::stridedArrayView(indices), + Containers::stridedArrayView(positions), interpolator); + CORRADE_COMPARE(out.str(), "MeshTools::subdivideInPlace(): can't divide 25 indices to four parts with each having triangle faces\n"); +} - CORRADE_COMPARE(indices.size(), 24); +void SubdivideTest::subdivideInPlaceSmallIndexType() { + std::stringstream out; + Error redirectError{&out}; - CORRADE_VERIFY(positions == (std::vector{0, 2, 6, 8, 1, 4, 3, 4, 7, 5})); - CORRADE_COMPARE(indices, (std::vector{4, 5, 6, 7, 8, 9, 0, 4, 6, 4, 1, 5, 6, 5, 2, 1, 7, 9, 7, 2, 8, 9, 8, 3})); + UnsignedByte indices[6*4]{0, 1, 2, 1, 2, 3, /* and 18 more */}; + Vector1 positions[256]{}; + MeshTools::subdivideInPlace(Containers::stridedArrayView(indices), + Containers::stridedArrayView(positions), interpolator); + CORRADE_COMPARE(out.str(), "MeshTools::subdivideInPlace(): a 1-byte index type is too small for 256 vertices\n"); } }}}} From 677b24a8077e3e88d709e1ba14caf1037c3214c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 27 Feb 2020 00:46:45 +0100 Subject: [PATCH 005/107] MeshTools: doc++ --- src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp | 2 ++ src/Magnum/MeshTools/Test/SubdivideTest.cpp | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp index b349addd10..3c932751e1 100644 --- a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp +++ b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp @@ -42,6 +42,8 @@ struct RemoveDuplicatesTest: TestSuite::Tester { void removeDuplicatesIndexedInPlaceSmallType(); void removeDuplicatesIndexedInPlaceEmptyIndices(); void removeDuplicatesIndexedInPlaceEmptyIndicesVertices(); + + /* this is additionally regression-tested in PrimitivesIcosphereTest */ }; RemoveDuplicatesTest::RemoveDuplicatesTest() { diff --git a/src/Magnum/MeshTools/Test/SubdivideTest.cpp b/src/Magnum/MeshTools/Test/SubdivideTest.cpp index 9617af2218..390b898f40 100644 --- a/src/Magnum/MeshTools/Test/SubdivideTest.cpp +++ b/src/Magnum/MeshTools/Test/SubdivideTest.cpp @@ -41,6 +41,8 @@ struct SubdivideTest: TestSuite::Tester { template void subdivideInPlace(); void subdivideInPlaceWrongIndexCount(); void subdivideInPlaceSmallIndexType(); + + /* this is additionally regression-tested in PrimitivesIcosphereTest */ }; typedef Math::Vector<1, Int> Vector1; From 847e3c8e49b730f85463146fc9e5a1c5819781b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 5 Jan 2020 18:30:45 +0100 Subject: [PATCH 006/107] MeshTools: group similar functions together. --- src/Magnum/MeshTools/Duplicate.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/Magnum/MeshTools/Duplicate.h b/src/Magnum/MeshTools/Duplicate.h index 542e11dc75..78427fee28 100644 --- a/src/Magnum/MeshTools/Duplicate.h +++ b/src/Magnum/MeshTools/Duplicate.h @@ -62,6 +62,18 @@ template Containers::Array duplicate(const Containe return out; } +/** +@brief Duplicate data using given index array + +Like @ref duplicate(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&), +but putting the result into a @ref std::vector. +*/ +template std::vector duplicate(const std::vector& indices, const std::vector& data) { + std::vector out(indices.size()); + duplicateInto(indices, data, out); + return out; +} + /** @brief Duplicate data using an index array into given output array @param[in] indices Index array to use @@ -82,18 +94,6 @@ template void duplicateInto(const Containers::StridedA } } -/** -@brief Duplicate data using given index array - -Like @ref duplicate(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&), -but putting the result into a @ref std::vector. -*/ -template std::vector duplicate(const std::vector& indices, const std::vector& data) { - std::vector out(indices.size()); - duplicateInto(indices, data, out); - return out; -} - }} #endif From 2f15f95ab5e0992f64f5cd9e6d250a6c12a8c2be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 8 Jan 2020 13:21:38 +0100 Subject: [PATCH 007/107] MeshTools: this is also new since last release. --- src/Magnum/MeshTools/Duplicate.h | 1 + src/Magnum/MeshTools/GenerateNormals.h | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/Magnum/MeshTools/Duplicate.h b/src/Magnum/MeshTools/Duplicate.h index 78427fee28..1e05c191dd 100644 --- a/src/Magnum/MeshTools/Duplicate.h +++ b/src/Magnum/MeshTools/Duplicate.h @@ -46,6 +46,7 @@ template void duplicateInto(const Containers::StridedA /** @brief Duplicate data using given index array +@m_since{2019,10} Converts indexed array to non-indexed, for example data `{a, b, c, d}` with index array `{1, 1, 0, 3, 2, 2}` will be converted to `{b, b, a, d, c, c}`. diff --git a/src/Magnum/MeshTools/GenerateNormals.h b/src/Magnum/MeshTools/GenerateNormals.h index b515d4f95f..026ddabdc0 100644 --- a/src/Magnum/MeshTools/GenerateNormals.h +++ b/src/Magnum/MeshTools/GenerateNormals.h @@ -43,6 +43,7 @@ namespace Magnum { namespace MeshTools { @brief Generate flat normals @param positions Triangle vertex positions @return Per-vertex normals +@m_since{2019,10} All vertices in each triangle face get the same normal vector. Expects that the position count is divisible by 3. If you need to generate flat normals for an @@ -130,6 +131,7 @@ extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoot @param[in] indices Triangle face indices @param[in] positions Triangle vertex positions @param[out] normals Where to put the generated normals +@m_since{2019,10} A variant of @ref generateSmoothNormals() that fills existing memory instead of allocating a new array. The @p normals array is expected to have the same size From 29f0fdb188f5ad27005c488d6a1f07288b1f87cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 8 Jan 2020 13:47:48 +0100 Subject: [PATCH 008/107] MeshTools: explicit generateSmoothNormals() overloads for each index type. The templated version had the unfortunate "feature" of not being able to figure out the type when an array view or a C array got passed to it. That led to worse-than-ideal UX and even though it's now a bit more verbose on the implementation side, it's the preferred solution. --- doc/snippets/MagnumMeshTools-stl.cpp | 2 +- src/Magnum/MeshTools/Compile.cpp | 2 +- src/Magnum/MeshTools/GenerateNormals.cpp | 46 ++++++++++++++----- src/Magnum/MeshTools/GenerateNormals.h | 36 ++++++++++----- .../MeshTools/Test/GenerateNormalsTest.cpp | 33 +++++++------ 5 files changed, 76 insertions(+), 43 deletions(-) diff --git a/doc/snippets/MagnumMeshTools-stl.cpp b/doc/snippets/MagnumMeshTools-stl.cpp index 82bbf5a359..dc01571740 100644 --- a/doc/snippets/MagnumMeshTools-stl.cpp +++ b/doc/snippets/MagnumMeshTools-stl.cpp @@ -58,7 +58,7 @@ std::vector indices; std::vector positions; std::vector normals{positions.size()}; -MeshTools::generateSmoothNormalsInto(indices, positions, normals); +MeshTools::generateSmoothNormalsInto(indices, positions, normals); /* [generateSmoothNormalsInto] */ } diff --git a/src/Magnum/MeshTools/Compile.cpp b/src/Magnum/MeshTools/Compile.cpp index c4c5d5b804..1cb807b96d 100644 --- a/src/Magnum/MeshTools/Compile.cpp +++ b/src/Magnum/MeshTools/Compile.cpp @@ -199,7 +199,7 @@ GL::Mesh compile(const Trade::MeshData3D& meshData, CompileFlags flags) { normalStorage = generateFlatNormals(positions); useIndices = false; } else { - normalStorage = generateSmoothNormals(meshData.indices(), positions); + normalStorage = generateSmoothNormals(meshData.indices(), positions); useIndices = true; } diff --git a/src/Magnum/MeshTools/GenerateNormals.cpp b/src/Magnum/MeshTools/GenerateNormals.cpp index b6f9142f1e..76d9ef7487 100644 --- a/src/Magnum/MeshTools/GenerateNormals.cpp +++ b/src/Magnum/MeshTools/GenerateNormals.cpp @@ -86,6 +86,8 @@ std::pair, std::vector> generateFlatNormals(co } #endif +namespace { + #if defined(CORRADE_MSVC2019_COMPATIBILITY) && !defined(CORRADE_MSVC2017_COMPATIBILITY) /* When using /permissive- with MSVC2019, using namespace inside the function below FOR SOME REASON gets lost when instantiating the template. That's @@ -94,7 +96,7 @@ std::pair, std::vector> generateFlatNormals(co using namespace Math::Literals; #endif -template void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals) { +template inline void generateSmoothNormalsIntoImplementation(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals) { CORRADE_ASSERT(indices.size() % 3 == 0, "MeshTools::generateSmoothNormalsInto(): index count not divisible by 3", ); CORRADE_ASSERT(normals.size() == positions.size(), @@ -217,22 +219,42 @@ template void generateSmoothNormalsInto(const Containers::StridedArrayV } } -#ifndef DOXYGEN_GENERATING_OUTPUT -template void generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -template void generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -template void generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -#endif +} + +/* If not done this way but with templates instead, C++ wouldn't be able to + figure out on its own which overload to use when indices are not already a + strided arrray view */ +void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals) { + generateSmoothNormalsIntoImplementation(indices, positions, normals); +} +void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals) { + generateSmoothNormalsIntoImplementation(indices, positions, normals); +} +void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals) { + generateSmoothNormalsIntoImplementation(indices, positions, normals); +} -template Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions) { +namespace { + +template inline Containers::Array generateSmoothNormalsImplementation(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions) { Containers::Array out{Containers::NoInit, positions.size()}; generateSmoothNormalsInto(indices, positions, out); return out; } -#ifndef DOXYGEN_GENERATING_OUTPUT -template Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -template Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -template Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -#endif +} + +/* If not done this way but with templates instead, C++ wouldn't be able to + figure out on its own which overload to use when indices are not already a + strided arrray view */ +Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions) { + return generateSmoothNormalsImplementation(indices, positions); +} +Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions) { + return generateSmoothNormalsImplementation(indices, positions); +} +Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions) { + return generateSmoothNormalsImplementation(indices, positions); +} }} diff --git a/src/Magnum/MeshTools/GenerateNormals.h b/src/Magnum/MeshTools/GenerateNormals.h index 026ddabdc0..fa1ccdffb6 100644 --- a/src/Magnum/MeshTools/GenerateNormals.h +++ b/src/Magnum/MeshTools/GenerateNormals.h @@ -118,13 +118,19 @@ Martijn Buijs. @see @ref generateSmoothNormalsInto(), @ref generateFlatNormals(), @ref MeshTools::CompileFlag::GenerateSmoothNormals */ -template MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions); +MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions); -#if defined(CORRADE_TARGET_WINDOWS) && !defined(__MINGW32__) -extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -#endif +/** + * @overload + * @m_since{2019,10} + */ +MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions); + +/** + * @overload + * @m_since{2019,10} + */ +MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions); /** @brief Generate smooth normals into an existing array @@ -147,13 +153,19 @@ case @cpp #include @ce @ref Corrade/Containers/ArrayViewStl.h to get implicit @see @ref generateFlatNormalsInto() */ -template MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals); +MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals); -#if defined(CORRADE_TARGET_WINDOWS) && !defined(__MINGW32__) -extern template MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -extern template MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -extern template MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&); -#endif +/** + * @overload + * @m_since{2019,10} + */ +MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals); + +/** + * @overload + * @m_since{2019,10} + */ +MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals); }} diff --git a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp index 872e532e1c..22e36c3bbc 100644 --- a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp +++ b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp @@ -51,7 +51,7 @@ struct GenerateNormalsTest: TestSuite::Tester { void flatWrongCount(); void flatIntoWrongSize(); - void smoothTwoTriangles(); + template void smoothTwoTriangles(); void smoothCube(); void smoothBeveledCube(); void smoothCylinder(); @@ -72,7 +72,9 @@ GenerateNormalsTest::GenerateNormalsTest() { &GenerateNormalsTest::flatWrongCount, &GenerateNormalsTest::flatIntoWrongSize, - &GenerateNormalsTest::smoothTwoTriangles, + &GenerateNormalsTest::smoothTwoTriangles, + &GenerateNormalsTest::smoothTwoTriangles, + &GenerateNormalsTest::smoothTwoTriangles, &GenerateNormalsTest::smoothCube, &GenerateNormalsTest::smoothBeveledCube, &GenerateNormalsTest::smoothCylinder, @@ -155,12 +157,14 @@ void GenerateNormalsTest::flatIntoWrongSize() { CORRADE_COMPARE(out.str(), "MeshTools::generateFlatNormalsInto(): bad output size, expected 6 but got 7\n"); } -void GenerateNormalsTest::smoothTwoTriangles() { - const UnsignedInt indices[]{0, 1, 2, 3, 4, 5}; +template void GenerateNormalsTest::smoothTwoTriangles() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + const T indices[]{0, 1, 2, 3, 4, 5}; /* Should generate the same output as flat normals */ CORRADE_COMPARE_AS( - generateSmoothNormals(Containers::stridedArrayView(indices), TwoTriangles), + generateSmoothNormals(indices, TwoTriangles), (Containers::Array{Containers::InPlaceInit, { Vector3::zAxis(), Vector3::zAxis(), @@ -194,7 +198,7 @@ void GenerateNormalsTest::smoothCube() { /* Normals should be the same as positions, only normalized */ CORRADE_COMPARE_AS( - generateSmoothNormals(Containers::stridedArrayView(indices), positions), + generateSmoothNormals(indices, positions), (Containers::Array{Containers::InPlaceInit, { positions[0]/Constants::sqrt3(), positions[1]/Constants::sqrt3(), @@ -207,7 +211,6 @@ void GenerateNormalsTest::smoothCube() { }}), TestSuite::Compare::Container); } - constexpr Vector3 BeveledCubePositions[] { {-1.0f, -0.6f, 1.1f}, { 1.0f, -0.6f, 1.1f}, @@ -284,7 +287,7 @@ void GenerateNormalsTest::smoothBeveledCube() { Vector3 x{0.996072f, 0.0754969f, 0.0462723f}; Vector3 y{0.0467958f, 0.997808f, 0.0467958f}; CORRADE_COMPARE_AS(generateSmoothNormals( - Containers::stridedArrayView(BeveledCubeIndices), BeveledCubePositions), + BeveledCubeIndices, BeveledCubePositions), (Containers::Array{Containers::InPlaceInit, { z*Math::sign(BeveledCubePositions[ 0]), z*Math::sign(BeveledCubePositions[ 1]), @@ -341,8 +344,7 @@ void GenerateNormalsTest::smoothZeroAreaTriangle() { 0, 1, 2, 1, 2, 1 }; - CORRADE_COMPARE_AS(generateSmoothNormals( - Containers::stridedArrayView(indices), positions), + CORRADE_COMPARE_AS(generateSmoothNormals(indices, positions), (Containers::Array{Containers::InPlaceInit, { Vector3::zAxis(), Vector3::zAxis(), @@ -364,8 +366,7 @@ void GenerateNormalsTest::smoothNanPosition() { 0, 1, 2, 1, 2, 1 }; - Containers::Array generated = generateSmoothNormals( - Containers::stridedArrayView(indices), positions); + Containers::Array generated = generateSmoothNormals(indices, positions); CORRADE_COMPARE_AS(generated.prefix(3), (Containers::Array{Containers::InPlaceInit, { Vector3::zAxis(), @@ -381,7 +382,7 @@ void GenerateNormalsTest::smoothWrongCount() { const UnsignedByte indices[7]{}; const Vector3 positions[1]; - generateSmoothNormals(Containers::stridedArrayView(indices), positions); + generateSmoothNormals(indices, positions); CORRADE_COMPARE(out.str(), "MeshTools::generateSmoothNormalsInto(): index count not divisible by 3\n"); } @@ -392,7 +393,7 @@ void GenerateNormalsTest::smoothIntoWrongSize() { const UnsignedByte indices[6]{}; const Vector3 positions[3]; Vector3 normals[4]; - generateSmoothNormalsInto(Containers::stridedArrayView(indices), positions, normals); + generateSmoothNormalsInto(indices, positions, normals); CORRADE_COMPARE(out.str(), "MeshTools::generateSmoothNormalsInto(): bad output size, expected 3 but got 4\n"); } @@ -412,9 +413,7 @@ void GenerateNormalsTest::benchmarkFlat() { void GenerateNormalsTest::benchmarkSmooth() { Containers::Array normals{Containers::NoInit, Containers::arraySize(BeveledCubePositions)}; CORRADE_BENCHMARK(10) { - generateSmoothNormalsInto( - Containers::stridedArrayView(BeveledCubeIndices), - BeveledCubePositions, normals); + generateSmoothNormalsInto(BeveledCubeIndices, BeveledCubePositions, normals); } CORRADE_COMPARE(Math::min(normals), (Vector3{-0.996072f, -0.997808f, -0.996072f})); From 735c59e96b97572b23ed0ab65597c6233e836741 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 8 Jan 2020 17:01:55 +0100 Subject: [PATCH 009/107] MeshTools: added a duplicateInto() variant taking a 2D strided array view. This also allowed me to move all the complexity and assertions into a cpp file, no longer polluting the header. --- doc/changelog.dox | 2 + src/Magnum/MeshTools/CMakeLists.txt | 1 + src/Magnum/MeshTools/Duplicate.cpp | 64 +++++++++++++++++++++ src/Magnum/MeshTools/Duplicate.h | 44 ++++++++++---- src/Magnum/MeshTools/Test/CMakeLists.txt | 4 +- src/Magnum/MeshTools/Test/DuplicateTest.cpp | 63 +++++++++++++++++++- 6 files changed, 164 insertions(+), 14 deletions(-) create mode 100644 src/Magnum/MeshTools/Duplicate.cpp diff --git a/doc/changelog.dox b/doc/changelog.dox index a206e7593e..4104e348ef 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -210,6 +210,8 @@ See also: - Added @ref MeshTools::removeDuplicatesIndexedInPlace() that operates in-place on an indexed array view and a STL-less @ref MeshTools::removeDuplicatesInPlace() variant +- Added @ref MeshTools::duplicateInto() variants that take type-erased + 2D strided array views @subsubsection changelog-latest-changes-platform Platform libraries diff --git a/src/Magnum/MeshTools/CMakeLists.txt b/src/Magnum/MeshTools/CMakeLists.txt index 7c1c65d7dc..59ac7bde86 100644 --- a/src/Magnum/MeshTools/CMakeLists.txt +++ b/src/Magnum/MeshTools/CMakeLists.txt @@ -31,6 +31,7 @@ set(MagnumMeshTools_SRCS set(MagnumMeshTools_GracefulAssert_SRCS CombineIndexedArrays.cpp CompressIndices.cpp + Duplicate.cpp FlipNormals.cpp GenerateNormals.cpp) diff --git a/src/Magnum/MeshTools/Duplicate.cpp b/src/Magnum/MeshTools/Duplicate.cpp new file mode 100644 index 0000000000..8d2d076b11 --- /dev/null +++ b/src/Magnum/MeshTools/Duplicate.cpp @@ -0,0 +1,64 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Duplicate.h" + +#include + +namespace Magnum { namespace MeshTools { + +namespace { + +template inline void duplicateIntoImplementation(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out) { + CORRADE_ASSERT(out.size()[0] == indices.size(), + "MeshTools::duplicateInto(): index array and output size don't match, expected" << indices.size() << "but got" << out.size()[0], ); + CORRADE_ASSERT(data.isContiguous<1>() && out.isContiguous<1>(), + "MeshTools::duplicateInto(): second view dimension is not contiguous", ); + CORRADE_ASSERT(data.size()[1] == out.size()[1], + "MeshTools::duplicateInto(): input and output type size doesn't match, expected" << data.size()[1] << "but got" << out.size()[1], ); + const std::size_t size = data.size()[1]; + for(std::size_t i = 0; i != indices.size(); ++i) { + const std::size_t index = indices[i]; + CORRADE_ASSERT(index < data.size()[0], "MeshTools::duplicateInto(): index" << index << "out of bounds for" << data.size()[0] << "elements", ); + std::memcpy(out[i].data(), data[index].data(), size); + } +} + +} + +/* If not done this way but with templates instead, C++ wouldn't be able to + figure out on its own which overload to use when indices are not already a + strided arrray view */ +void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out) { + duplicateIntoImplementation(indices, data, out); +} +void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out) { + duplicateIntoImplementation(indices, data, out); +} +void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out) { + duplicateIntoImplementation(indices, data, out); +} + +}} diff --git a/src/Magnum/MeshTools/Duplicate.h b/src/Magnum/MeshTools/Duplicate.h index 1e05c191dd..94d736aa0b 100644 --- a/src/Magnum/MeshTools/Duplicate.h +++ b/src/Magnum/MeshTools/Duplicate.h @@ -33,9 +33,9 @@ #include #include #include -#include #include "Magnum/Magnum.h" +#include "Magnum/MeshTools/visibility.h" namespace Magnum { namespace MeshTools { @@ -83,16 +83,40 @@ template std::vector duplicate(const std::vector& indic @m_since{2019,10} A variant of @ref duplicate() that fills existing memory instead of allocating -a new array. +a new array. Expects that @p out has the same size as @p indices and all +indices are in range for the @p data array. +*/ +template void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& data, const Containers::StridedArrayView1D& out); + +/** +@brief Duplicate type-erased data using an index array into given output array +@param[in] indices Index array to use +@param[in] data Input data +@param[out] out Where to store the output +@m_since_latest + +Compared to @ref duplicateInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) +accepts a 2D view, where the second dimension spans the actual type. Expects +that @p out has the same size as @p indices and all indices are in range for +the @p data array, and that the second dimension of both @p data and @p out +is contiguous and has the same size. */ -template void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& data, const Containers::StridedArrayView1D& out) { - CORRADE_ASSERT(out.size() == indices.size(), - "MeshTools::duplicateInto(): bad output size, expected" << indices.size() << "but got" << out.size(), ); - for(std::size_t i = 0; i != indices.size(); ++i) { - const std::size_t index = indices[i]; - CORRADE_ASSERT(index < data.size(), "MeshTools::duplicateInto(): index" << index << "out of bounds for" << data.size() << "elements", ); - out[i] = data[index]; - } +MAGNUM_MESHTOOLS_EXPORT void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out); + +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/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index e03c7c8934..0e2277a589 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -25,7 +25,7 @@ corrade_add_test(MeshToolsCombineIndexedArraysTest CombineIndexedArraysTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsCompressIndicesTest CompressIndicesTest.cpp LIBRARIES MagnumMeshToolsTestLib) -corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp LIBRARIES Magnum) +corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsFlipNormalsTest FlipNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsGenerateNormalsTest GenerateNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib MagnumPrimitives) corrade_add_test(MeshToolsInterleaveTest InterleaveTest.cpp LIBRARIES Magnum) @@ -33,7 +33,7 @@ corrade_add_test(MeshToolsRemoveDuplicatesTest RemoveDuplicatesTest.cpp LIBRARIE corrade_add_test(MeshToolsSubdivideTest SubdivideTest.cpp LIBRARIES Magnum) corrade_add_test(MeshToolsTipsifyTest TipsifyTest.cpp LIBRARIES MagnumMeshTools) corrade_add_test(MeshToolsTransformTest TransformTest.cpp LIBRARIES MagnumMeshTools) -corrade_add_test(MeshToolsSubdivideRemov___Benchmark SubdivideRemoveDuplicatesBenchmark.cpp LIBRARIES MagnumPrimitives) +corrade_add_test(MeshToolsSubdivideRemov___Benchmark SubdivideRemoveDuplicatesBenchmark.cpp LIBRARIES MagnumMeshTools MagnumPrimitives) # Graceful assert for testing set_property(TARGET diff --git a/src/Magnum/MeshTools/Test/DuplicateTest.cpp b/src/Magnum/MeshTools/Test/DuplicateTest.cpp index a4ccce2955..5f8e986181 100644 --- a/src/Magnum/MeshTools/Test/DuplicateTest.cpp +++ b/src/Magnum/MeshTools/Test/DuplicateTest.cpp @@ -29,6 +29,7 @@ #include #include "Magnum/Magnum.h" +#include "Magnum/Math/TypeTraits.h" #include "Magnum/MeshTools/Duplicate.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { @@ -42,6 +43,10 @@ struct DuplicateTest: TestSuite::Tester { void duplicateInto(); void duplicateIntoWrongSize(); + + template void duplicateIntoErased(); + void duplicateIntoErasedWrongTypeSize(); + void duplicateIntoErasedNonContiguous(); }; DuplicateTest::DuplicateTest() { @@ -50,7 +55,13 @@ DuplicateTest::DuplicateTest() { &DuplicateTest::duplicateStl, &DuplicateTest::duplicateInto, - &DuplicateTest::duplicateIntoWrongSize}); + &DuplicateTest::duplicateIntoWrongSize, + + &DuplicateTest::duplicateIntoErased, + &DuplicateTest::duplicateIntoErased, + &DuplicateTest::duplicateIntoErased, + &DuplicateTest::duplicateIntoErasedWrongTypeSize, + &DuplicateTest::duplicateIntoErasedNonContiguous}); } void DuplicateTest::duplicate() { @@ -102,7 +113,55 @@ void DuplicateTest::duplicateIntoWrongSize() { MeshTools::duplicateInto(indices, data, output); CORRADE_COMPARE(out.str(), - "MeshTools::duplicateInto(): bad output size, expected 6 but got 5\n"); + "MeshTools::duplicateInto(): index array and output size don't match, expected 6 but got 5\n"); +} + +template void DuplicateTest::duplicateIntoErased() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + constexpr T indices[]{1, 1, 0, 3, 2, 2}; + constexpr Int data[]{-7, 35, 12, -18}; + Int output[6]; + + MeshTools::duplicateInto( + Containers::stridedArrayView(indices), + Containers::arrayCast<2, const char>(Containers::stridedArrayView(data)), + Containers::arrayCast<2, char>(Containers::stridedArrayView(output))); + CORRADE_COMPARE_AS(Containers::arrayView(output), + Containers::arrayView({35, 35, -7, -18, 12, 12}), + TestSuite::Compare::Container); +} + +void DuplicateTest::duplicateIntoErasedWrongTypeSize() { + constexpr UnsignedByte indices[]{1, 1, 0, 3, 2, 2}; + constexpr Int data[]{-7, 35, 12, -18}; + Short output[6]; + + std::ostringstream out; + Error redirectError{&out}; + + MeshTools::duplicateInto( + Containers::stridedArrayView(indices), + Containers::arrayCast<2, const char>(Containers::stridedArrayView(data)), + Containers::arrayCast<2, char>(Containers::stridedArrayView(output))); + CORRADE_COMPARE(out.str(), + "MeshTools::duplicateInto(): input and output type size doesn't match, expected 4 but got 2\n"); +} + +void DuplicateTest::duplicateIntoErasedNonContiguous() { + constexpr UnsignedByte indices[]{1, 1, 0, 3, 2, 2}; + constexpr Int data[]{-7, 35, 12, -18}; + Short output[6]; + + std::ostringstream out; + Error redirectError{&out}; + + MeshTools::duplicateInto( + Containers::stridedArrayView(indices), + Containers::arrayCast<2, const char>(Containers::stridedArrayView(data)).every({1, 2}), + Containers::arrayCast<2, char>(Containers::stridedArrayView(output))); + CORRADE_COMPARE(out.str(), + "MeshTools::duplicateInto(): second view dimension is not contiguous\n"); } }}}} From 24d01e29c89107ea8d877da4ffba9a430fc3de79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 8 Jan 2020 17:27:37 +0100 Subject: [PATCH 010/107] MeshTools: update an assertion text, add a test for it. --- src/Magnum/MeshTools/GenerateNormals.cpp | 2 +- src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/Magnum/MeshTools/GenerateNormals.cpp b/src/Magnum/MeshTools/GenerateNormals.cpp index 76d9ef7487..de924b808c 100644 --- a/src/Magnum/MeshTools/GenerateNormals.cpp +++ b/src/Magnum/MeshTools/GenerateNormals.cpp @@ -111,7 +111,7 @@ template inline void generateSmoothNormalsIntoImplementation(const Cont Containers::arrayCast(normals); for(UnsignedInt& i: triangleCount) i = 0; for(const T index: indices) { - CORRADE_ASSERT(index < positions.size(), "MeshTools::generateSmoothNormals(): index" << index << "out of bounds for" << positions.size() << "elements", ); + CORRADE_ASSERT(index < positions.size(), "MeshTools::generateSmoothNormalsInto(): index" << index << "out of bounds for" << positions.size() << "elements", ); ++triangleCount[index]; } diff --git a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp index 22e36c3bbc..9149bb4260 100644 --- a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp +++ b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp @@ -58,6 +58,7 @@ struct GenerateNormalsTest: TestSuite::Tester { void smoothZeroAreaTriangle(); void smoothNanPosition(); void smoothWrongCount(); + void smoothOutOfBounds(); void smoothIntoWrongSize(); void benchmarkFlat(); @@ -81,6 +82,7 @@ GenerateNormalsTest::GenerateNormalsTest() { &GenerateNormalsTest::smoothZeroAreaTriangle, &GenerateNormalsTest::smoothNanPosition, &GenerateNormalsTest::smoothWrongCount, + &GenerateNormalsTest::smoothOutOfBounds, &GenerateNormalsTest::smoothIntoWrongSize}); addBenchmarks({&GenerateNormalsTest::benchmarkFlat, @@ -386,6 +388,16 @@ void GenerateNormalsTest::smoothWrongCount() { CORRADE_COMPARE(out.str(), "MeshTools::generateSmoothNormalsInto(): index count not divisible by 3\n"); } +void GenerateNormalsTest::smoothOutOfBounds() { + std::stringstream out; + Error redirectError{&out}; + + const Vector3 positions[2]; + const UnsignedInt indices[] { 0, 1, 2 }; + generateSmoothNormals(indices, positions); + CORRADE_COMPARE(out.str(), "MeshTools::generateSmoothNormalsInto(): index 2 out of bounds for 2 elements\n"); +} + void GenerateNormalsTest::smoothIntoWrongSize() { std::stringstream out; Error redirectError{&out}; From db502c1acf99f7e4793e793f6f49164848965a52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 8 Jan 2020 17:28:35 +0100 Subject: [PATCH 011/107] MeshTools: this test didn't test the NaNs at all. I wonder what was I doing back then. --- .../MeshTools/Test/GenerateNormalsTest.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp index 9149bb4260..a42f0e95d7 100644 --- a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp +++ b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp @@ -362,20 +362,17 @@ void GenerateNormalsTest::smoothNanPosition() { { 0.0f, Constants::nan(), 0.0f}, }; - /* Second triangle is just an edge, so it shouldn't contribute to the first - triangle normal */ + /* Second triangle will poison a part of the first with NaNs, but it won't + crash */ constexpr UnsignedInt indices[] { - 0, 1, 2, 1, 2, 1 + 0, 1, 2, 1, 2, 3 }; Containers::Array generated = generateSmoothNormals(indices, positions); - CORRADE_COMPARE_AS(generated.prefix(3), - (Containers::Array{Containers::InPlaceInit, { - Vector3::zAxis(), - Vector3::zAxis(), - Vector3::zAxis() - }}), TestSuite::Compare::Container>); - CORRADE_COMPARE(Math::isNan(generated[3]), BoolVector3{0x7}); + CORRADE_COMPARE(generated[0], Vector3::zAxis()); + CORRADE_VERIFY(Math::isNan(generated[1]).all()); + CORRADE_VERIFY(Math::isNan(generated[2]).all()); + CORRADE_VERIFY(Math::isNan(generated[3]).all()); } void GenerateNormalsTest::smoothWrongCount() { From 1684ccac2dc670ad90eff08c5e93f78370d1ac67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 8 Jan 2020 17:43:37 +0100 Subject: [PATCH 012/107] MeshTools: simplify test code a bit. We have new less shitty APIs, so use them! --- .../MeshTools/Test/CompressIndicesTest.cpp | 38 +++++-------------- src/Magnum/MeshTools/Test/DuplicateTest.cpp | 10 ++--- .../MeshTools/Test/GenerateNormalsTest.cpp | 25 ++++++------ 3 files changed, 25 insertions(+), 48 deletions(-) diff --git a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp index 309271083e..15889b561e 100644 --- a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp +++ b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp @@ -62,8 +62,9 @@ void CompressIndicesTest::compressChar() { CORRADE_COMPARE(start, 0); CORRADE_COMPARE(end, 4); CORRADE_COMPARE(type, MeshIndexType::UnsignedByte); - CORRADE_COMPARE(std::vector(data.begin(), data.end()), - (std::vector{ 0x01, 0x02, 0x03, 0x00, 0x04 })); + CORRADE_COMPARE_AS(Containers::arrayCast(data), + Containers::arrayView({1, 2, 3, 0, 4}), + TestSuite::Compare::Container); } void CompressIndicesTest::compressShort() { @@ -76,19 +77,9 @@ void CompressIndicesTest::compressShort() { CORRADE_COMPARE(start, 0); CORRADE_COMPARE(end, 256); CORRADE_COMPARE(type, MeshIndexType::UnsignedShort); - if(!Utility::Endianness::isBigEndian()) { - CORRADE_COMPARE(std::vector(data.begin(), data.end()), - (std::vector{ 0x01, 0x00, - 0x00, 0x01, - 0x00, 0x00, - 0x05, 0x00 })); - } else { - CORRADE_COMPARE(std::vector(data.begin(), data.end()), - (std::vector{ 0x00, 0x01, - 0x01, 0x00, - 0x00, 0x00, - 0x00, 0x05 })); - } + CORRADE_COMPARE_AS(Containers::arrayCast(data), + Containers::arrayView({1, 256, 0, 5}), + TestSuite::Compare::Container); } void CompressIndicesTest::compressInt() { @@ -101,23 +92,14 @@ void CompressIndicesTest::compressInt() { CORRADE_COMPARE(start, 2); CORRADE_COMPARE(end, 65536); CORRADE_COMPARE(type, MeshIndexType::UnsignedInt); - - if(!Utility::Endianness::isBigEndian()) { - CORRADE_COMPARE(std::vector(data.begin(), data.end()), - (std::vector{ 0x00, 0x00, 0x01, 0x00, - 0x03, 0x00, 0x00, 0x00, - 0x02, 0x00, 0x00, 0x00 })); - } else { - CORRADE_COMPARE(std::vector(data.begin(), data.end()), - (std::vector{ 0x00, 0x01, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x03, - 0x00, 0x00, 0x00, 0x02 })); - } + CORRADE_COMPARE_AS(Containers::arrayCast(data), + Containers::arrayView({65536, 3, 2}), + TestSuite::Compare::Container); } void CompressIndicesTest::compressAsShort() { CORRADE_COMPARE_AS(MeshTools::compressIndicesAs({123, 456}), - (Containers::Array{Containers::InPlaceInit, {123, 456}}), + Containers::arrayView({123, 456}), TestSuite::Compare::Container); std::ostringstream out; diff --git a/src/Magnum/MeshTools/Test/DuplicateTest.cpp b/src/Magnum/MeshTools/Test/DuplicateTest.cpp index 5f8e986181..38f7650df9 100644 --- a/src/Magnum/MeshTools/Test/DuplicateTest.cpp +++ b/src/Magnum/MeshTools/Test/DuplicateTest.cpp @@ -69,9 +69,8 @@ void DuplicateTest::duplicate() { constexpr Int data[]{-7, 35, 12, -18}; CORRADE_COMPARE_AS((MeshTools::duplicate(indices, data)), - (Containers::Array{Containers::InPlaceInit, { - 35, 35, -7, -18, 12, 12 - }}), TestSuite::Compare::Container); + Containers::arrayView({35, 35, -7, -18, 12, 12}), + TestSuite::Compare::Container); } void DuplicateTest::duplicateOutOfBounds() { @@ -98,9 +97,8 @@ void DuplicateTest::duplicateInto() { MeshTools::duplicateInto(indices, data, output); CORRADE_COMPARE_AS(Containers::arrayView(output), - (Containers::Array{Containers::InPlaceInit, { - 35, 35, -7, -18, 12, 12 - }}), TestSuite::Compare::Container); + Containers::arrayView({35, 35, -7, -18, 12, 12}), + TestSuite::Compare::Container); } void DuplicateTest::duplicateIntoWrongSize() { diff --git a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp index a42f0e95d7..486582c9fe 100644 --- a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp +++ b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp @@ -102,14 +102,14 @@ constexpr Vector3 TwoTriangles[]{ void GenerateNormalsTest::flat() { CORRADE_COMPARE_AS(generateFlatNormals(TwoTriangles), - (Containers::Array{Containers::InPlaceInit, { + Containers::arrayView({ Vector3::zAxis(), Vector3::zAxis(), Vector3::zAxis(), -Vector3::zAxis(), -Vector3::zAxis(), -Vector3::zAxis() - }}), TestSuite::Compare::Container); + }), TestSuite::Compare::Container); } #ifdef MAGNUM_BUILD_DEPRECATED @@ -165,16 +165,15 @@ template void GenerateNormalsTest::smoothTwoTriangles() { const T indices[]{0, 1, 2, 3, 4, 5}; /* Should generate the same output as flat normals */ - CORRADE_COMPARE_AS( - generateSmoothNormals(indices, TwoTriangles), - (Containers::Array{Containers::InPlaceInit, { + CORRADE_COMPARE_AS(generateSmoothNormals(indices, TwoTriangles), + Containers::arrayView({ Vector3::zAxis(), Vector3::zAxis(), Vector3::zAxis(), -Vector3::zAxis(), -Vector3::zAxis(), -Vector3::zAxis() - }}), TestSuite::Compare::Container); + }), TestSuite::Compare::Container); } void GenerateNormalsTest::smoothCube() { @@ -199,9 +198,8 @@ void GenerateNormalsTest::smoothCube() { }; /* Normals should be the same as positions, only normalized */ - CORRADE_COMPARE_AS( - generateSmoothNormals(indices, positions), - (Containers::Array{Containers::InPlaceInit, { + CORRADE_COMPARE_AS(generateSmoothNormals(indices, positions), + Containers::arrayView({ positions[0]/Constants::sqrt3(), positions[1]/Constants::sqrt3(), positions[2]/Constants::sqrt3(), @@ -210,7 +208,7 @@ void GenerateNormalsTest::smoothCube() { positions[5]/Constants::sqrt3(), positions[6]/Constants::sqrt3(), positions[7]/Constants::sqrt3() - }}), TestSuite::Compare::Container); + }), TestSuite::Compare::Container); } constexpr Vector3 BeveledCubePositions[] { @@ -288,9 +286,8 @@ void GenerateNormalsTest::smoothBeveledCube() { Vector3 z{0.0462723f, 0.0754969f, 0.996072f}; Vector3 x{0.996072f, 0.0754969f, 0.0462723f}; Vector3 y{0.0467958f, 0.997808f, 0.0467958f}; - CORRADE_COMPARE_AS(generateSmoothNormals( - BeveledCubeIndices, BeveledCubePositions), - (Containers::Array{Containers::InPlaceInit, { + CORRADE_COMPARE_AS(generateSmoothNormals(BeveledCubeIndices, BeveledCubePositions), + Containers::arrayView({ z*Math::sign(BeveledCubePositions[ 0]), z*Math::sign(BeveledCubePositions[ 1]), z*Math::sign(BeveledCubePositions[ 2]), /* +Z */ @@ -320,7 +317,7 @@ void GenerateNormalsTest::smoothBeveledCube() { x*Math::sign(BeveledCubePositions[21]), x*Math::sign(BeveledCubePositions[22]), /* -X */ x*Math::sign(BeveledCubePositions[23]) - }}), TestSuite::Compare::Container); + }), TestSuite::Compare::Container); } void GenerateNormalsTest::smoothCylinder() { From aeedf1264d34ff9af23bf207393a90681677865a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 13 Jan 2020 21:10:24 +0100 Subject: [PATCH 013/107] MeshTools: generateSmoothNormals() taking also type-erased indices. --- doc/changelog.dox | 3 ++ src/Magnum/MeshTools/GenerateNormals.cpp | 18 +++++++ src/Magnum/MeshTools/GenerateNormals.h | 23 +++++++++ .../MeshTools/Test/GenerateNormalsTest.cpp | 51 ++++++++++++++++++- 4 files changed, 94 insertions(+), 1 deletion(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 4104e348ef..572de1d3ef 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -212,6 +212,9 @@ See also: @ref MeshTools::removeDuplicatesInPlace() variant - Added @ref MeshTools::duplicateInto() variants that take type-erased 2D strided array views +- Added @ref MeshTools::generateSmoothNormals() and + @ref MeshTools::generateSmoothNormalsInto() variants taking type-erased + index arrays @subsubsection changelog-latest-changes-platform Platform libraries diff --git a/src/Magnum/MeshTools/GenerateNormals.cpp b/src/Magnum/MeshTools/GenerateNormals.cpp index de924b808c..7aacd35ec2 100644 --- a/src/Magnum/MeshTools/GenerateNormals.cpp +++ b/src/Magnum/MeshTools/GenerateNormals.cpp @@ -234,6 +234,18 @@ void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals) { + CORRADE_ASSERT(indices.isContiguous<1>(), "MeshTools::generateSmoothNormalsInto(): second index view dimension is not contiguous", ); + if(indices.size()[1] == 4) + return generateSmoothNormalsIntoImplementation(Containers::arrayCast<1, const UnsignedInt>(indices), positions, normals); + else if(indices.size()[1] == 2) + return generateSmoothNormalsIntoImplementation(Containers::arrayCast<1, const UnsignedShort>(indices), positions, normals); + else { + CORRADE_ASSERT(indices.size()[1] == 1, "MeshTools::generateSmoothNormalsInto(): expected index type size 1, 2 or 4 but got" << indices.size()[1], ); + return generateSmoothNormalsIntoImplementation(Containers::arrayCast<1, const UnsignedByte>(indices), positions, normals); + } +} + namespace { template inline Containers::Array generateSmoothNormalsImplementation(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions) { @@ -257,4 +269,10 @@ Containers::Array generateSmoothNormals(const Containers::StridedArrayV return generateSmoothNormalsImplementation(indices, positions); } +Containers::Array generateSmoothNormals(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView1D& positions) { + Containers::Array out{Containers::NoInit, positions.size()}; + generateSmoothNormalsInto(indices, positions, out); + return out; +} + }} diff --git a/src/Magnum/MeshTools/GenerateNormals.h b/src/Magnum/MeshTools/GenerateNormals.h index fa1ccdffb6..219bdfe737 100644 --- a/src/Magnum/MeshTools/GenerateNormals.h +++ b/src/Magnum/MeshTools/GenerateNormals.h @@ -132,6 +132,17 @@ MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const C */ MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions); +/** +@brief Generate smooth normals using a type-erased index array +@m_since_latest + +Expects that the second dimension of @p indices is contiguous and represents +the actual 1/2/4-byte index type. Based on its size then calls one of the +@ref generateSmoothNormals(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) +etc. overloads. +*/ +MAGNUM_MESHTOOLS_EXPORT Containers::Array generateSmoothNormals(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView1D& positions); + /** @brief Generate smooth normals into an existing array @param[in] indices Triangle face indices @@ -167,6 +178,18 @@ MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::Strided */ MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals); +/** +@brief Generate smooth normals into an existing array using a type-erased index array +@m_since_latest + +Expects that @p normals has the same size as @p positions and that the second +dimension of @p indices is contiguous and represents the actual 1/2/4-byte +index type. Based on its size then calls one of the +@ref generateSmoothNormalsInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) +etc. overloads. +*/ +MAGNUM_MESHTOOLS_EXPORT void generateSmoothNormalsInto(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView1D& positions, const Containers::StridedArrayView1D& normals); + }} #endif diff --git a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp index 486582c9fe..bc80b787a9 100644 --- a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp +++ b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp @@ -61,6 +61,10 @@ struct GenerateNormalsTest: TestSuite::Tester { void smoothOutOfBounds(); void smoothIntoWrongSize(); + template void smoothErased(); + void smoothErasedNonContiguous(); + void smoothErasedWrongIndexSize(); + void benchmarkFlat(); void benchmarkSmooth(); }; @@ -83,7 +87,13 @@ GenerateNormalsTest::GenerateNormalsTest() { &GenerateNormalsTest::smoothNanPosition, &GenerateNormalsTest::smoothWrongCount, &GenerateNormalsTest::smoothOutOfBounds, - &GenerateNormalsTest::smoothIntoWrongSize}); + &GenerateNormalsTest::smoothIntoWrongSize, + + &GenerateNormalsTest::smoothErased, + &GenerateNormalsTest::smoothErased, + &GenerateNormalsTest::smoothErased, + &GenerateNormalsTest::smoothErasedNonContiguous, + &GenerateNormalsTest::smoothErasedWrongIndexSize}); addBenchmarks({&GenerateNormalsTest::benchmarkFlat, &GenerateNormalsTest::benchmarkSmooth}, 150); @@ -425,6 +435,45 @@ void GenerateNormalsTest::benchmarkSmooth() { CORRADE_COMPARE(Math::min(normals), (Vector3{-0.996072f, -0.997808f, -0.996072f})); } +template void GenerateNormalsTest::smoothErased() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + const T indices[]{0, 1, 2, 3, 4, 5}; + + /* Should generate the same output as flat normals */ + CORRADE_COMPARE_AS(generateSmoothNormals(Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices)), TwoTriangles), + Containers::arrayView({ + Vector3::zAxis(), + Vector3::zAxis(), + Vector3::zAxis(), + -Vector3::zAxis(), + -Vector3::zAxis(), + -Vector3::zAxis() + }), TestSuite::Compare::Container); +} + +void GenerateNormalsTest::smoothErasedNonContiguous() { + const char indices[6*4]{}; + const Vector3 positions[3]; + + std::stringstream out; + Error redirectError{&out}; + generateSmoothNormals(Containers::StridedArrayView2D{indices, {6, 2}, {4, 2}}, positions); + CORRADE_COMPARE(out.str(), + "MeshTools::generateSmoothNormalsInto(): second index view dimension is not contiguous\n"); +} + +void GenerateNormalsTest::smoothErasedWrongIndexSize() { + const char indices[6*3]{}; + const Vector3 positions[3]; + + std::stringstream out; + Error redirectError{&out}; + generateSmoothNormals(Containers::StridedArrayView2D{indices, {6, 3}}.every(2), positions); + CORRADE_COMPARE(out.str(), + "MeshTools::generateSmoothNormalsInto(): expected index type size 1, 2 or 4 but got 3\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::GenerateNormalsTest) From 78a29431b1d45ac7dfdf57febe081728e7237ac1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 13 Jan 2020 21:13:20 +0100 Subject: [PATCH 014/107] MeshTools: duplicateInto() taking also type-erased indices. --- src/Magnum/MeshTools/Duplicate.cpp | 12 +++++ src/Magnum/MeshTools/Duplicate.h | 11 ++++ src/Magnum/MeshTools/Test/DuplicateTest.cpp | 60 ++++++++++++++++++++- 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/src/Magnum/MeshTools/Duplicate.cpp b/src/Magnum/MeshTools/Duplicate.cpp index 8d2d076b11..a640755bed 100644 --- a/src/Magnum/MeshTools/Duplicate.cpp +++ b/src/Magnum/MeshTools/Duplicate.cpp @@ -61,4 +61,16 @@ void duplicateInto(const Containers::StridedArrayView1D& indi duplicateIntoImplementation(indices, data, out); } +void duplicateInto(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out) { + CORRADE_ASSERT(indices.isContiguous<1>(), "MeshTools::duplicateInto(): second index view dimension is not contiguous", ); + if(indices.size()[1] == 4) + return duplicateIntoImplementation(Containers::arrayCast<1, const UnsignedInt>(indices), data, out); + else if(indices.size()[1] == 2) + return duplicateIntoImplementation(Containers::arrayCast<1, const UnsignedShort>(indices), data, out); + else { + CORRADE_ASSERT(indices.size()[1] == 1, "MeshTools::duplicateInto(): expected index type size 1, 2 or 4 but got" << indices.size()[1], ); + return duplicateIntoImplementation(Containers::arrayCast<1, const UnsignedByte>(indices), data, out); + } +} + }} diff --git a/src/Magnum/MeshTools/Duplicate.h b/src/Magnum/MeshTools/Duplicate.h index 94d736aa0b..8b58cd13d0 100644 --- a/src/Magnum/MeshTools/Duplicate.h +++ b/src/Magnum/MeshTools/Duplicate.h @@ -115,6 +115,17 @@ MAGNUM_MESHTOOLS_EXPORT void duplicateInto(const Containers::StridedArrayView1D< */ MAGNUM_MESHTOOLS_EXPORT void duplicateInto(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out); +/** +@brief Duplicate type-erased data using a type-erased index array into given output array +@m_since_latest + +Expects that the second dimension of @p indices is contiguous and represents +the actual 1/2/4-byte index type. Based on its size then calls one of the +@ref duplicateInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView2D&, const Containers::StridedArrayView2D&) +etc. overloads. +*/ +MAGNUM_MESHTOOLS_EXPORT void duplicateInto(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView2D& data, const Containers::StridedArrayView2D& out); + 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 38f7650df9..a715b42390 100644 --- a/src/Magnum/MeshTools/Test/DuplicateTest.cpp +++ b/src/Magnum/MeshTools/Test/DuplicateTest.cpp @@ -47,6 +47,10 @@ struct DuplicateTest: TestSuite::Tester { template void duplicateIntoErased(); void duplicateIntoErasedWrongTypeSize(); void duplicateIntoErasedNonContiguous(); + + template void duplicateErasedIndicesIntoErased(); + void duplicateErasedIndicesIntoErasedNonContiguous(); + void duplicateErasedIndicesIntoErasedWrongTypeSize(); }; DuplicateTest::DuplicateTest() { @@ -61,7 +65,13 @@ DuplicateTest::DuplicateTest() { &DuplicateTest::duplicateIntoErased, &DuplicateTest::duplicateIntoErased, &DuplicateTest::duplicateIntoErasedWrongTypeSize, - &DuplicateTest::duplicateIntoErasedNonContiguous}); + &DuplicateTest::duplicateIntoErasedNonContiguous, + + &DuplicateTest::duplicateErasedIndicesIntoErased, + &DuplicateTest::duplicateErasedIndicesIntoErased, + &DuplicateTest::duplicateErasedIndicesIntoErased, + &DuplicateTest::duplicateErasedIndicesIntoErasedNonContiguous, + &DuplicateTest::duplicateErasedIndicesIntoErasedWrongTypeSize}); } void DuplicateTest::duplicate() { @@ -162,6 +172,54 @@ void DuplicateTest::duplicateIntoErasedNonContiguous() { "MeshTools::duplicateInto(): second view dimension is not contiguous\n"); } +template void DuplicateTest::duplicateErasedIndicesIntoErased() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + constexpr T indices[]{1, 1, 0, 3, 2, 2}; + constexpr Int data[]{-7, 35, 12, -18}; + Int output[6]; + + MeshTools::duplicateInto( + Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices)), + Containers::arrayCast<2, const char>(Containers::stridedArrayView(data)), + Containers::arrayCast<2, char>(Containers::stridedArrayView(output))); + CORRADE_COMPARE_AS(Containers::arrayView(output), + Containers::arrayView({35, 35, -7, -18, 12, 12}), + TestSuite::Compare::Container); +} + +void DuplicateTest::duplicateErasedIndicesIntoErasedWrongTypeSize() { + constexpr char indices[6*3]{}; + constexpr Int data[]{-7, 35, 12, -18}; + Short output[6]; + + std::ostringstream out; + Error redirectError{&out}; + + MeshTools::duplicateInto( + Containers::StridedArrayView2D{indices, {6, 3}}.every(2), + Containers::arrayCast<2, const char>(Containers::stridedArrayView(data)), + Containers::arrayCast<2, char>(Containers::stridedArrayView(output))); + CORRADE_COMPARE(out.str(), + "MeshTools::duplicateInto(): expected index type size 1, 2 or 4 but got 3\n"); +} + +void DuplicateTest::duplicateErasedIndicesIntoErasedNonContiguous() { + constexpr char indices[3*6]{}; + constexpr Int data[]{-7, 35, 12, -18}; + Short output[6]; + + std::ostringstream out; + Error redirectError{&out}; + + MeshTools::duplicateInto( + Containers::StridedArrayView2D{indices, {3, 3}, {6, 2}}, + Containers::arrayCast<2, const char>(Containers::stridedArrayView(data)).every({1, 2}), + Containers::arrayCast<2, char>(Containers::stridedArrayView(output))); + CORRADE_COMPARE(out.str(), + "MeshTools::duplicateInto(): second index view dimension is not contiguous\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::DuplicateTest) From 77d7931df2d79eebfcfca1c8db630c5c78338413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 19 Jan 2020 15:12:49 +0100 Subject: [PATCH 015/107] MeshTools: added a STL-less subdivide(). The subdivideInPlace() alone wasn't convenient enough. --- doc/changelog.dox | 7 ++++++ src/Magnum/MeshTools/Subdivide.h | 25 +++++++++++++++++---- src/Magnum/MeshTools/Test/SubdivideTest.cpp | 15 +++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 572de1d3ef..b18fac2d58 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -106,6 +106,11 @@ See also: - Added @ref Math::reflect() and @ref Math::refract() (see [mosra/magnum#420](https://github.com/mosra/magnum/pull/420)) +@subsubsection changelog-latest-new-meshtools MeshTools library + +- Added @ref MeshTools::subdivideInPlace() for allocation-less mesh + subdivision + @subsubsection changelog-latest-new-platform Platform libraries - Cursor management using @ref Platform::Sdl2Application::setCursor(), @@ -205,6 +210,8 @@ See also: @subsubsection changelog-latest-changes-meshtools MeshTools library +- Added @ref MeshTools::subdivide() that operates on a (growable) + @ref Corrade::Containers::Array instead of a @ref std::vector - Added @ref MeshTools::subdivideInPlace() that operates on a partially filled array view instead of a @ref std::vector - Added @ref MeshTools::removeDuplicatesIndexedInPlace() that operates diff --git a/src/Magnum/MeshTools/Subdivide.h b/src/Magnum/MeshTools/Subdivide.h index 1796c16f4d..72f94b8453 100644 --- a/src/Magnum/MeshTools/Subdivide.h +++ b/src/Magnum/MeshTools/Subdivide.h @@ -30,6 +30,7 @@ */ #include +#include #include #include #include @@ -43,18 +44,34 @@ template void subdivideInPlac #endif /** -@brief Subdivide the mesh +@brief Subdivide a mesh @tparam Vertex Vertex data type @tparam Interpolator See the @p interpolator function parameter @param[in,out] indices Index array to operate on @param[in,out] vertices Vertex array to operate on @param interpolator Functor or function pointer which interpolates two adjacent vertices: @cpp Vertex interpolator(Vertex a, Vertex b) @ce +@m_since_latest -Goes through all triangle faces and subdivides them into four new. Removing -duplicate vertices in the mesh is up to the user. +Goes through all triangle faces and subdivides them into four new, enlarging +the @p indices and @p vertices arrays as appropriate. Removing duplicate +vertices in the mesh is up to the user. @see @ref subdivideInPlace(), @ref removeDuplicatesInPlace() */ +template void subdivide(Containers::Array& indices, Containers::Array& vertices, Interpolator interpolator) { + CORRADE_ASSERT(!(indices.size()%3), "MeshTools::subdivide(): index count is not divisible by 3", ); + + arrayResize(vertices, Containers::NoInit, vertices.size() + indices.size()); + arrayResize(indices, Containers::NoInit, indices.size()*4); + subdivideInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(vertices), interpolator); +} + +/** +@brief Subdivide a mesh + +Same as @ref subdivide(Containers::Array&, Containers::Array&vertices, Interpolator), only +working on a @ref std::vector. +*/ template void subdivide(std::vector& indices, std::vector& vertices, Interpolator interpolator) { CORRADE_ASSERT(!(indices.size()%3), "MeshTools::subdivide(): index count is not divisible by 3", ); @@ -64,7 +81,7 @@ template void subdivide(std::vector void subdivideInPlace(); void subdivideInPlaceWrongIndexCount(); @@ -51,6 +52,7 @@ inline Vector1 interpolator(Vector1 a, Vector1 b) { return (a[0]+b[0])/2; } SubdivideTest::SubdivideTest() { addTests({&SubdivideTest::subdivide, + &SubdivideTest::subdivideStl, &SubdivideTest::subdivideWrongIndexCount, &SubdivideTest::subdivideInPlace, &SubdivideTest::subdivideInPlace, @@ -60,6 +62,19 @@ SubdivideTest::SubdivideTest() { } void SubdivideTest::subdivide() { + auto positions = Containers::array({0, 2, 6, 8}); + auto indices = Containers::array({0, 1, 2, 1, 2, 3}); + MeshTools::subdivide(indices, positions, interpolator); + + CORRADE_COMPARE_AS(indices, Containers::arrayView({ + 4, 5, 6, 7, 8, 9, 0, 4, 6, 4, 1, 5, 6, 5, 2, 1, 7, 9, 7, 2, 8, 9, 8, 3 + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(positions, Containers::arrayView({ + 0, 2, 6, 8, 1, 4, 3, 4, 7, 5 + }), TestSuite::Compare::Container); +} + +void SubdivideTest::subdivideStl() { std::vector positions{0, 2, 6, 8}; std::vector indices{0, 1, 2, 1, 2, 3}; MeshTools::subdivide(indices, positions, interpolator); From b589f15640adc4a977084aa1d93b06765dd2cd25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 19 Jan 2020 15:31:14 +0100 Subject: [PATCH 016/107] MeshTools: deSTLify flipNormals(), flipFaceWinding() and tipsify(). And rename them to *InPlace(), since that's what they do. The original STL variants are now deprecated wrappers over the new names. Not adapting the test yet in order to test everything is alright. --- doc/changelog.dox | 17 ++- src/Magnum/MeshTools/FlipNormals.cpp | 37 +++++- src/Magnum/MeshTools/FlipNormals.h | 113 ++++++++++++++---- src/Magnum/MeshTools/Implementation/Tipsify.h | 18 ++- src/Magnum/MeshTools/Test/TipsifyTest.cpp | 23 ++-- src/Magnum/MeshTools/Tipsify.cpp | 58 ++++++--- src/Magnum/MeshTools/Tipsify.h | 41 ++++++- 7 files changed, 234 insertions(+), 73 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index b18fac2d58..d8e348d1be 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -219,6 +219,13 @@ See also: @ref MeshTools::removeDuplicatesInPlace() variant - Added @ref MeshTools::duplicateInto() variants that take type-erased 2D strided array views +- @ref MeshTools::flipNormalsInPlace() and @ref MeshTools::flipFaceWindingInPlace() + were renamed for clarity and now accept a + @ref Corrade::Containers::StridedArrayView instead of a @ref std::vector, + additionally working on 8- and 16-byte index types as well +- @ref MeshTools::tipsifyInPlace() was renamed for clarity and now accepts a + @ref Corrade::Containers::StridedArrayView instead of a @ref std::vector, + additionally working on 8- and 16-byte index types as well - Added @ref MeshTools::generateSmoothNormals() and @ref MeshTools::generateSmoothNormalsInto() variants taking type-erased index arrays @@ -313,8 +320,8 @@ See also: - It was not possible to override DPI scaling using @ref Platform::Sdl2Application::Configuration as command-line arguments always got a priority (see [mosra/magnum#416](https://github.com/mosra/magnum/issues/416)) -- Fixed an otherwise harmless OOB access in @ref MeshTools::tipsify() that - could trigger ASan or debug iterator errors +- Fixed an otherwise harmless OOB access in @ref MeshTools::tipsifyInPlace() + that could trigger ASan or debug iterator errors - With @ref Corrade/Utility/DebugStl.h not being included, @ref std::string instances could get accidentally printed as @ref ResourceKey instances. Added and explicit header dependency to avoid such cases. @@ -356,6 +363,12 @@ See also: @ref Text::FontConverterFeatures, @ref Trade::ImporterFeatures, @ref Trade::ImageConverterFeatures enum sets and their corresponding enums placed directly in the namespace to have them shorter and unambiguous +- @cpp MeshTools::flipNormals() @ce and @cpp MeshTools::flipFaceWinding() @ce + and @cpp MeshTools::tipsify() @ce are deprecated in favor of + @ref MeshTools::flipNormalsInPlace(), + @ref MeshTools::flipFaceWindingInPlace() and @ref MeshTools::tipsifyInPlace() + that accept a @ref Corrade::Containers::StridedArrayView instead of a + @ref std::vector and work with 8- and 16-byte index types as well. @subsection changelog-latest-compatibility Potential compatibility breakages, removed APIs diff --git a/src/Magnum/MeshTools/FlipNormals.cpp b/src/Magnum/MeshTools/FlipNormals.cpp index f6159135db..05089b1325 100644 --- a/src/Magnum/MeshTools/FlipNormals.cpp +++ b/src/Magnum/MeshTools/FlipNormals.cpp @@ -25,11 +25,16 @@ #include "FlipNormals.h" +#include +#include + #include "Magnum/Math/Vector3.h" namespace Magnum { namespace MeshTools { -void flipFaceWinding(std::vector& indices) { +namespace { + +template inline void flipFaceWindingInPlaceImplementation(const Containers::StridedArrayView1D& indices) { CORRADE_ASSERT(!(indices.size()%3), "MeshTools::flipNormals(): index count is not divisible by 3!", ); using std::swap; @@ -37,9 +42,37 @@ void flipFaceWinding(std::vector& indices) { swap(indices[i+1], indices[i+2]); } -void flipNormals(std::vector& normals) { +} + +void flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices) { + flipFaceWindingInPlaceImplementation(indices); +} + +void flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices) { + flipFaceWindingInPlaceImplementation(indices); +} + +void flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices) { + flipFaceWindingInPlaceImplementation(indices); +} + +void flipNormalsInPlace(const Containers::StridedArrayView1D& normals) { for(Vector3& normal: normals) normal = -normal; } +#ifdef MAGNUM_BUILD_DEPRECATED +void flipNormals(std::vector& indices, std::vector& normals) { + flipNormalsInPlace(indices, normals); +} + +void flipFaceWinding(std::vector& indices) { + flipFaceWindingInPlace(indices); +} + +void flipNormals(std::vector& normals) { + flipNormalsInPlace(normals); +} +#endif + }} diff --git a/src/Magnum/MeshTools/FlipNormals.h b/src/Magnum/MeshTools/FlipNormals.h index 6fad4a4a2a..de9e863ec9 100644 --- a/src/Magnum/MeshTools/FlipNormals.h +++ b/src/Magnum/MeshTools/FlipNormals.h @@ -26,52 +26,119 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::flipFaceWinding(), @ref Magnum::MeshTools::flipNormals() + * @brief Function @ref Magnum::MeshTools::flipFaceWindingInPlace(), @ref Magnum::MeshTools::flipNormalsInPlace() */ -#include +#include #include "Magnum/Magnum.h" #include "Magnum/MeshTools/visibility.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#include +#include +#endif + namespace Magnum { namespace MeshTools { /** -@brief Flip face winding +@brief Flip mesh normals and face winding in-place @param[in,out] indices Index array to operate on +@param[in,out] normals Normal array to operate on -The same as @ref flipNormals(std::vector&, std::vector&), -but flips only face winding. - -@attention The function requires the mesh to have triangle faces, thus index - count must be divisible by 3. +Flips normal vectors and face winding in index array for face culling to work +properly too. See also @ref flipNormalsInPlace(const Containers::StridedArrayView1D&) +and @ref flipFaceWindingInPlace(), which flip normals or face winding only. +Expects a triangle mesh, thus the index count has to be divisible by 3. */ -void MAGNUM_MESHTOOLS_EXPORT flipFaceWinding(std::vector& indices); +void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals); /** -@brief Flip mesh normals -@param[in,out] normals Normal array to operate on + * @overload + * @m_since_latest + */ +void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals); -The same as @ref flipNormals(std::vector&, std::vector&), -but flips only normals, not face winding. +/** + * @overload + * @m_since_latest + */ +void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals); + +#ifdef MAGNUM_BUILD_DEPRECATED +/** +@brief @copybrief flipNormalsInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) +@m_deprecated_since_latest Use @ref flipNormalsInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + instead. */ -void MAGNUM_MESHTOOLS_EXPORT flipNormals(std::vector& normals); +CORRADE_DEPRECATED("use flipNormalsInPlace() instead") MAGNUM_MESHTOOLS_EXPORT void flipNormals(std::vector& indices, std::vector& normals); +#endif /** -@brief Flip mesh normals and face winding +@brief Flip face winding in-place @param[in,out] indices Index array to operate on +@m_since_latest + +Same as @ref flipNormalsInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&), +but flips only face winding. Expects a triangle mesh, thus the index count has +to be divisible by 3. +*/ +void MAGNUM_MESHTOOLS_EXPORT flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices); + +/** + * @overload + * @m_since_latest + */ +void MAGNUM_MESHTOOLS_EXPORT flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices); + +/** + * @overload + * @m_since_latest + */ +void MAGNUM_MESHTOOLS_EXPORT flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices); + +#ifdef MAGNUM_BUILD_DEPRECATED +/** +@brief @copybrief flipFaceWindingInPlace(const Containers::StridedArrayView1D&) +@m_deprecated_since_latest Use @ref flipFaceWindingInPlace(const Containers::StridedArrayView1D&) + instead. +*/ +CORRADE_DEPRECATED("use flipFaceWindingInPlace() instead") MAGNUM_MESHTOOLS_EXPORT void flipFaceWinding(std::vector& indices); +#endif + +/** +@brief Flip mesh normals in-place @param[in,out] normals Normal array to operate on -Flips normal vectors and face winding in index array for face culling to work -properly too. See also @ref flipNormals(std::vector&) and -@ref flipFaceWinding(), which flip normals or face winding only. +Same as @ref flipNormalsInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&), +but flips only normals, not face winding. +*/ +void MAGNUM_MESHTOOLS_EXPORT flipNormalsInPlace(const Containers::StridedArrayView1D& normals); -@attention The function requires the mesh to have triangle faces, thus index - count must be divisible by 3. +#ifdef MAGNUM_BUILD_DEPRECATED +/** +@copybrief flipNormalsInPlace(const Containers::StridedArrayView1D&) +@m_deprecated_since_latest Use @ref flipNormalsInPlace(const Containers::StridedArrayView1D&) + instead. */ -inline void flipNormals(std::vector& indices, std::vector& normals) { - flipFaceWinding(indices); - flipNormals(normals); +CORRADE_DEPRECATED("use flipNormalsInPlace() instead") MAGNUM_MESHTOOLS_EXPORT void flipNormals(std::vector& normals); +#endif + +inline void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals) { + flipFaceWindingInPlace(indices); + flipNormalsInPlace(normals); +} + +inline void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals) { + flipFaceWindingInPlace(indices); + flipNormalsInPlace(normals); +} + +inline void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals) { + flipFaceWindingInPlace(indices); + flipNormalsInPlace(normals); } }} diff --git a/src/Magnum/MeshTools/Implementation/Tipsify.h b/src/Magnum/MeshTools/Implementation/Tipsify.h index 63def0dcd8..214e385b0a 100644 --- a/src/Magnum/MeshTools/Implementation/Tipsify.h +++ b/src/Magnum/MeshTools/Implementation/Tipsify.h @@ -25,7 +25,8 @@ DEALINGS IN THE SOFTWARE. */ -#include +#include +#include #include "Magnum/Magnum.h" @@ -33,11 +34,10 @@ namespace Magnum { namespace MeshTools { namespace Implementation { namespace { /* Vertex-triangle adjacency. Computes count and indices of adjacent triangles for each vertex (used internally by tipsify()) */ -void buildAdjacency(const std::vector& indices, const UnsignedInt vertexCount, std::vector& liveTriangleCount, std::vector& neighborOffset, std::vector& neighbors) { +template void buildAdjacency(const Containers::StridedArrayView1D& indices, const UnsignedInt vertexCount, Containers::Array& liveTriangleCount, Containers::Array& neighborOffset, Containers::Array& neighbors) { /* How many times is each vertex referenced == count of neighboring triangles for each vertex */ - liveTriangleCount.clear(); - liveTriangleCount.resize(vertexCount); + liveTriangleCount = Containers::Array{vertexCount}; for(std::size_t i = 0; i != indices.size(); ++i) ++liveTriangleCount[indices[i]]; @@ -45,19 +45,17 @@ void buildAdjacency(const std::vector& indices, const UnsignedInt v the end be in interval neighbors[neighborOffset[i]] ; neighbors[neighborOffset[i+1]]. Currently the values are shifted to right, because the next loop will shift them back left. */ - neighborOffset.clear(); - neighborOffset.reserve(vertexCount+1); - neighborOffset.push_back(0); + neighborOffset = Containers::Array{Containers::NoInit, vertexCount + 1}; + neighborOffset[0] = 0; UnsignedInt sum = 0; for(std::size_t i = 0; i != vertexCount; ++i) { - neighborOffset.push_back(sum); + neighborOffset[i + 1] = sum; sum += liveTriangleCount[i]; } /* Array of neighbors, using (and changing) neighborOffset array for positioning */ - neighbors.clear(); - neighbors.resize(sum); + neighbors = Containers::Array{Containers::NoInit, sum}; for(std::size_t i = 0; i != indices.size(); ++i) neighbors[neighborOffset[indices[i]+1]++] = i/3; } diff --git a/src/Magnum/MeshTools/Test/TipsifyTest.cpp b/src/Magnum/MeshTools/Test/TipsifyTest.cpp index 3b7e972994..15e5714796 100644 --- a/src/Magnum/MeshTools/Test/TipsifyTest.cpp +++ b/src/Magnum/MeshTools/Test/TipsifyTest.cpp @@ -23,7 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include "Magnum/Magnum.h" #include "Magnum/MeshTools/Tipsify.h" @@ -54,7 +56,7 @@ struct TipsifyTest: TestSuite::Tester { */ -const std::vector Indices{ +constexpr UnsignedInt Indices[]{ 4, 1, 0, 10, 9, 13, 6, 3, 2, @@ -88,27 +90,26 @@ TipsifyTest::TipsifyTest() { } void TipsifyTest::buildAdjacency() { - std::vector indices = Indices; - std::vector liveTriangleCount, neighborOffset, neighbors; - Implementation::buildAdjacency(indices, VertexCount, liveTriangleCount, neighborOffset, neighbors); + Containers::Array liveTriangleCount, neighborOffset, neighbors; + Implementation::buildAdjacency(Containers::stridedArrayView(Indices), VertexCount, liveTriangleCount, neighborOffset, neighbors); - CORRADE_COMPARE(liveTriangleCount, (std::vector{ + CORRADE_COMPARE_AS(liveTriangleCount, Containers::arrayView({ 1, 3, 3, 2, 4, 6, 6, 2, 2, 6, 6, 4, 2, 3, 3, 1, 1, 1, 1 - })); + }), TestSuite::Compare::Container); - CORRADE_COMPARE(neighborOffset, (std::vector{ + CORRADE_COMPARE_AS(neighborOffset, Containers::arrayView({ 0, 1, 4, 7, 9, 13, 19, 25, 27, 29, 35, 41, 45, 47, 50, 53, 54, 55, 56, 57 - })); + }), TestSuite::Compare::Container); - CORRADE_COMPARE(neighbors, (std::vector{ + CORRADE_COMPARE_AS(neighbors, Containers::arrayView({ 0, 0, 7, 11, 2, 7, 13, @@ -130,11 +131,11 @@ void TipsifyTest::buildAdjacency() { 6, 18, 18, 18 - })); + }), TestSuite::Compare::Container); } void TipsifyTest::tipsify() { - std::vector indices = Indices; + std::vector indices{std::begin(Indices), std::end(Indices)}; MeshTools::tipsify(indices, VertexCount, 3); CORRADE_COMPARE(indices, (std::vector{ diff --git a/src/Magnum/MeshTools/Tipsify.cpp b/src/Magnum/MeshTools/Tipsify.cpp index b3d4373635..4182335181 100644 --- a/src/Magnum/MeshTools/Tipsify.cpp +++ b/src/Magnum/MeshTools/Tipsify.cpp @@ -25,36 +25,43 @@ #include "Tipsify.h" -#include +#include +#include #include "Magnum/MeshTools/Implementation/Tipsify.h" namespace Magnum { namespace MeshTools { -void tipsify(std::vector& indices, const UnsignedInt vertexCount, const std::size_t cacheSize) { +namespace { + +template void tipsifyInPlaceImplementation(const Containers::StridedArrayView1D& indices, const UnsignedInt vertexCount, const std::size_t cacheSize) { /* Neighboring triangles for each vertex, per-vertex live triangle count */ - std::vector liveTriangleCount, neighborOffset, neighbors; - Implementation::buildAdjacency(indices, vertexCount, liveTriangleCount, neighborOffset, neighbors); + Containers::Array liveTriangleCount, neighborOffset, neighbors; + Implementation::buildAdjacency(indices, vertexCount, liveTriangleCount, neighborOffset, neighbors); /* Global time, per-vertex caching timestamps, per-triangle emmited flag */ UnsignedInt time = cacheSize+1; - std::vector timestamp(vertexCount); - std::vector emitted(indices.size()/3); + Containers::Array timestamp{vertexCount}; + /** @todo Have some bitset/staticbitset class for this */ + Containers::Array emitted{indices.size()/3}; /* Dead-end vertex stack */ - std::stack deadEndStack; + Containers::Array deadEndStack; /* Output index buffer */ - std::vector outputIndices; - outputIndices.reserve(indices.size()); + Containers::Array outputIndices{Containers::NoInit, indices.size()}; + std::size_t outputIndex = 0; + + /* Array with candidates for next fanning vertex (in 1-ring around + fanning vertex) */ + Containers::Array candidates; /* Starting vertex for fanning, cursor */ UnsignedInt fanningVertex = 0; UnsignedInt i = 0; while(fanningVertex != 0xFFFFFFFFu) { - /* Array with candidates for next fanning vertex (in 1-ring around - fanning vertex) */ - std::vector candidates; + /* Reset the candidates for this vertex */ + arrayResize(candidates, 0); /* For all neighbors of fanning vertex */ for(UnsignedInt ti = neighborOffset[fanningVertex]; ti != neighborOffset[fanningVertex+1]; ++ti) { @@ -68,12 +75,12 @@ void tipsify(std::vector& indices, const UnsignedInt vertexCount, c for(UnsignedInt vi = 0; vi != 3; ++vi) { const UnsignedInt v = indices[vi + t*3]; - outputIndices.push_back(v); + outputIndices[outputIndex++] = v; /* Add to dead end stack and candidates array */ /** @todo Limit size of dead end stack to cache size */ - deadEndStack.push(v); - candidates.push_back(v); + arrayAppend(deadEndStack, v); + arrayAppend(candidates, v); /* Decrease live triangle count */ --liveTriangleCount[v]; @@ -109,8 +116,8 @@ void tipsify(std::vector& indices, const UnsignedInt vertexCount, c if(fanningVertex == 0xFFFFFFFFu) { /* Find vertex with live triangles in dead-end stack */ while(!deadEndStack.empty()) { - unsigned int d = deadEndStack.top(); - deadEndStack.pop(); + UnsignedInt d = deadEndStack.back(); + arrayRemoveSuffix(deadEndStack); if(!liveTriangleCount[d]) continue; fanningVertex = d; @@ -129,8 +136,21 @@ void tipsify(std::vector& indices, const UnsignedInt vertexCount, c } /* Swap original index buffer with optimized */ - using std::swap; - swap(indices, outputIndices); + Utility::copy(outputIndices, indices); +} + +} + +void tipsifyInPlace(const Containers::StridedArrayView1D& indices, const UnsignedInt vertexCount, const std::size_t cacheSize) { + tipsifyInPlaceImplementation(indices, vertexCount, cacheSize); +} + +void tipsifyInPlace(const Containers::StridedArrayView1D& indices, const UnsignedInt vertexCount, const std::size_t cacheSize) { + tipsifyInPlaceImplementation(indices, vertexCount, cacheSize); +} + +void tipsifyInPlace(const Containers::StridedArrayView1D& indices, const UnsignedInt vertexCount, const std::size_t cacheSize) { + tipsifyInPlaceImplementation(indices, vertexCount, cacheSize); } }} diff --git a/src/Magnum/MeshTools/Tipsify.h b/src/Magnum/MeshTools/Tipsify.h index 4855d175f3..54885a677d 100644 --- a/src/Magnum/MeshTools/Tipsify.h +++ b/src/Magnum/MeshTools/Tipsify.h @@ -26,30 +26,59 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::tipsify() + * @brief Function @ref Magnum::MeshTools::tipsifyInPlace() */ -#include +#include -#include "Magnum/Types.h" +#include "Magnum/Magnum.h" #include "Magnum/MeshTools/visibility.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#include +#include +#endif + namespace Magnum { namespace MeshTools { /** -@brief Tipsify the mesh +@brief Tipsify the mesh in-place @param[in,out] indices Indices array to operate on @param[in] vertexCount Vertex count @param[in] cacheSize Post-transform vertex cache size Optimizes the mesh for vertex-bound applications by rearranging its index array for beter usage of post-transform vertex cache. Algorithm used: -*Pedro V. Sander, Diego Nehab, and Joshua Barczak --- Fast Triangle Reordering +* *Pedro V. Sander, Diego Nehab, and Joshua Barczak --- Fast Triangle Reordering for Vertex Locality and Reduced Overdraw, SIGGRAPH 2007, http://gfx.cs.princeton.edu/pubs/Sander_2007_%3ETR/index.php*. @todo Ability to compute vertex count automatically */ -MAGNUM_MESHTOOLS_EXPORT void tipsify(std::vector& indices, UnsignedInt vertexCount, std::size_t cacheSize); +MAGNUM_MESHTOOLS_EXPORT void tipsifyInPlace(const Containers::StridedArrayView1D& indices, UnsignedInt vertexCount, std::size_t cacheSize); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT void tipsifyInPlace(const Containers::StridedArrayView1D& indices, UnsignedInt vertexCount, std::size_t cacheSize); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT void tipsifyInPlace(const Containers::StridedArrayView1D& indices, UnsignedInt vertexCount, std::size_t cacheSize); + +#ifdef MAGNUM_BUILD_DEPRECATED +/** + * @brief @copybrief tipsifyInPlace() + * @m_deprecated_since_latest Use @ref tipsifyInPlace() instead. + */ +inline CORRADE_DEPRECATED("use tipsifyInPlace() instead") void tipsify(std::vector& indices, UnsignedInt vertexCount, std::size_t cacheSize) { + tipsifyInPlace(indices, vertexCount, cacheSize); +} +#endif }} From 75eefdbe70f73dfeff08ee637bb6b72e79a529e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 19 Jan 2020 18:50:51 +0100 Subject: [PATCH 017/107] MeshTools: adapt flipFaceWinding(), flipNormals() and tipsify() tests. --- src/Magnum/MeshTools/Test/FlipNormalsTest.cpp | 65 +++++++++++++------ src/Magnum/MeshTools/Test/TipsifyTest.cpp | 29 ++++++--- 2 files changed, 65 insertions(+), 29 deletions(-) diff --git a/src/Magnum/MeshTools/Test/FlipNormalsTest.cpp b/src/Magnum/MeshTools/Test/FlipNormalsTest.cpp index cade8733e8..9f2049fc4e 100644 --- a/src/Magnum/MeshTools/Test/FlipNormalsTest.cpp +++ b/src/Magnum/MeshTools/Test/FlipNormalsTest.cpp @@ -24,7 +24,9 @@ */ #include +#include #include +#include #include #include "Magnum/Math/Vector3.h" @@ -36,44 +38,69 @@ struct FlipNormalsTest: TestSuite::Tester { explicit FlipNormalsTest(); void wrongIndexCount(); - void flipFaceWinding(); + template void flipFaceWinding(); void flipNormals(); + + template void flipNormalsFaceWinding(); }; FlipNormalsTest::FlipNormalsTest() { addTests({&FlipNormalsTest::wrongIndexCount, - &FlipNormalsTest::flipFaceWinding, - &FlipNormalsTest::flipNormals}); + &FlipNormalsTest::flipFaceWinding, + &FlipNormalsTest::flipFaceWinding, + &FlipNormalsTest::flipFaceWinding, + &FlipNormalsTest::flipNormals, + + &FlipNormalsTest::flipNormalsFaceWinding, + &FlipNormalsTest::flipNormalsFaceWinding, + &FlipNormalsTest::flipNormalsFaceWinding}); } void FlipNormalsTest::wrongIndexCount() { std::stringstream ss; Error redirectError{&ss}; - std::vector indices{0, 1}; - MeshTools::flipFaceWinding(indices); + UnsignedByte indices[2]; + MeshTools::flipFaceWindingInPlace(Containers::stridedArrayView(indices)); CORRADE_COMPARE(ss.str(), "MeshTools::flipNormals(): index count is not divisible by 3!\n"); } -void FlipNormalsTest::flipFaceWinding() { - std::vector indices{0, 1, 2, - 3, 4, 5}; - MeshTools::flipFaceWinding(indices); +template void FlipNormalsTest::flipFaceWinding() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[]{0, 1, 2, 3, 4, 5}; + MeshTools::flipFaceWindingInPlace(indices); - CORRADE_COMPARE(indices, (std::vector{0, 2, 1, - 3, 5, 4})); + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({0, 2, 1, 3, 5, 4}), + TestSuite::Compare::Container); } void FlipNormalsTest::flipNormals() { - std::vector normals{Vector3::xAxis(), - Vector3::yAxis(), - Vector3::zAxis()}; - MeshTools::flipNormals(normals); - - CORRADE_COMPARE(normals, (std::vector{-Vector3::xAxis(), - -Vector3::yAxis(), - -Vector3::zAxis()})); + Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}; + MeshTools::flipNormalsInPlace(normals); + + CORRADE_COMPARE_AS(Containers::arrayView(normals), + Containers::arrayView({ + -Vector3::xAxis(), -Vector3::yAxis(), -Vector3::zAxis() + }), TestSuite::Compare::Container); +} + +template void FlipNormalsTest::flipNormalsFaceWinding() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[]{0, 1, 2, 3, 4, 5}; + Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}; + MeshTools::flipNormalsInPlace(indices, normals); + + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({0, 2, 1, 3, 5, 4}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(normals), + Containers::arrayView({ + -Vector3::xAxis(), -Vector3::yAxis(), -Vector3::zAxis() + }), TestSuite::Compare::Container); } }}}} diff --git a/src/Magnum/MeshTools/Test/TipsifyTest.cpp b/src/Magnum/MeshTools/Test/TipsifyTest.cpp index 15e5714796..fbf3ae9e09 100644 --- a/src/Magnum/MeshTools/Test/TipsifyTest.cpp +++ b/src/Magnum/MeshTools/Test/TipsifyTest.cpp @@ -28,6 +28,7 @@ #include #include "Magnum/Magnum.h" +#include "Magnum/Math/TypeTraits.h" #include "Magnum/MeshTools/Tipsify.h" #include "Magnum/MeshTools/Implementation/Tipsify.h" @@ -37,7 +38,7 @@ struct TipsifyTest: TestSuite::Tester { explicit TipsifyTest(); void buildAdjacency(); - void tipsify(); + template void tipsify(); void oneDegenerateTriangle(); }; @@ -85,7 +86,9 @@ constexpr std::size_t VertexCount = 19; TipsifyTest::TipsifyTest() { addTests({&TipsifyTest::buildAdjacency, - &TipsifyTest::tipsify, + &TipsifyTest::tipsify, + &TipsifyTest::tipsify, + &TipsifyTest::tipsify, &TipsifyTest::oneDegenerateTriangle}); } @@ -134,11 +137,15 @@ void TipsifyTest::buildAdjacency() { }), TestSuite::Compare::Container); } -void TipsifyTest::tipsify() { - std::vector indices{std::begin(Indices), std::end(Indices)}; - MeshTools::tipsify(indices, VertexCount, 3); +template void TipsifyTest::tipsify() { + setTestCaseTemplateName(Math::TypeTraits::name()); - CORRADE_COMPARE(indices, (std::vector{ + T indices[Containers::arraySize(Indices)]; + for(std::size_t i = 0; i != Containers::arraySize(Indices); ++i) + indices[i] = Indices[i]; + MeshTools::tipsifyInPlace(indices, VertexCount, 3); + + CORRADE_COMPARE_AS(Containers::arrayView(indices), Containers::arrayView({ 4, 1, 0, 9, 5, 4, 1, 4, 5, @@ -158,16 +165,18 @@ void TipsifyTest::tipsify() { 2, 1, 5, 14, 15, 11, /* from dead-end vertex stack */ 16, 17, 18 /* arbitrary vertex */ - })); + }), TestSuite::Compare::Container); } void TipsifyTest::oneDegenerateTriangle() { /* There used to be an OOB access (neighbors[++ti]) caught by ASan, this triggers it */ - std::vector indices{0, 0, 0}; - MeshTools::tipsify(indices, 1, 2); + UnsignedInt indices[]{0, 0, 0}; + MeshTools::tipsifyInPlace(indices, 1, 2); - CORRADE_COMPARE(indices, (std::vector{0, 0, 0})); + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({0, 0, 0}), + TestSuite::Compare::Container); } }}}} From 937689ea616eb32f287138e488e2c6c307645a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 22 Jan 2020 17:01:59 +0100 Subject: [PATCH 018/107] MeshTools: deSTLify compressIndices(). There's a lot to change with the current version -- the bloaty tuple, the useless min/max, and compressing all the way down to 8 bits is not desirable anymore either. The new function allows to specify a minimal type to compress to and works also on 8- and 16-byte types, which makes it possible to also inflate a smaller type into a larger one. The old function is now deprecated. --- doc/changelog.dox | 7 ++ doc/snippets/MagnumMeshTools-gl.cpp | 26 ++++- src/Magnum/MeshTools/CompressIndices.cpp | 64 +++++++---- src/Magnum/MeshTools/CompressIndices.h | 55 +++++++++- .../MeshTools/Test/CompressIndicesTest.cpp | 102 ++++++++++++------ 5 files changed, 196 insertions(+), 58 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index d8e348d1be..a9d6f36af8 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -210,6 +210,10 @@ See also: @subsubsection changelog-latest-changes-meshtools MeshTools library +- Added @ref MeshTools::compressIndices() that takes a + @ref Containers::StridedArrayView instead of a @ref std::vector and + additionally allows you to specify the smallest allowed type and is working + with 8- and 16-byte index types as well - Added @ref MeshTools::subdivide() that operates on a (growable) @ref Corrade::Containers::Array instead of a @ref std::vector - Added @ref MeshTools::subdivideInPlace() that operates on a partially @@ -363,6 +367,9 @@ See also: @ref Text::FontConverterFeatures, @ref Trade::ImporterFeatures, @ref Trade::ImageConverterFeatures enum sets and their corresponding enums placed directly in the namespace to have them shorter and unambiguous +- @cpp MeshTools::compressIndices() @ce taking a @ref std::vector is + deprecated in favor of @ref MeshTools::compressIndices(const Containers::StridedArrayView1D&, MeshIndexType) and + its 8- and 16-byte overloads - @cpp MeshTools::flipNormals() @ce and @cpp MeshTools::flipFaceWinding() @ce and @cpp MeshTools::tipsify() @ce are deprecated in favor of @ref MeshTools::flipNormalsInPlace(), diff --git a/doc/snippets/MagnumMeshTools-gl.cpp b/doc/snippets/MagnumMeshTools-gl.cpp index bf17e95e71..39ad83a023 100644 --- a/doc/snippets/MagnumMeshTools-gl.cpp +++ b/doc/snippets/MagnumMeshTools-gl.cpp @@ -23,6 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +#include /* for std::tie() :( */ +#include + #include "Magnum/GL/Buffer.h" #include "Magnum/GL/Mesh.h" #include "Magnum/Math/Vector3.h" @@ -35,6 +38,25 @@ int main() { { /* [compressIndices] */ +Containers::Array indices; + +Containers::Array indexData; +MeshIndexType indexType; +std::tie(indexData, indexType) = MeshTools::compressIndices(indices); + +GL::Buffer indexBuffer; +indexBuffer.setData(indexData); + +GL::Mesh mesh; +mesh.setCount(indices.size()) + .setIndexBuffer(indexBuffer, 0, indexType); +/* [compressIndices] */ +} + +#ifdef MAGNUM_BUILD_DEPRECATED +{ +CORRADE_IGNORE_DEPRECATED_PUSH +/* [compressIndices-stl] */ std::vector indices; Containers::Array indexData; @@ -49,8 +71,10 @@ indexBuffer.setData(indexData, GL::BufferUsage::StaticDraw); GL::Mesh mesh; mesh.setCount(indices.size()) .setIndexBuffer(indexBuffer, 0, indexType, indexStart, indexEnd); -/* [compressIndices] */ +/* [compressIndices-stl] */ } +CORRADE_IGNORE_DEPRECATED_POP +#endif { struct MyShader { diff --git a/src/Magnum/MeshTools/CompressIndices.cpp b/src/Magnum/MeshTools/CompressIndices.cpp index c3b56d32ed..c2b2dd3c7b 100644 --- a/src/Magnum/MeshTools/CompressIndices.cpp +++ b/src/Magnum/MeshTools/CompressIndices.cpp @@ -35,7 +35,9 @@ namespace Magnum { namespace MeshTools { namespace { -template inline Containers::Array compress(const std::vector& indices) { +template inline Containers::Array compress(const Containers::StridedArrayView1D& indices) { + /* Can't use Utility::copy() here because we're copying from a larger type + to a smaller one */ Containers::Array buffer(indices.size()*sizeof(T)); for(std::size_t i = 0; i != indices.size(); ++i) { T index = static_cast(indices[i]); @@ -45,34 +47,56 @@ template inline Containers::Array compress(const std::vector std::pair, MeshIndexType> compressIndicesImplementation(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast) { + const T max = Math::max(indices); + Containers::Array out; + MeshIndexType type; + const UnsignedInt log = Math::log(256, max); + + /* If it fits into 8 bytes and 8 bytes are allowed, pack into 8 */ + if(log == 0 && atLeast == MeshIndexType::UnsignedByte) { + out = compress(indices); + type = MeshIndexType::UnsignedByte; + + /* Otherwise, if it fits into either 8 or 16 bytes and we allow either 8 or + 16, pack into 16 */ + } else if(log <= 1 && atLeast != MeshIndexType::UnsignedInt) { + out = compress(indices); + type = MeshIndexType::UnsignedShort; + + /* Otherwise pack into 32 */ + } else { + out = compress(indices); + type = MeshIndexType::UnsignedInt; + } + + return {std::move(out), type}; +} + +} + +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast) { + return compressIndicesImplementation(indices, atLeast); } +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast) { + return compressIndicesImplementation(indices, atLeast); +} + +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast) { + return compressIndicesImplementation(indices, atLeast); +} + +#ifdef MAGNUM_BUILD_DEPRECATED std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> compressIndices(const std::vector& indices) { /** @todo Performance hint when range can be represented by smaller value? */ const auto minmax = Math::minmax(indices); Containers::Array data; MeshIndexType type; - switch(Math::log(256, minmax.second)) { - case 0: - data = compress(indices); - type = MeshIndexType::UnsignedByte; - break; - case 1: - data = compress(indices); - type = MeshIndexType::UnsignedShort; - break; - case 2: - case 3: - data = compress(indices); - type = MeshIndexType::UnsignedInt; - break; - - default: - CORRADE_ASSERT(false, "MeshTools::compressIndices(): no type able to index" << minmax.second << "elements.", {}); /* LCOV_EXCL_LINE */ - } - + std::tie(data, type) = compressIndices(indices, MeshIndexType::UnsignedByte); return std::make_tuple(std::move(data), type, minmax.first, minmax.second); } +#endif template Containers::Array compressIndicesAs(const std::vector& indices) { #if !defined(CORRADE_NO_ASSERT) || defined(CORRADE_GRACEFUL_ASSERT) diff --git a/src/Magnum/MeshTools/CompressIndices.h b/src/Magnum/MeshTools/CompressIndices.h index eceb614bd2..ea2e0b6583 100644 --- a/src/Magnum/MeshTools/CompressIndices.h +++ b/src/Magnum/MeshTools/CompressIndices.h @@ -26,21 +26,65 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::compressIndices() + * @brief Function @ref Magnum::MeshTools::compressIndices(), @ref Magnum::MeshTools::compressIndicesAs() */ -#include -#include +#include +#include +#include #include "Magnum/Mesh.h" #include "Magnum/MeshTools/visibility.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#endif + namespace Magnum { namespace MeshTools { +/** +@brief Compress an index array +@param indices Index array +@param atLeast Smallest allowed type +@return Compressed index array and corresponding type +@m_since_latest + +This function compresses @p indices to the smallest possible size. For example +when your indices have the maximum vertex index 463, it's wasteful to store +them in array of 32bit integers, array of 16bit integers is sufficient. The +@p atLeast parameter allows you to specify the smallest type to use and it +defaults to @ref MeshIndexType::UnsignedShort as 8-bit types are not friendly +to many GPUs (and for example unextended Vulkan or D3D12 don't even support +them). It's also possible to choose a type larger than the input type to +"inflate" an index buffer of a smaller type. + +Example usage: + +@snippet MagnumMeshTools-gl.cpp compressIndices +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort); + +/** +@overload +@m_since_latest +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort); + +/** +@overload +@m_since_latest +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort); + +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief Compress vertex indices @param indices Index array @return Index range, type and compressed index array +@m_deprecated_since_latest Use @ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType) + instead. The index range isn't returned anymore, use @ref Math::minmax(const Containers::StridedArrayView1D&) + to get it if needed. This function takes index array and outputs them compressed to smallest possible size. For example when your indices have maximum number 463, it's @@ -49,11 +93,12 @@ sufficient. Example usage: -@snippet MagnumMeshTools-gl.cpp compressIndices +@snippet MagnumMeshTools-gl.cpp compressIndices-stl @see @ref compressIndicesAs() */ -std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> MAGNUM_MESHTOOLS_EXPORT compressIndices(const std::vector& indices); +CORRADE_DEPRECATED("use compressIndices(const Containers::StridedArrayView1D&, MeshIndexType) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> compressIndices(const std::vector& indices); +#endif /** @brief Compress vertex indices as given type diff --git a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp index 15889b561e..0ed27bff5e 100644 --- a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp +++ b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp @@ -24,12 +24,15 @@ */ #include +#include #include +#include #include #include #include #include +#include "Magnum/Math/TypeTraits.h" #include "Magnum/MeshTools/CompressIndices.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { @@ -37,42 +40,91 @@ namespace Magnum { namespace MeshTools { namespace Test { namespace { struct CompressIndicesTest: TestSuite::Tester { explicit CompressIndicesTest(); - void compressChar(); - void compressShort(); - void compressInt(); + template void compressUnsignedByte(); + template void compressUnsignedShort(); + template void compressUnsignedInt(); + void compressUnsignedByteInflateToShort(); + #ifdef MAGNUM_BUILD_DEPRECATED + void compressDeprecated(); + #endif void compressAsShort(); }; CompressIndicesTest::CompressIndicesTest() { - addTests({&CompressIndicesTest::compressChar, - &CompressIndicesTest::compressShort, - &CompressIndicesTest::compressInt, + addTests({&CompressIndicesTest::compressUnsignedByte, + &CompressIndicesTest::compressUnsignedByte, + &CompressIndicesTest::compressUnsignedByte, + &CompressIndicesTest::compressUnsignedShort, + &CompressIndicesTest::compressUnsignedShort, + &CompressIndicesTest::compressUnsignedInt, + &CompressIndicesTest::compressUnsignedByteInflateToShort, + + #ifdef MAGNUM_BUILD_DEPRECATED + &CompressIndicesTest::compressDeprecated, + #endif &CompressIndicesTest::compressAsShort}); } -void CompressIndicesTest::compressChar() { - Containers::Array data; - MeshIndexType type; - UnsignedInt start, end; - std::tie(data, type, start, end) = MeshTools::compressIndices( - std::vector{1, 2, 3, 0, 4}); +template void CompressIndicesTest::compressUnsignedByte() { + setTestCaseTemplateName(Math::TypeTraits::name()); - CORRADE_COMPARE(start, 0); - CORRADE_COMPARE(end, 4); - CORRADE_COMPARE(type, MeshIndexType::UnsignedByte); - CORRADE_COMPARE_AS(Containers::arrayCast(data), + const T indices[]{1, 2, 3, 0, 4}; + /* By default it has 16-byte type as minimum, override */ + std::pair, MeshIndexType> out = + compressIndices(indices, MeshIndexType::UnsignedByte); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedByte); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), Containers::arrayView({1, 2, 3, 0, 4}), TestSuite::Compare::Container); } -void CompressIndicesTest::compressShort() { +template void CompressIndicesTest::compressUnsignedShort() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + const T indices[]{1, 256, 0, 5}; + std::pair, MeshIndexType> out = compressIndices(indices); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 256, 0, 5}), + TestSuite::Compare::Container); +} + +template void CompressIndicesTest::compressUnsignedInt() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + const T indices[]{65536, 3, 2}; + std::pair, MeshIndexType> out = compressIndices(indices); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({65536, 3, 2}), + TestSuite::Compare::Container); +} + +void CompressIndicesTest::compressUnsignedByteInflateToShort() { + const UnsignedByte indices[]{1, 2, 3, 0, 4}; + /* That's the default */ + std::pair, MeshIndexType> out = compressIndices(indices); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 2, 3, 0, 4}), + TestSuite::Compare::Container); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void CompressIndicesTest::compressDeprecated() { Containers::Array data; MeshIndexType type; UnsignedInt start, end; + CORRADE_IGNORE_DEPRECATED_PUSH std::tie(data, type, start, end) = MeshTools::compressIndices( std::vector{1, 256, 0, 5}); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(start, 0); CORRADE_COMPARE(end, 256); @@ -81,21 +133,7 @@ void CompressIndicesTest::compressShort() { Containers::arrayView({1, 256, 0, 5}), TestSuite::Compare::Container); } - -void CompressIndicesTest::compressInt() { - Containers::Array data; - MeshIndexType type; - UnsignedInt start, end; - std::tie(data, type, start, end) = MeshTools::compressIndices( - std::vector{65536, 3, 2}); - - CORRADE_COMPARE(start, 2); - CORRADE_COMPARE(end, 65536); - CORRADE_COMPARE(type, MeshIndexType::UnsignedInt); - CORRADE_COMPARE_AS(Containers::arrayCast(data), - Containers::arrayView({65536, 3, 2}), - TestSuite::Compare::Container); -} +#endif void CompressIndicesTest::compressAsShort() { CORRADE_COMPARE_AS(MeshTools::compressIndicesAs({123, 456}), From 1974da2635805a63d91bc9c118d619e94f0d2622 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 19 Jan 2020 21:41:04 +0100 Subject: [PATCH 019/107] MeshTools: added a type-erased compressIndices(). Useful when consuming the indices straight from MeshData. --- doc/changelog.dox | 6 +-- src/Magnum/MeshTools/CompressIndices.cpp | 12 +++++ src/Magnum/MeshTools/CompressIndices.h | 11 +++++ .../MeshTools/Test/CompressIndicesTest.cpp | 49 +++++++++++++++++++ 4 files changed, 75 insertions(+), 3 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index a9d6f36af8..4a3ea23977 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -211,9 +211,9 @@ See also: @subsubsection changelog-latest-changes-meshtools MeshTools library - Added @ref MeshTools::compressIndices() that takes a - @ref Containers::StridedArrayView instead of a @ref std::vector and - additionally allows you to specify the smallest allowed type and is working - with 8- and 16-byte index types as well + @ref Corrade::Containers::StridedArrayView instead of a @ref std::vector + and additionally allows you to specify the smallest allowed type and is + working with 8- and 16-byte index types as well - Added @ref MeshTools::subdivide() that operates on a (growable) @ref Corrade::Containers::Array instead of a @ref std::vector - Added @ref MeshTools::subdivideInPlace() that operates on a partially diff --git a/src/Magnum/MeshTools/CompressIndices.cpp b/src/Magnum/MeshTools/CompressIndices.cpp index c2b2dd3c7b..1be656c4c6 100644 --- a/src/Magnum/MeshTools/CompressIndices.cpp +++ b/src/Magnum/MeshTools/CompressIndices.cpp @@ -87,6 +87,18 @@ std::pair, MeshIndexType> compressIndices(const Containe return compressIndicesImplementation(indices, atLeast); } +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, const MeshIndexType atLeast) { + CORRADE_ASSERT(indices.isContiguous<1>(), "MeshTools::compressIndices(): second view dimension is not contiguous", {}); + if(indices.size()[1] == 4) + return compressIndicesImplementation(Containers::arrayCast<1, const UnsignedInt>(indices), atLeast); + else if(indices.size()[1] == 2) + return compressIndicesImplementation(Containers::arrayCast<1, const UnsignedShort>(indices), atLeast); + else { + CORRADE_ASSERT(indices.size()[1] == 1, "MeshTools::compressIndices(): expected index type size 1, 2 or 4 but got" << indices.size()[1], {}); + return compressIndicesImplementation(Containers::arrayCast<1, const UnsignedByte>(indices), atLeast); + } +} + #ifdef MAGNUM_BUILD_DEPRECATED std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> compressIndices(const std::vector& indices) { /** @todo Performance hint when range can be represented by smaller value? */ diff --git a/src/Magnum/MeshTools/CompressIndices.h b/src/Magnum/MeshTools/CompressIndices.h index ea2e0b6583..23a4eb0703 100644 --- a/src/Magnum/MeshTools/CompressIndices.h +++ b/src/Magnum/MeshTools/CompressIndices.h @@ -77,6 +77,17 @@ MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compre */ MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort); +/** +@brief Compress a type-erased index array +@m_since_latest + +Expects that the second dimension of @p indices is contiguous and represents +the actual 1/2/4-byte index type. Based on its size then calls one of the +@ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType) +etc. overloads. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort); + #ifdef MAGNUM_BUILD_DEPRECATED /** @brief Compress vertex indices diff --git a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp index 0ed27bff5e..145b8edb8c 100644 --- a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp +++ b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp @@ -44,6 +44,9 @@ struct CompressIndicesTest: TestSuite::Tester { template void compressUnsignedShort(); template void compressUnsignedInt(); void compressUnsignedByteInflateToShort(); + /* No compressErased(), as that's tested in the templates above */ + void compressErasedNonContiguous(); + void compressErasedWrongIndexSize(); #ifdef MAGNUM_BUILD_DEPRECATED void compressDeprecated(); #endif @@ -59,6 +62,8 @@ CompressIndicesTest::CompressIndicesTest() { &CompressIndicesTest::compressUnsignedShort, &CompressIndicesTest::compressUnsignedInt, &CompressIndicesTest::compressUnsignedByteInflateToShort, + &CompressIndicesTest::compressErasedNonContiguous, + &CompressIndicesTest::compressErasedWrongIndexSize, #ifdef MAGNUM_BUILD_DEPRECATED &CompressIndicesTest::compressDeprecated, @@ -79,6 +84,14 @@ template void CompressIndicesTest::compressUnsignedByte() { CORRADE_COMPARE_AS(Containers::arrayCast(out.first), Containers::arrayView({1, 2, 3, 0, 4}), TestSuite::Compare::Container); + + /* Test the type-erased variant as well */ + out = compressIndices(Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices)), MeshIndexType::UnsignedByte); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedByte); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 2, 3, 0, 4}), + TestSuite::Compare::Container); } template void CompressIndicesTest::compressUnsignedShort() { @@ -87,6 +100,14 @@ template void CompressIndicesTest::compressUnsignedShort() { const T indices[]{1, 256, 0, 5}; std::pair, MeshIndexType> out = compressIndices(indices); + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 256, 0, 5}), + TestSuite::Compare::Container); + + /* Test the type-erased variant as well */ + out = compressIndices(Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices))); + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedShort); CORRADE_COMPARE_AS(Containers::arrayCast(out.first), Containers::arrayView({1, 256, 0, 5}), @@ -99,6 +120,14 @@ template void CompressIndicesTest::compressUnsignedInt() { const T indices[]{65536, 3, 2}; std::pair, MeshIndexType> out = compressIndices(indices); + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({65536, 3, 2}), + TestSuite::Compare::Container); + + /* Test the type-erased variant as well */ + out = compressIndices(Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices))); + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedInt); CORRADE_COMPARE_AS(Containers::arrayCast(out.first), Containers::arrayView({65536, 3, 2}), @@ -116,6 +145,26 @@ void CompressIndicesTest::compressUnsignedByteInflateToShort() { TestSuite::Compare::Container); } +void CompressIndicesTest::compressErasedNonContiguous() { + const char indices[6*4]{}; + + std::stringstream out; + Error redirectError{&out}; + compressIndices(Containers::StridedArrayView2D{indices, {6, 2}, {4, 2}}); + CORRADE_COMPARE(out.str(), + "MeshTools::compressIndices(): second view dimension is not contiguous\n"); +} + +void CompressIndicesTest::compressErasedWrongIndexSize() { + const char indices[6*3]{}; + + std::stringstream out; + Error redirectError{&out}; + compressIndices(Containers::StridedArrayView2D{indices, {6, 3}}.every(2)); + CORRADE_COMPARE(out.str(), + "MeshTools::compressIndices(): expected index type size 1, 2 or 4 but got 3\n"); +} + #ifdef MAGNUM_BUILD_DEPRECATED void CompressIndicesTest::compressDeprecated() { Containers::Array data; From bae5eecf420ee2f60b24ae3ead1e53ea7f1a1a9c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 22 Jan 2020 17:02:14 +0100 Subject: [PATCH 020/107] MeshTools: compressIndices() now takes also an offset. --- doc/changelog.dox | 8 +-- doc/snippets/MagnumMeshTools.cpp | 12 ++++ src/Magnum/MeshTools/CompressIndices.cpp | 53 +++++++++----- src/Magnum/MeshTools/CompressIndices.h | 70 ++++++++++++++++--- .../MeshTools/Test/CompressIndicesTest.cpp | 44 ++++++++++++ 5 files changed, 153 insertions(+), 34 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 4a3ea23977..474e095cc2 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -212,8 +212,8 @@ See also: - Added @ref MeshTools::compressIndices() that takes a @ref Corrade::Containers::StridedArrayView instead of a @ref std::vector - and additionally allows you to specify the smallest allowed type and is - working with 8- and 16-byte index types as well + and additionally allows you to specify the smallest allowed type, offset to + apply to each index and is working with 8- and 16-byte index types as well - Added @ref MeshTools::subdivide() that operates on a (growable) @ref Corrade::Containers::Array instead of a @ref std::vector - Added @ref MeshTools::subdivideInPlace() that operates on a partially @@ -368,8 +368,8 @@ See also: @ref Trade::ImageConverterFeatures enum sets and their corresponding enums placed directly in the namespace to have them shorter and unambiguous - @cpp MeshTools::compressIndices() @ce taking a @ref std::vector is - deprecated in favor of @ref MeshTools::compressIndices(const Containers::StridedArrayView1D&, MeshIndexType) and - its 8- and 16-byte overloads + deprecated in favor of @ref MeshTools::compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) + and its 8- and 16-byte overloads - @cpp MeshTools::flipNormals() @ce and @cpp MeshTools::flipFaceWinding() @ce and @cpp MeshTools::tipsify() @ce are deprecated in favor of @ref MeshTools::flipNormalsInPlace(), diff --git a/doc/snippets/MagnumMeshTools.cpp b/doc/snippets/MagnumMeshTools.cpp index a34dcc1224..530c770dbc 100644 --- a/doc/snippets/MagnumMeshTools.cpp +++ b/doc/snippets/MagnumMeshTools.cpp @@ -24,6 +24,7 @@ */ #include "Magnum/Math/Color.h" +#include "Magnum/Math/FunctionsBatch.h" #include "Magnum/MeshTools/CombineIndexedArrays.h" #include "Magnum/MeshTools/CompressIndices.h" #include "Magnum/MeshTools/Duplicate.h" @@ -53,6 +54,17 @@ std::vector indices = MeshTools::combineIndexedArrays( /* [combineIndexedArrays] */ } +{ +/* [compressIndices-offset] */ +Containers::ArrayView indices; +UnsignedInt offset = Math::min(indices); +std::pair, MeshIndexType> result = + MeshTools::compressIndices(indices, offset); + +// use `offset` to adjust vertex attribute offset … +/* [compressIndices-offset] */ +} + { /* [compressIndicesAs] */ std::vector indices; diff --git a/src/Magnum/MeshTools/CompressIndices.cpp b/src/Magnum/MeshTools/CompressIndices.cpp index 1be656c4c6..b9bf7091a2 100644 --- a/src/Magnum/MeshTools/CompressIndices.cpp +++ b/src/Magnum/MeshTools/CompressIndices.cpp @@ -35,38 +35,38 @@ namespace Magnum { namespace MeshTools { namespace { -template inline Containers::Array compress(const Containers::StridedArrayView1D& indices) { +template inline Containers::Array compress(const Containers::StridedArrayView1D& indices, Long offset) { /* Can't use Utility::copy() here because we're copying from a larger type - to a smaller one */ + to a smaller one (and subtracting an offset in addition) */ Containers::Array buffer(indices.size()*sizeof(T)); for(std::size_t i = 0; i != indices.size(); ++i) { - T index = static_cast(indices[i]); + T index = static_cast(indices[i] - offset); std::memcpy(buffer.begin()+i*sizeof(T), &index, sizeof(T)); } return buffer; } -template std::pair, MeshIndexType> compressIndicesImplementation(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast) { - const T max = Math::max(indices); +template std::pair, MeshIndexType> compressIndicesImplementation(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast, const Long offset) { + const UnsignedInt max = Math::max(indices) - offset; Containers::Array out; MeshIndexType type; const UnsignedInt log = Math::log(256, max); /* If it fits into 8 bytes and 8 bytes are allowed, pack into 8 */ if(log == 0 && atLeast == MeshIndexType::UnsignedByte) { - out = compress(indices); + out = compress(indices, offset); type = MeshIndexType::UnsignedByte; /* Otherwise, if it fits into either 8 or 16 bytes and we allow either 8 or 16, pack into 16 */ } else if(log <= 1 && atLeast != MeshIndexType::UnsignedInt) { - out = compress(indices); + out = compress(indices, offset); type = MeshIndexType::UnsignedShort; /* Otherwise pack into 32 */ } else { - out = compress(indices); + out = compress(indices, offset); type = MeshIndexType::UnsignedInt; } @@ -75,33 +75,48 @@ template std::pair, MeshIndexType> compressIndi } -std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast) { - return compressIndicesImplementation(indices, atLeast); +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast, const Long offset) { + return compressIndicesImplementation(indices, atLeast, offset); } -std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast) { - return compressIndicesImplementation(indices, atLeast); +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast, const Long offset) { + return compressIndicesImplementation(indices, atLeast, offset); } -std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast) { - return compressIndicesImplementation(indices, atLeast); +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const MeshIndexType atLeast, const Long offset) { + return compressIndicesImplementation(indices, atLeast, offset); } -std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, const MeshIndexType atLeast) { +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const Long offset) { + return compressIndicesImplementation(indices, MeshIndexType::UnsignedShort, offset); +} + +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const Long offset) { + return compressIndicesImplementation(indices, MeshIndexType::UnsignedShort, offset); +} + +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, const Long offset) { + return compressIndicesImplementation(indices, MeshIndexType::UnsignedShort, offset); +} + +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, const MeshIndexType atLeast, const Long offset) { CORRADE_ASSERT(indices.isContiguous<1>(), "MeshTools::compressIndices(): second view dimension is not contiguous", {}); if(indices.size()[1] == 4) - return compressIndicesImplementation(Containers::arrayCast<1, const UnsignedInt>(indices), atLeast); + return compressIndicesImplementation(Containers::arrayCast<1, const UnsignedInt>(indices), atLeast, offset); else if(indices.size()[1] == 2) - return compressIndicesImplementation(Containers::arrayCast<1, const UnsignedShort>(indices), atLeast); + return compressIndicesImplementation(Containers::arrayCast<1, const UnsignedShort>(indices), atLeast, offset); else { CORRADE_ASSERT(indices.size()[1] == 1, "MeshTools::compressIndices(): expected index type size 1, 2 or 4 but got" << indices.size()[1], {}); - return compressIndicesImplementation(Containers::arrayCast<1, const UnsignedByte>(indices), atLeast); + return compressIndicesImplementation(Containers::arrayCast<1, const UnsignedByte>(indices), atLeast, offset); } } +std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, const Long offset) { + return compressIndices(indices, MeshIndexType::UnsignedShort, offset); +} + #ifdef MAGNUM_BUILD_DEPRECATED std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> compressIndices(const std::vector& indices) { - /** @todo Performance hint when range can be represented by smaller value? */ const auto minmax = Math::minmax(indices); Containers::Array data; MeshIndexType type; diff --git a/src/Magnum/MeshTools/CompressIndices.h b/src/Magnum/MeshTools/CompressIndices.h index 23a4eb0703..a594a41d51 100644 --- a/src/Magnum/MeshTools/CompressIndices.h +++ b/src/Magnum/MeshTools/CompressIndices.h @@ -47,35 +47,74 @@ namespace Magnum { namespace MeshTools { @brief Compress an index array @param indices Index array @param atLeast Smallest allowed type +@param offset Offset to subtract from each index @return Compressed index array and corresponding type @m_since_latest This function compresses @p indices to the smallest possible size. For example when your indices have the maximum vertex index 463, it's wasteful to store -them in array of 32bit integers, array of 16bit integers is sufficient. The +them in array of 32-bit integers, array of 16-bit integers is sufficient. The @p atLeast parameter allows you to specify the smallest type to use and it defaults to @ref MeshIndexType::UnsignedShort as 8-bit types are not friendly to many GPUs (and for example unextended Vulkan or D3D12 don't even support them). It's also possible to choose a type larger than the input type to -"inflate" an index buffer of a smaller type. - -Example usage: +"inflate" an index buffer of a smaller type. Example usage: @snippet MagnumMeshTools-gl.cpp compressIndices + +In case the indices all start from a large offset, the @p offset parameter can +be used to subtract it, allowing them to be compressed even further. For +example, if all indices are in range @f$ [ 75000 ; 96000 ] @f$ (which fits only +into a 32-bit type), subtracting 75000 makes them in range @f$ [ 0; 21000 ] @f$ +which fits into 16 bits. Note that you also need to update vertex attribute +offsets accordingly. Example: + +@snippet MagnumMeshTools.cpp compressIndices-offset + +A negative @p offset value will do an operation inverse to the above. See also +@ref compressIndices(const Trade::MeshData&, MeshIndexType) that can do this +operation directly on a @ref Trade::MeshData instance. */ -MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort); +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort, Long offset = 0); /** @overload @m_since_latest */ -MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort); +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort, Long offset = 0); /** @overload @m_since_latest */ -MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort); +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort, Long offset = 0); + +/** +@overload +@m_since_latest + +Same as @ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) +with @p atLeast set to @ref MeshIndexType::UnsignedShort. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, Long offset); + +/** +@overload +@m_since_latest + +Same as @ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) +with @p atLeast set to @ref MeshIndexType::UnsignedShort. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, Long offset); + +/** +@overload +@m_since_latest + +Same as @ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) +with @p atLeast set to @ref MeshIndexType::UnsignedShort. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView1D& indices, Long offset); /** @brief Compress a type-erased index array @@ -83,17 +122,26 @@ MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compre Expects that the second dimension of @p indices is contiguous and represents the actual 1/2/4-byte index type. Based on its size then calls one of the -@ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType) +@ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) etc. overloads. */ -MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort); +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, MeshIndexType atLeast = MeshIndexType::UnsignedShort, Long offset = 0); + +/** +@overload +@m_since_latest + +Same as @ref compressIndices(const Containers::StridedArrayView2D&, MeshIndexType, Long) +with @p atLeast set to @ref MeshIndexType::UnsignedShort. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, Long offset); #ifdef MAGNUM_BUILD_DEPRECATED /** @brief Compress vertex indices @param indices Index array @return Index range, type and compressed index array -@m_deprecated_since_latest Use @ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType) +@m_deprecated_since_latest Use @ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) instead. The index range isn't returned anymore, use @ref Math::minmax(const Containers::StridedArrayView1D&) to get it if needed. @@ -108,7 +156,7 @@ Example usage: @see @ref compressIndicesAs() */ -CORRADE_DEPRECATED("use compressIndices(const Containers::StridedArrayView1D&, MeshIndexType) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> compressIndices(const std::vector& indices); +CORRADE_DEPRECATED("use compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> compressIndices(const std::vector& indices); #endif /** diff --git a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp index 145b8edb8c..611601291c 100644 --- a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp +++ b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp @@ -44,6 +44,8 @@ struct CompressIndicesTest: TestSuite::Tester { template void compressUnsignedShort(); template void compressUnsignedInt(); void compressUnsignedByteInflateToShort(); + void compressOffset(); + template void compressOffsetNegative(); /* No compressErased(), as that's tested in the templates above */ void compressErasedNonContiguous(); void compressErasedWrongIndexSize(); @@ -62,6 +64,10 @@ CompressIndicesTest::CompressIndicesTest() { &CompressIndicesTest::compressUnsignedShort, &CompressIndicesTest::compressUnsignedInt, &CompressIndicesTest::compressUnsignedByteInflateToShort, + &CompressIndicesTest::compressOffset, + &CompressIndicesTest::compressOffsetNegative, + &CompressIndicesTest::compressOffsetNegative, + &CompressIndicesTest::compressOffsetNegative, &CompressIndicesTest::compressErasedNonContiguous, &CompressIndicesTest::compressErasedWrongIndexSize, @@ -145,6 +151,44 @@ void CompressIndicesTest::compressUnsignedByteInflateToShort() { TestSuite::Compare::Container); } +void CompressIndicesTest::compressOffset() { + const UnsignedInt indices[]{75000 + 1, 75000 + 256, 75000 + 0, 75000 + 5}; + std::pair, MeshIndexType> out = compressIndices(indices, 75000); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 256, 0, 5}), + TestSuite::Compare::Container); + + /* Test the type-erased variant as well */ + out = compressIndices(Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices)), 75000); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({1, 256, 0, 5}), + TestSuite::Compare::Container); +} + +template void CompressIndicesTest::compressOffsetNegative() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + const T indices[]{1, 255, 0, 5}; + std::pair, MeshIndexType> out = compressIndices(indices, -75000); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({75000 + 1, 75000 + 255, 75000 + 0, 75000 + 5}), + TestSuite::Compare::Container); + + /* Test the type-erased variant as well */ + out = compressIndices(Containers::arrayCast<2, const char>(Containers::stridedArrayView(indices)), -75000); + + CORRADE_COMPARE(out.second, MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(Containers::arrayCast(out.first), + Containers::arrayView({75000 + 1, 75000 + 255, 75000 + 0, 75000 + 5}), + TestSuite::Compare::Container); +} + void CompressIndicesTest::compressErasedNonContiguous() { const char indices[6*4]{}; From 87c2bc74fefb43afb5c13ce1ddf592b8665b0042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 21 Jan 2020 12:19:45 +0100 Subject: [PATCH 021/107] MeshTools: implement removeDuplicates() for discrete data as well. --- doc/changelog.dox | 2 + doc/snippets/MagnumMeshTools.cpp | 12 ++ src/Magnum/MeshTools/CMakeLists.txt | 3 +- src/Magnum/MeshTools/RemoveDuplicates.cpp | 114 ++++++++++++++++++ src/Magnum/MeshTools/RemoveDuplicates.h | 67 ++++++++-- src/Magnum/MeshTools/Test/CMakeLists.txt | 2 +- .../MeshTools/Test/RemoveDuplicatesTest.cpp | 98 +++++++++++++-- 7 files changed, 280 insertions(+), 18 deletions(-) create mode 100644 src/Magnum/MeshTools/RemoveDuplicates.cpp diff --git a/doc/changelog.dox b/doc/changelog.dox index 474e095cc2..6f6fc89ad5 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -110,6 +110,8 @@ See also: - Added @ref MeshTools::subdivideInPlace() for allocation-less mesh subdivision +- New @ref MeshTools::removeDuplicatesInPlace() variant that works on + discrete data in addition to floating-point @subsubsection changelog-latest-new-platform Platform libraries diff --git a/doc/snippets/MagnumMeshTools.cpp b/doc/snippets/MagnumMeshTools.cpp index 530c770dbc..e3e64c468d 100644 --- a/doc/snippets/MagnumMeshTools.cpp +++ b/doc/snippets/MagnumMeshTools.cpp @@ -96,6 +96,18 @@ auto data = MeshTools::interleave(positions, weights, 2, vertexColors, 1); /* [interleave2] */ } +{ +/* [removeDuplicates] */ +Containers::ArrayView data; + +std::size_t size; +Containers::Array indices; +std::tie(indices, size) = MeshTools::removeDuplicatesInPlace( + Containers::arrayCast<2, char>(data)); +data = data.prefix(size); +/* [removeDuplicates] */ +} + { /* [removeDuplicates-multiple] */ std::vector positions; diff --git a/src/Magnum/MeshTools/CMakeLists.txt b/src/Magnum/MeshTools/CMakeLists.txt index 59ac7bde86..a1a7c4d475 100644 --- a/src/Magnum/MeshTools/CMakeLists.txt +++ b/src/Magnum/MeshTools/CMakeLists.txt @@ -33,7 +33,8 @@ set(MagnumMeshTools_GracefulAssert_SRCS CompressIndices.cpp Duplicate.cpp FlipNormals.cpp - GenerateNormals.cpp) + GenerateNormals.cpp + RemoveDuplicates.cpp) set(MagnumMeshTools_HEADERS CombineIndexedArrays.h diff --git a/src/Magnum/MeshTools/RemoveDuplicates.cpp b/src/Magnum/MeshTools/RemoveDuplicates.cpp new file mode 100644 index 0000000000..6f7a526a6d --- /dev/null +++ b/src/Magnum/MeshTools/RemoveDuplicates.cpp @@ -0,0 +1,114 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +#include "RemoveDuplicates.h" + +namespace Magnum { namespace MeshTools { + +struct ArrayEqual { + bool operator()(Containers::ArrayView a, Containers::ArrayView b) const { + CORRADE_INTERNAL_ASSERT(a.size() == b.size()); + return std::memcmp(a, b, a.size()) == 0; + } +}; + +struct ArrayHash { + std::size_t operator()(Containers::ArrayView a) const { + return *reinterpret_cast(Utility::MurmurHash2{}(a, a.size()).byteArray()); + } +}; + +std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView2D& data) { + /* Assuming the second dimension is contiguous so we can calculate the + hashes easily */ + CORRADE_ASSERT(data.empty()[0] || data.isContiguous<1>(), + "MeshTools::removeDuplicatesInPlace(): second data view dimension is not contiguous", {}); + + const std::size_t dataSize = data.size()[0]; + /* Table containing index of first occurence for each unique entry. + Reserving more buckets than necessary (i.e. as if each entry was + unique). */ + std::unordered_map, UnsignedInt, ArrayHash, ArrayEqual> table{dataSize}; + + Containers::Array remapping{Containers::NoInit, dataSize}; + + /* Go through all entries */ + for(std::size_t i = 0; i != dataSize; ++i) { + /* Try to insert new entry into the table */ + const Containers::ArrayView entry = data[i].asContiguous(); + const auto result = table.emplace(entry, table.size()); + + /* Add the (either new or already existing) index into the array */ + remapping[i] = result.first->second; + + /* If this is a new combination, copy the data to new (earlier) + position in the array. Data in [table.size()-1, i) are already + present in the [0, table.size()-1) range from previous iterations so + we aren't overwriting anything. */ + if(result.second && i != table.size() - 1) + Utility::copy(entry, data[table.size() - 1].asContiguous()); + } + + CORRADE_INTERNAL_ASSERT(dataSize >= table.size()); + return {std::move(remapping), table.size()}; +} + +namespace { + +template std::size_t removeDuplicatesIndexedInPlaceImplementation(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data) { + /* Somehow ~IndexType{} doesn't work for < 4byte types, as the result is + int(-1) instead of the type I want */ + CORRADE_ASSERT(data.size()[0] <= IndexType(-1), + "MeshTools::removeDuplicatesIndexedInPlace(): a" << sizeof(IndexType) << Debug::nospace << "-byte index type is too small for" << data.size()[0] << "vertices", {}); + + /* There's no way to avoid the additional allocation, unfortunately --- + iterating over the indices instead of data would not preserve the + original order, which is an useful property. The float version has this + inverted (having the *Indexed() variant as the main implementation) + because the remapping there has to be done once for every dimension. */ + std::pair, std::size_t> result = removeDuplicatesInPlace(data); + for(auto& i: indices) i = result.first[i]; + return result.second; +} + +} + +std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data) { + return removeDuplicatesIndexedInPlaceImplementation(indices, data); +} + +std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data) { + return removeDuplicatesIndexedInPlaceImplementation(indices, data); +} + +std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data) { + return removeDuplicatesIndexedInPlaceImplementation(indices, data); +} + +}} diff --git a/src/Magnum/MeshTools/RemoveDuplicates.h b/src/Magnum/MeshTools/RemoveDuplicates.h index a256bf4125..504b992a34 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.h +++ b/src/Magnum/MeshTools/RemoveDuplicates.h @@ -39,6 +39,7 @@ #include "Magnum/Magnum.h" #include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/MeshTools/visibility.h" namespace Magnum { namespace MeshTools { @@ -51,6 +52,55 @@ namespace Implementation { }; } +/** +@brief Remove duplicate data from given array in-place +@param[in,out] data Data array, duplicate items will be cut away with order + preserved +@return Size of unique prefix in the cleaned up @p data array and the resulting + index array +@m_since_latest + +Removes duplicate data from given array by comparing the second dimension of +each item, the second dimension is expected to be contiguous. A plain bit-exact +matching is used, if you need fuzzy comparison for floating-point data, use +@ref removeDuplicatesInPlace(const Containers::StridedArrayView1D&, typename Vector::Type) +instead. If you want to remove duplicate data from an already indexed array, +use @ref removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView2D&) +instead. Usage example: + +@snippet MagnumMeshTools.cpp removeDuplicates + +@see @ref Corrade::Containers::StridedArrayView::isContiguous() +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView2D& data); + +/** +@brief Remove duplicates from indexed data in-place +@param[in,out] indices Index array, which will get remapped to list just + unique data +@param[in,out] data Data array, duplicate items will be cut away with order + preserved +@return Size of unique prefix in the cleaned up @p data array +@m_since_latest + +Compared to @ref removeDuplicatesInPlace(const Containers::StridedArrayView2D&) +this variant is more suited for data that are already indexed as it works on +the existing index array instead of allocating a new one. +*/ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data); + /** @brief Remove duplicate floating-point vector data from given array in-place @param[in,out] data Data array, duplicate items will be cut away with order @@ -64,13 +114,14 @@ namespace Implementation { Removes duplicate data from the array by collapsing them into buckets of size @p epsilon. First vector in given bucket is used, other ones are thrown away, no interpolation is done. Note that this function is meant to be used for -floating-point data (or generally with non-zero @p epsilon), for discrete data -the usual sorting method is much more efficient. +floating-point data (or generally with non-zero @p epsilon), for data where +bit-exact matching is sufficient use @ref removeDuplicatesInPlace(const Containers::StridedArrayView2D&) +instead. If you want to remove duplicate data from an already indexed array, use -@ref removeDuplicatesIndexedInPlace() instead. See also -@ref removeDuplicates(std::vector&, typename Vector::Type) for a -variant operating on a STL vector. +@ref removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, typename Vector::Type) instead. +See also @ref removeDuplicates(std::vector&, typename Vector::Type) for +a variant operating on a STL vector. */ template std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView1D& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()); @@ -104,9 +155,9 @@ template std::vector removeDuplicates(std::vector&, typename Vector::Type) +this variant is more suited for data that are already indexed as it works on +the existing index array instead of allocating a new one. */ template std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()) { /* Somehow ~IndexType{} doesn't work for < 4byte types, as the result is diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index 0e2277a589..8c49708bdd 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -29,7 +29,7 @@ corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp LIBRARIES MagnumMeshTo corrade_add_test(MeshToolsFlipNormalsTest FlipNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsGenerateNormalsTest GenerateNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib MagnumPrimitives) corrade_add_test(MeshToolsInterleaveTest InterleaveTest.cpp LIBRARIES Magnum) -corrade_add_test(MeshToolsRemoveDuplicatesTest RemoveDuplicatesTest.cpp LIBRARIES Magnum) +corrade_add_test(MeshToolsRemoveDuplicatesTest RemoveDuplicatesTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsSubdivideTest SubdivideTest.cpp LIBRARIES Magnum) corrade_add_test(MeshToolsTipsifyTest TipsifyTest.cpp LIBRARIES MagnumMeshTools) corrade_add_test(MeshToolsTransformTest TransformTest.cpp LIBRARIES MagnumMeshTools) diff --git a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp index 3c932751e1..1891083f3f 100644 --- a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp +++ b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp @@ -37,27 +37,109 @@ struct RemoveDuplicatesTest: TestSuite::Tester { explicit RemoveDuplicatesTest(); void removeDuplicatesInPlace(); - void removeDuplicatesStl(); + void removeDuplicatesInPlaceNonContiguous(); template void removeDuplicatesIndexedInPlace(); void removeDuplicatesIndexedInPlaceSmallType(); void removeDuplicatesIndexedInPlaceEmptyIndices(); void removeDuplicatesIndexedInPlaceEmptyIndicesVertices(); + void removeDuplicatesFuzzyInPlace(); + void removeDuplicatesFuzzyStl(); + template void removeDuplicatesFuzzyIndexedInPlace(); + void removeDuplicatesFuzzyIndexedInPlaceSmallType(); + void removeDuplicatesFuzzyIndexedInPlaceEmptyIndices(); + void removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices(); + /* this is additionally regression-tested in PrimitivesIcosphereTest */ }; RemoveDuplicatesTest::RemoveDuplicatesTest() { addTests({&RemoveDuplicatesTest::removeDuplicatesInPlace, - &RemoveDuplicatesTest::removeDuplicatesStl, + &RemoveDuplicatesTest::removeDuplicatesInPlaceNonContiguous, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceSmallType, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndices, - &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndicesVertices}); + &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndicesVertices, + + &RemoveDuplicatesTest::removeDuplicatesFuzzyInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyStl, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceSmallType, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndices, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices}); } void RemoveDuplicatesTest::removeDuplicatesInPlace() { + Int data[]{-15, 32, 24, -15, 15, 7541, 24, 32}; + + std::pair, std::size_t> result = + MeshTools::removeDuplicatesInPlace(Containers::arrayCast<2, char>(Containers::arrayView(data))); + CORRADE_COMPARE_AS(Containers::arrayView(result.first), + Containers::arrayView({0, 1, 2, 0, 3, 4, 2, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(result.second), + Containers::arrayView({-15, 32, 24, 15, 7541}), + TestSuite::Compare::Container); +} + +void RemoveDuplicatesTest::removeDuplicatesInPlaceNonContiguous() { + Int data[8]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::removeDuplicatesInPlace(Containers::arrayCast<2, char>(Containers::arrayView(data)).every({1, 2})); + CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesInPlace(): second data view dimension is not contiguous\n"); +} + +template void RemoveDuplicatesTest::removeDuplicatesIndexedInPlace() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[]{3, 2, 0, 1, 7, 6, 4, 2, 5, 0}; + Int data[]{-15, 32, 24, -15, 15, 7541, 24, 32}; + std::size_t count = MeshTools::removeDuplicatesIndexedInPlace(indices, + Containers::arrayCast<2, char>(Containers::arrayView(data))); + + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({0, 2, 0, 1, 1, 2, 3, 2, 4, 0}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count), + Containers::arrayView({-15, 32, 24, 15, 7541}), + TestSuite::Compare::Container); +} + +void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceSmallType() { + std::stringstream out; + Error redirectError{&out}; + + UnsignedByte indices[1]; + Vector2i data[256]{}; + MeshTools::removeDuplicatesIndexedInPlace( + Containers::stridedArrayView(indices), + Containers::arrayCast<2, char>(Containers::arrayView(data))); + CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesIndexedInPlace(): a 1-byte index type is too small for 256 vertices\n"); +} + +void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndices() { + Int data[]{-15, 32, 24, -15, 15, 7541, 24, 32}; + + std::size_t count = MeshTools::removeDuplicatesIndexedInPlace( + Containers::StridedArrayView1D{}, + Containers::arrayCast<2, char>(Containers::arrayView(data))); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count), + Containers::arrayView({-15, 32, 24, 15, 7541}), + TestSuite::Compare::Container); +} + +void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndicesVertices() { + CORRADE_COMPARE(MeshTools::removeDuplicatesIndexedInPlace( + Containers::StridedArrayView1D{}, {}), 0); +} + +void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlace() { /* Numbers with distance 1 should be merged, numbers with distance 2 should be kept. Testing both even-odd and odd-even sequence to verify that half-epsilon translations are applied properly. */ @@ -77,7 +159,7 @@ void RemoveDuplicatesTest::removeDuplicatesInPlace() { TestSuite::Compare::Container); } -void RemoveDuplicatesTest::removeDuplicatesStl() { +void RemoveDuplicatesTest::removeDuplicatesFuzzyStl() { /* Same but with implicit bloat. HEH HEH */ std::vector data{ {1, 0}, @@ -95,7 +177,7 @@ void RemoveDuplicatesTest::removeDuplicatesStl() { TestSuite::Compare::Container); } -template void RemoveDuplicatesTest::removeDuplicatesIndexedInPlace() { +template void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace() { setTestCaseTemplateName(Math::TypeTraits::name()); /* Same as above, but with an explicit index buffer */ @@ -118,7 +200,7 @@ template void RemoveDuplicatesTest::removeDuplicatesIndexedInPlace() { TestSuite::Compare::Container); } -void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceSmallType() { +void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceSmallType() { std::stringstream out; Error redirectError{&out}; @@ -130,7 +212,7 @@ void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceSmallType() { CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesIndexedInPlace(): a 1-byte index type is too small for 256 vertices\n"); } -void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndices() { +void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndices() { Vector2i data[]{ {1, 0}, {2, 1}, @@ -146,7 +228,7 @@ void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndices() { TestSuite::Compare::Container); } -void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndicesVertices() { +void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices() { CORRADE_COMPARE((MeshTools::removeDuplicatesIndexedInPlace({}, {}, 2)), 0); } From 7fd92c10ddc175910a414f1d5051d44a22fcf1f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 5 Nov 2019 13:11:31 +0100 Subject: [PATCH 022/107] Reserve zero MeshPrimitive and MeshIndexType for invalid values. Better for checking accidents, as picking a wrong primitive / index type can lead to *serious* rendering issues. Similarly to a change done to (Compressed)PixelFormat in 2019.10. --- doc/changelog.dox | 8 ++++++++ src/Magnum/GL/Mesh.cpp | 8 ++++---- src/Magnum/GL/Test/MeshTest.cpp | 4 ++++ src/Magnum/Mesh.cpp | 24 ++++++++++++------------ src/Magnum/Mesh.h | 12 ++++++++---- src/Magnum/Sampler.h | 18 +++++++++++++++--- src/Magnum/Test/MeshTest.cpp | 25 ++++++++++++++++++------- src/Magnum/Vk/Enums.cpp | 16 ++++++++-------- src/Magnum/Vk/Test/EnumsTest.cpp | 8 ++++++++ 9 files changed, 85 insertions(+), 38 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 6f6fc89ad5..137ca77367 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -418,6 +418,14 @@ See also: @ref Shaders::Phong::bindDiffuseTexture(), @ref Shaders::Phong::bindSpecularTexture() and @ref Shaders::Phong::bindTextures() instead +- @ref MeshPrimitive and @ref MeshIndexType now reserve the zero value to + indicate an invalid primitive / type, better catching accidentally + forgotten initialization. Valid code shouldn't be affected by this change, + but broken code that seemingly worked before might start throwing + assertions now. In contrast, @ref SamplerFilter, @ref SamplerMipmap and + @ref SamplerWrapping keep the zero value as a reasonable default. This + follows a similar change done for @ref PixelFormat and + @ref CompressedPixelFormat in 2019.10. @subsection changelog-latest-documentation Documentation diff --git a/src/Magnum/GL/Mesh.cpp b/src/Magnum/GL/Mesh.cpp index 438a21820f..729d407d78 100644 --- a/src/Magnum/GL/Mesh.cpp +++ b/src/Magnum/GL/Mesh.cpp @@ -66,15 +66,15 @@ constexpr MeshIndexType IndexTypeMapping[]{ } MeshPrimitive meshPrimitive(const Magnum::MeshPrimitive primitive) { - CORRADE_ASSERT(UnsignedInt(primitive) < Containers::arraySize(PrimitiveMapping), + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveMapping), "GL::meshPrimitive(): invalid primitive" << primitive, {}); - return PrimitiveMapping[UnsignedInt(primitive)]; + return PrimitiveMapping[UnsignedInt(primitive) - 1]; } MeshIndexType meshIndexType(const Magnum::MeshIndexType type) { - CORRADE_ASSERT(UnsignedInt(type) < Containers::arraySize(IndexTypeMapping), + CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), "GL::meshIndexType(): invalid type" << type, {}); - return IndexTypeMapping[UnsignedInt(type)]; + return IndexTypeMapping[UnsignedInt(type) - 1]; } #ifndef DOXYGEN_GENERATING_OUTPUT diff --git a/src/Magnum/GL/Test/MeshTest.cpp b/src/Magnum/GL/Test/MeshTest.cpp index 880302abe6..e91279b581 100644 --- a/src/Magnum/GL/Test/MeshTest.cpp +++ b/src/Magnum/GL/Test/MeshTest.cpp @@ -181,8 +181,10 @@ void MeshTest::mapPrimitiveInvalid() { std::ostringstream out; Error redirectError{&out}; + meshPrimitive(Magnum::MeshPrimitive{}); meshPrimitive(Magnum::MeshPrimitive(0x123)); CORRADE_COMPARE(out.str(), + "GL::meshPrimitive(): invalid primitive MeshPrimitive(0x0)\n" "GL::meshPrimitive(): invalid primitive MeshPrimitive(0x123)\n"); } @@ -218,8 +220,10 @@ void MeshTest::mapIndexTypeInvalid() { std::ostringstream out; Error redirectError{&out}; + meshIndexType(Magnum::MeshIndexType(0x0)); meshIndexType(Magnum::MeshIndexType(0x123)); CORRADE_COMPARE(out.str(), + "GL::meshIndexType(): invalid type MeshIndexType(0x0)\n" "GL::meshIndexType(): invalid type MeshIndexType(0x123)\n"); } diff --git a/src/Magnum/Mesh.cpp b/src/Magnum/Mesh.cpp index 97e8ad83dc..65fa64b111 100644 --- a/src/Magnum/Mesh.cpp +++ b/src/Magnum/Mesh.cpp @@ -56,8 +56,8 @@ constexpr const char* MeshPrimitiveNames[] { Debug& operator<<(Debug& debug, const MeshPrimitive value) { debug << "MeshPrimitive" << Debug::nospace; - if(UnsignedInt(value) < Containers::arraySize(MeshPrimitiveNames)) { - return debug << "::" << Debug::nospace << MeshPrimitiveNames[UnsignedInt(value)]; + if(UnsignedInt(value) - 1 < Containers::arraySize(MeshPrimitiveNames)) { + return debug << "::" << Debug::nospace << MeshPrimitiveNames[UnsignedInt(value) - 1]; } return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; @@ -76,8 +76,8 @@ constexpr const char* MeshIndexTypeNames[] { Debug& operator<<(Debug& debug, const MeshIndexType value) { debug << "MeshIndexType" << Debug::nospace; - if(UnsignedInt(value) < Containers::arraySize(MeshIndexTypeNames)) { - return debug << "::" << Debug::nospace << MeshIndexTypeNames[UnsignedInt(value)]; + if(UnsignedInt(value) - 1 < Containers::arraySize(MeshIndexTypeNames)) { + return debug << "::" << Debug::nospace << MeshIndexTypeNames[UnsignedInt(value) - 1]; } return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; @@ -89,31 +89,31 @@ Debug& operator<<(Debug& debug, const MeshIndexType value) { namespace Corrade { namespace Utility { std::string ConfigurationValue::toString(Magnum::MeshPrimitive value, ConfigurationValueFlags) { - if(Magnum::UnsignedInt(value) < Containers::arraySize(Magnum::MeshPrimitiveNames)) - return Magnum::MeshPrimitiveNames[Magnum::UnsignedInt(value)]; + if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::MeshPrimitiveNames)) + return Magnum::MeshPrimitiveNames[Magnum::UnsignedInt(value) - 1]; return {}; } Magnum::MeshPrimitive ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { for(std::size_t i = 0; i != Containers::arraySize(Magnum::MeshPrimitiveNames); ++i) - if(stringValue == Magnum::MeshPrimitiveNames[i]) return Magnum::MeshPrimitive(i); + if(stringValue == Magnum::MeshPrimitiveNames[i]) return Magnum::MeshPrimitive(i + 1); - return Magnum::MeshPrimitive::Points; + return {}; } std::string ConfigurationValue::toString(Magnum::MeshIndexType value, ConfigurationValueFlags) { - if(Magnum::UnsignedInt(value) < Containers::arraySize(Magnum::MeshIndexTypeNames)) - return Magnum::MeshIndexTypeNames[Magnum::UnsignedInt(value)]; + if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::MeshIndexTypeNames)) + return Magnum::MeshIndexTypeNames[Magnum::UnsignedInt(value) - 1]; return {}; } Magnum::MeshIndexType ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { for(std::size_t i = 0; i != Containers::arraySize(Magnum::MeshIndexTypeNames); ++i) - if(stringValue == Magnum::MeshIndexTypeNames[i]) return Magnum::MeshIndexType(i); + if(stringValue == Magnum::MeshIndexTypeNames[i]) return Magnum::MeshIndexType(i + 1); - return Magnum::MeshIndexType::UnsignedInt; + return {}; } }} diff --git a/src/Magnum/Mesh.h b/src/Magnum/Mesh.h index 4816b9728c..23bf1762e6 100644 --- a/src/Magnum/Mesh.h +++ b/src/Magnum/Mesh.h @@ -53,6 +53,8 @@ for Metal, corresponds to @m_class{m-doc-external} [MTLPrimitiveType](https://de See documentation of each value for more information about the mapping. */ enum class MeshPrimitive: UnsignedInt { + /* Zero reserved for an invalid type (but not being a named value) */ + /** * Single points. * @@ -62,7 +64,7 @@ enum class MeshPrimitive: UnsignedInt { * or @m_class{m-doc-external} [MTLPrimitiveTypePoint](https://developer.apple.com/documentation/metal/mtlprimitivetype/mtlprimitivetypepoint?language=objc). * @m_keywords{D3D_PRIMITIVE_TOPOLOGY_POINTLIST MTLPrimitiveTypePoint} */ - Points, + Points = 1, /** * Each pair of vertices defines a single line, lines aren't @@ -147,6 +149,8 @@ there, use @ref Vk::hasVkIndexType() to check for its presence. @see @ref meshIndexTypeSize() */ enum class MeshIndexType: UnsignedInt { + /* Zero reserved for an invalid type (but not being a named value) */ + /** * Unsigned byte * @@ -155,7 +159,7 @@ enum class MeshIndexType: UnsignedInt { * suggest (via debug output) using 16-byte types instead for better * efficiency. */ - UnsignedByte, + UnsignedByte = 1, /** * Unsigned short @@ -198,7 +202,7 @@ template<> struct MAGNUM_EXPORT ConfigurationValue { /** * @brief Reads enum value as string * - * If the value is invalid, returns @ref Magnum::MeshPrimitive::Points "MeshPrimitive::Points". + * If the value is invalid, returns a zero (invalid) primitive. */ static Magnum::MeshPrimitive fromString(const std::string& stringValue, ConfigurationValueFlags); }; @@ -217,7 +221,7 @@ template<> struct MAGNUM_EXPORT ConfigurationValue { /** * @brief Read enum value as string * - * If the value is invalid, returns @ref Magnum::MeshIndexType::UnsignedInt "MeshIndexType::UnsignedInt". + * If the value is invalid, returns a zero (invalid) type. */ static Magnum::MeshIndexType fromString(const std::string& stringValue, ConfigurationValueFlags); }; diff --git a/src/Magnum/Sampler.h b/src/Magnum/Sampler.h index 369ecc6390..188c3a196f 100644 --- a/src/Magnum/Sampler.h +++ b/src/Magnum/Sampler.h @@ -47,13 +47,17 @@ information about the mapping. @see @ref SamplerMipmap, @ref SamplerWrapping */ enum class SamplerFilter: UnsignedInt { + /* Unlike with MeshIndexType, MeshPrimitive, VertexFormat, PixelFormat + etc., this enum doesn't have zero as an invalid value -- Nearest is a + good default */ + /** * Nearest neighbor filtering. * * Corresponds to @ref GL::SamplerFilter::Nearest / * @def_vk_keyword{FILTER_NEAREST,Filter}. */ - Nearest, + Nearest = 0, /** * Linear interpolation filtering. @@ -77,6 +81,10 @@ each value for more information about the mapping. @see @ref SamplerFilter, @ref SamplerWrapping */ enum class SamplerMipmap: UnsignedInt { + /* Unlike with MeshIndexType, MeshPrimitive, VertexFormat, PixelFormat + etc., this enum doesn't have zero as an invalid value -- Base is a + good default */ + /** * Select base mip level * @@ -85,7 +93,7 @@ enum class SamplerMipmap: UnsignedInt { * @def_vk_keyword{SAMPLER_MIPMAP_MODE_NEAREST,SamplerMipmapMode} and you * have to configure the sampler to use just a single mipmap level. */ - Base, + Base = 0, /** * Select nearest mip level. @@ -119,13 +127,17 @@ presence. @see @ref SamplerFilter, @ref SamplerMipmap */ enum class SamplerWrapping: UnsignedInt { + /* Unlike with MeshIndexType, MeshPrimitive, VertexFormat, PixelFormat + etc., this enum doesn't have zero as an invalid value -- Repeat is a + good default */ + /** * Repeat texture. * * Corresponds to @ref GL::SamplerWrapping::Repeat / * @def_vk_keyword{SAMPLER_ADDRESS_MODE_REPEAT,SamplerAddressMode}. */ - Repeat, + Repeat = 0, /** * Repeat mirrored texture. diff --git a/src/Magnum/Test/MeshTest.cpp b/src/Magnum/Test/MeshTest.cpp index d212d4bf50..c121a9c729 100644 --- a/src/Magnum/Test/MeshTest.cpp +++ b/src/Magnum/Test/MeshTest.cpp @@ -63,8 +63,8 @@ MeshTest::MeshTest() { void MeshTest::primitiveMapping() { /* This goes through the first 8 bits, which should be enough. */ UnsignedInt firstUnhandled = 0xff; - UnsignedInt nextHandled = 0; - for(UnsignedInt i = 0; i <= 0xff; ++i) { + UnsignedInt nextHandled = 1; /* 0 is an invalid primitive */ + for(UnsignedInt i = 1; i <= 0xff; ++i) { const auto primitive = MeshPrimitive(i); /* Each case verifies: - that the entries are ordered by number by comparing a function to @@ -101,8 +101,8 @@ void MeshTest::primitiveMapping() { void MeshTest::indexTypeMapping() { /* This goes through the first 8 bits, which should be enough. */ UnsignedInt firstUnhandled = 0xff; - UnsignedInt nextHandled = 0; - for(UnsignedInt i = 0; i <= 0xff; ++i) { + UnsignedInt nextHandled = 1; /* 0 is an invalid type */ + for(UnsignedInt i = 1; i <= 0xff; ++i) { const auto type = MeshIndexType(i); /* Each case verifies: - that the entries are ordered by number by comparing a function to @@ -146,9 +146,12 @@ void MeshTest::indexTypeSizeInvalid() { std::ostringstream out; Error redirectError{&out}; + meshIndexTypeSize(MeshIndexType{}); meshIndexTypeSize(MeshIndexType(0xdead)); - CORRADE_COMPARE(out.str(), "meshIndexTypeSize(): invalid type MeshIndexType(0xdead)\n"); + CORRADE_COMPARE(out.str(), + "meshIndexTypeSize(): invalid type MeshIndexType(0x0)\n" + "meshIndexTypeSize(): invalid type MeshIndexType(0xdead)\n"); } void MeshTest::debugPrimitive() { @@ -170,9 +173,13 @@ void MeshTest::configurationPrimitive() { CORRADE_COMPARE(c.value("primitive"), "LineStrip"); CORRADE_COMPARE(c.value("primitive"), MeshPrimitive::LineStrip); + c.setValue("zero", MeshPrimitive{}); + CORRADE_COMPARE(c.value("zero"), ""); + CORRADE_COMPARE(c.value("zero"), MeshPrimitive{}); + c.setValue("invalid", MeshPrimitive(0xdead)); CORRADE_COMPARE(c.value("invalid"), ""); - CORRADE_COMPARE(c.value("invalid"), MeshPrimitive::Points); + CORRADE_COMPARE(c.value("invalid"), MeshPrimitive{}); } void MeshTest::configurationIndexType() { @@ -182,9 +189,13 @@ void MeshTest::configurationIndexType() { CORRADE_COMPARE(c.value("type"), "UnsignedShort"); CORRADE_COMPARE(c.value("type"), MeshIndexType::UnsignedShort); + c.setValue("zero", MeshIndexType{}); + CORRADE_COMPARE(c.value("zero"), ""); + CORRADE_COMPARE(c.value("zero"), MeshIndexType{}); + c.setValue("invalid", MeshIndexType(0xdead)); CORRADE_COMPARE(c.value("invalid"), ""); - CORRADE_COMPARE(c.value("invalid"), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(c.value("invalid"), MeshIndexType{}); } }}} diff --git a/src/Magnum/Vk/Enums.cpp b/src/Magnum/Vk/Enums.cpp index 75844d2380..6e8a69c47e 100644 --- a/src/Magnum/Vk/Enums.cpp +++ b/src/Magnum/Vk/Enums.cpp @@ -93,30 +93,30 @@ constexpr VkSamplerAddressMode SamplerAddressModeMapping[]{ } bool hasVkPrimitiveTopology(const Magnum::MeshPrimitive primitive) { - CORRADE_ASSERT(UnsignedInt(primitive) < Containers::arraySize(PrimitiveTopologyMapping), + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveTopologyMapping), "Vk::hasVkPrimitiveTopology(): invalid primitive" << primitive, {}); - return UnsignedInt(PrimitiveTopologyMapping[UnsignedInt(primitive)]) != ~UnsignedInt{}; + return UnsignedInt(PrimitiveTopologyMapping[UnsignedInt(primitive) - 1]) != ~UnsignedInt{}; } VkPrimitiveTopology vkPrimitiveTopology(const Magnum::MeshPrimitive primitive) { - CORRADE_ASSERT(UnsignedInt(primitive) < Containers::arraySize(PrimitiveTopologyMapping), + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveTopologyMapping), "Vk::vkPrimitiveTopology(): invalid primitive" << primitive, {}); - const VkPrimitiveTopology out = PrimitiveTopologyMapping[UnsignedInt(primitive)]; + const VkPrimitiveTopology out = PrimitiveTopologyMapping[UnsignedInt(primitive) - 1]; CORRADE_ASSERT(out != VkPrimitiveTopology(~UnsignedInt{}), "Vk::vkPrimitiveTopology(): unsupported primitive" << primitive, {}); return out; } bool hasVkIndexType(const Magnum::MeshIndexType type) { - CORRADE_ASSERT(UnsignedInt(type) < Containers::arraySize(IndexTypeMapping), + CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), "Vk::hasVkIndexType(): invalid type" << type, {}); - return UnsignedInt(IndexTypeMapping[UnsignedInt(type)]) != ~UnsignedInt{}; + return UnsignedInt(IndexTypeMapping[UnsignedInt(type) - 1]) != ~UnsignedInt{}; } VkIndexType vkIndexType(const Magnum::MeshIndexType type) { - CORRADE_ASSERT(UnsignedInt(type) < Containers::arraySize(IndexTypeMapping), + CORRADE_ASSERT(UnsignedInt(type) - 1 < Containers::arraySize(IndexTypeMapping), "Vk::vkIndexType(): invalid type" << type, {}); - const VkIndexType out = IndexTypeMapping[UnsignedInt(type)]; + const VkIndexType out = IndexTypeMapping[UnsignedInt(type) - 1]; CORRADE_ASSERT(out != VkIndexType(~UnsignedInt{}), "Vk::vkIndexType(): unsupported type" << type, {}); return out; diff --git a/src/Magnum/Vk/Test/EnumsTest.cpp b/src/Magnum/Vk/Test/EnumsTest.cpp index 3671f6638a..3896e1a5e3 100644 --- a/src/Magnum/Vk/Test/EnumsTest.cpp +++ b/src/Magnum/Vk/Test/EnumsTest.cpp @@ -157,10 +157,14 @@ void EnumsTest::mapVkPrimitiveTopologyInvalid() { std::ostringstream out; Error redirectError{&out}; + hasVkPrimitiveTopology(Magnum::MeshPrimitive{}); hasVkPrimitiveTopology(Magnum::MeshPrimitive(0x123)); + vkPrimitiveTopology(Magnum::MeshPrimitive{}); vkPrimitiveTopology(Magnum::MeshPrimitive(0x123)); CORRADE_COMPARE(out.str(), + "Vk::hasVkPrimitiveTopology(): invalid primitive MeshPrimitive(0x0)\n" "Vk::hasVkPrimitiveTopology(): invalid primitive MeshPrimitive(0x123)\n" + "Vk::vkPrimitiveTopology(): invalid primitive MeshPrimitive(0x0)\n" "Vk::vkPrimitiveTopology(): invalid primitive MeshPrimitive(0x123)\n"); } @@ -213,10 +217,14 @@ void EnumsTest::mapVkIndexTypeInvalid() { std::ostringstream out; Error redirectError{&out}; + hasVkIndexType(Magnum::MeshIndexType(0x0)); hasVkIndexType(Magnum::MeshIndexType(0x123)); + vkIndexType(Magnum::MeshIndexType(0x0)); vkIndexType(Magnum::MeshIndexType(0x123)); CORRADE_COMPARE(out.str(), + "Vk::hasVkIndexType(): invalid type MeshIndexType(0x0)\n" "Vk::hasVkIndexType(): invalid type MeshIndexType(0x123)\n" + "Vk::vkIndexType(): invalid type MeshIndexType(0x0)\n" "Vk::vkIndexType(): invalid type MeshIndexType(0x123)\n"); } From 520c22f2fe7220429478c75bf5d8c7f5ab00d940 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 4 Nov 2019 19:58:55 +0100 Subject: [PATCH 023/107] New VertexFormat enum. This deliberately doesn't follow the PixelFormat enum naming, as RGBA components make no sense for most vertex data. Checking Metal and WebGPU, they seem to have arrived at a similar conclusion, only VkFormat is an outlier. --- doc/changelog.dox | 1 + src/Magnum/CMakeLists.txt | 5 +- .../Implementation/vertexFormatMapping.hpp | 38 +++++ src/Magnum/Magnum.h | 1 + src/Magnum/PixelFormat.h | 3 +- src/Magnum/Test/CMakeLists.txt | 3 +- src/Magnum/Test/MeshTest.cpp | 3 + src/Magnum/Test/VertexFormatTest.cpp | 139 ++++++++++++++++++ src/Magnum/VertexFormat.cpp | 93 ++++++++++++ src/Magnum/VertexFormat.h | 114 ++++++++++++++ 10 files changed, 397 insertions(+), 3 deletions(-) create mode 100644 src/Magnum/Implementation/vertexFormatMapping.hpp create mode 100644 src/Magnum/Test/VertexFormatTest.cpp create mode 100644 src/Magnum/VertexFormat.cpp create mode 100644 src/Magnum/VertexFormat.h diff --git a/doc/changelog.dox b/doc/changelog.dox index 137ca77367..f4afb5015e 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -49,6 +49,7 @@ See also: @ref Vector2s, @ref Vector3s, @ref Vector4s, @ref Color3h, @ref Color4h, @ref Color3us, @ref Color4us convenience typedefs for half-float, 8- and 16-bit integer vector and color types +- New @ref VertexFormat enum for vertex formats and related utilities @subsubsection changelog-latest-new-audio Audio library diff --git a/src/Magnum/CMakeLists.txt b/src/Magnum/CMakeLists.txt index 37fc5c8284..a1253ba6cc 100644 --- a/src/Magnum/CMakeLists.txt +++ b/src/Magnum/CMakeLists.txt @@ -39,6 +39,7 @@ set(Magnum_GracefulAssert_SRCS ImageView.cpp Mesh.cpp PixelFormat.cpp + VertexFormat.cpp Animation/Player.cpp Animation/Interpolation.cpp) @@ -60,6 +61,7 @@ set(Magnum_HEADERS Tags.h Timeline.h Types.h + VertexFormat.h visibility.h) set(Magnum_PRIVATE_HEADERS @@ -68,7 +70,8 @@ set(Magnum_PRIVATE_HEADERS Implementation/meshIndexTypeMapping.hpp Implementation/meshPrimitiveMapping.hpp Implementation/compressedPixelFormatMapping.hpp - Implementation/pixelFormatMapping.hpp) + Implementation/pixelFormatMapping.hpp + Implementation/vertexFormatMapping.hpp) # Functionality specific to static Windows builds if(CORRADE_TARGET_WINDOWS AND NOT CORRADE_TARGET_WINDOWS_RT AND MAGNUM_BUILD_STATIC) diff --git a/src/Magnum/Implementation/vertexFormatMapping.hpp b/src/Magnum/Implementation/vertexFormatMapping.hpp new file mode 100644 index 0000000000..405eaa1bed --- /dev/null +++ b/src/Magnum/Implementation/vertexFormatMapping.hpp @@ -0,0 +1,38 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/* Each entry is just the name, for debug output and configuration to string */ +#ifdef _c +_c(Float) +_c(UnsignedByte) +_c(Byte) +_c(UnsignedShort) +_c(Short) +_c(UnsignedInt) +_c(Int) +_c(Vector2) +_c(Vector3) +_c(Vector4) +#endif diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index f169d18841..5def75eae0 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -862,6 +862,7 @@ typedef BasicMutableCompressedImageView<3> MutableCompressedImageView3D; enum class MeshPrimitive: UnsignedInt; enum class MeshIndexType: UnsignedInt; +enum class VertexFormat: UnsignedInt; enum class PixelFormat: UnsignedInt; enum class CompressedPixelFormat: UnsignedInt; diff --git a/src/Magnum/PixelFormat.h b/src/Magnum/PixelFormat.h index 58c4d756bb..bfcb746858 100644 --- a/src/Magnum/PixelFormat.h +++ b/src/Magnum/PixelFormat.h @@ -61,7 +61,8 @@ For D3D, corresponds to @m_class{m-doc-external} [DXGI_FORMAT](https://docs.micr and import is provided by the @ref Trade::DdsImporter "DdsImporter" plugin; for Metal, corresponds to @m_class{m-doc-external} [MTLPixelFormat](https://developer.apple.com/documentation/metal/mtlpixelformat?language=objc). See documentation of each value for more information about the mapping. -@see @ref pixelSize(), @ref CompressedPixelFormat, @ref Image, @ref ImageView +@see @ref pixelSize(), @ref CompressedPixelFormat, @ref Image, @ref ImageView, + @ref VertexFormat */ enum class PixelFormat: UnsignedInt { /* Zero reserved for an invalid format (but not being a named value) */ diff --git a/src/Magnum/Test/CMakeLists.txt b/src/Magnum/Test/CMakeLists.txt index c205c80f09..e46b1abf4c 100644 --- a/src/Magnum/Test/CMakeLists.txt +++ b/src/Magnum/Test/CMakeLists.txt @@ -34,8 +34,8 @@ corrade_add_test(PixelStorageTest PixelStorageTest.cpp LIBRARIES Magnum) corrade_add_test(ResourceManagerTest ResourceManagerTest.cpp LIBRARIES Magnum) target_compile_definitions(ResourceManagerTest PRIVATE "CORRADE_GRACEFUL_ASSERT") corrade_add_test(SamplerTest SamplerTest.cpp LIBRARIES MagnumTestLib) - corrade_add_test(TagsTest TagsTest.cpp LIBRARIES Magnum) +corrade_add_test(VertexFormatTest VertexFormatTest.cpp LIBRARIES MagnumTestLib) set_target_properties( ArrayTest @@ -47,4 +47,5 @@ set_target_properties( ResourceManagerTest SamplerTest TagsTest + VertexFormatTest PROPERTIES FOLDER "Magnum/Test") diff --git a/src/Magnum/Test/MeshTest.cpp b/src/Magnum/Test/MeshTest.cpp index c121a9c729..b1e63acf1e 100644 --- a/src/Magnum/Test/MeshTest.cpp +++ b/src/Magnum/Test/MeshTest.cpp @@ -29,6 +29,7 @@ #include #include "Magnum/Mesh.h" +#include "Magnum/Math/Vector4.h" namespace Magnum { namespace Test { namespace { @@ -43,6 +44,7 @@ struct MeshTest: TestSuite::Tester { void debugPrimitive(); void debugIndexType(); + void configurationPrimitive(); void configurationIndexType(); }; @@ -56,6 +58,7 @@ MeshTest::MeshTest() { &MeshTest::debugPrimitive, &MeshTest::debugIndexType, + &MeshTest::configurationPrimitive, &MeshTest::configurationIndexType}); } diff --git a/src/Magnum/Test/VertexFormatTest.cpp b/src/Magnum/Test/VertexFormatTest.cpp new file mode 100644 index 0000000000..a95fef885b --- /dev/null +++ b/src/Magnum/Test/VertexFormatTest.cpp @@ -0,0 +1,139 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include + +#include "Magnum/VertexFormat.h" +#include "Magnum/Math/Vector4.h" + +namespace Magnum { namespace Test { namespace { + +struct VertexFormatTest: TestSuite::Tester { + explicit VertexFormatTest(); + + void mapping(); + + void size(); + void sizeInvalid(); + + void debug(); + void configuration(); +}; + +VertexFormatTest::VertexFormatTest() { + addTests({&VertexFormatTest::mapping, + + &VertexFormatTest::size, + &VertexFormatTest::sizeInvalid, + + &VertexFormatTest::debug, + &VertexFormatTest::configuration}); +} + +void VertexFormatTest::mapping() { + /* This goes through the first 16 bits, which should be enough. Going + through 32 bits takes 8 seconds, too much. */ + UnsignedInt firstUnhandled = 0xffff; + UnsignedInt nextHandled = 1; /* 0 is an invalid type */ + for(UnsignedInt i = 1; i <= 0xffff; ++i) { + const auto type = VertexFormat(i); + /* Each case verifies: + - that the entries are ordered by number by comparing a function to + expected result (so insertion here is done in proper place) + - that there was no gap (unhandled value inside the range) */ + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" + #endif + switch(type) { + #define _c(type) \ + case VertexFormat::type: \ + CORRADE_COMPARE(Utility::ConfigurationValue::toString(VertexFormat::type, {}), #type); \ + CORRADE_COMPARE(nextHandled, i); \ + CORRADE_COMPARE(firstUnhandled, 0xffff); \ + ++nextHandled; \ + continue; + #include "Magnum/Implementation/vertexFormatMapping.hpp" + #undef _c + } + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + + /* Not handled by any value, remember -- we might either be at the end + of the enum range (which is okay) or some value might be unhandled + here */ + firstUnhandled = i; + } + + CORRADE_COMPARE(firstUnhandled, 0xffff); +} + +void VertexFormatTest::size() { + CORRADE_COMPARE(Magnum::vertexFormatSize(VertexFormat::Vector2), sizeof(Vector2)); + CORRADE_COMPARE(Magnum::vertexFormatSize(VertexFormat::Vector3), sizeof(Vector3)); + CORRADE_COMPARE(Magnum::vertexFormatSize(VertexFormat::Vector4), sizeof(Vector4)); +} + +void VertexFormatTest::sizeInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + Magnum::vertexFormatSize(VertexFormat{}); + Magnum::vertexFormatSize(VertexFormat(0xdead)); + + CORRADE_COMPARE(out.str(), + "vertexFormatSize(): invalid format VertexFormat(0x0)\n" + "vertexFormatSize(): invalid format VertexFormat(0xdead)\n"); +} + +void VertexFormatTest::debug() { + std::ostringstream o; + Debug(&o) << VertexFormat::Vector4 << VertexFormat(0xdead); + CORRADE_COMPARE(o.str(), "VertexFormat::Vector4 VertexFormat(0xdead)\n"); +} + +void VertexFormatTest::configuration() { + Utility::Configuration c; + + c.setValue("format", VertexFormat::Vector3); + CORRADE_COMPARE(c.value("format"), "Vector3"); + CORRADE_COMPARE(c.value("format"), VertexFormat::Vector3); + + c.setValue("zero", VertexFormat{}); + CORRADE_COMPARE(c.value("zero"), ""); + CORRADE_COMPARE(c.value("zero"), VertexFormat{}); + + c.setValue("invalid", VertexFormat(0xdead)); + CORRADE_COMPARE(c.value("invalid"), ""); + CORRADE_COMPARE(c.value("invalid"), VertexFormat{}); +} + +}}} + +CORRADE_TEST_MAIN(Magnum::Test::VertexFormatTest) diff --git a/src/Magnum/VertexFormat.cpp b/src/Magnum/VertexFormat.cpp new file mode 100644 index 0000000000..344c35c2ac --- /dev/null +++ b/src/Magnum/VertexFormat.cpp @@ -0,0 +1,93 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "VertexFormat.h" + +#include +#include +#include +#include + +namespace Magnum { + +UnsignedInt vertexFormatSize(const VertexFormat format) { + switch(format) { + case VertexFormat::UnsignedByte: + case VertexFormat::Byte: + return 1; + case VertexFormat::UnsignedShort: + case VertexFormat::Short: + return 2; + case VertexFormat::Float: + case VertexFormat::UnsignedInt: + case VertexFormat::Int: + return 4; + case VertexFormat::Vector2: return 8; + case VertexFormat::Vector3: return 12; + case VertexFormat::Vector4: return 16; + } + + CORRADE_ASSERT(false, "vertexFormatSize(): invalid format" << format, {}); +} + +namespace { + +constexpr const char* VertexFormatNames[] { + #define _c(format) #format, + #include "Magnum/Implementation/vertexFormatMapping.hpp" + #undef _c +}; + +} + +Debug& operator<<(Debug& debug, const VertexFormat value) { + debug << "VertexFormat" << Debug::nospace; + + if(UnsignedInt(value) - 1 < Containers::arraySize(VertexFormatNames)) { + return debug << "::" << Debug::nospace << VertexFormatNames[UnsignedInt(value) - 1]; + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; +} + +} + +namespace Corrade { namespace Utility { + +std::string ConfigurationValue::toString(Magnum::VertexFormat value, ConfigurationValueFlags) { + if(Magnum::UnsignedInt(value) - 1 < Containers::arraySize(Magnum::VertexFormatNames)) + return Magnum::VertexFormatNames[Magnum::UnsignedInt(value) - 1]; + + return {}; +} + +Magnum::VertexFormat ConfigurationValue::fromString(const std::string& stringValue, ConfigurationValueFlags) { + for(std::size_t i = 0; i != Containers::arraySize(Magnum::VertexFormatNames); ++i) + if(stringValue == Magnum::VertexFormatNames[i]) return Magnum::VertexFormat(i + 1); + + return {}; +} + +}} diff --git a/src/Magnum/VertexFormat.h b/src/Magnum/VertexFormat.h new file mode 100644 index 0000000000..1ecdcb7712 --- /dev/null +++ b/src/Magnum/VertexFormat.h @@ -0,0 +1,114 @@ +#ifndef Magnum_VertexFormat_h +#define Magnum_VertexFormat_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Enum @ref Magnum::VertexFormat, function @ref Magnum::vertexFormatSize() + */ + +#include + +#include "Magnum/Magnum.h" +#include "Magnum/visibility.h" + +namespace Magnum { + +/** +@brief Vertex format +@m_since_latest + +Like @ref PixelFormat, but for mesh attributes --- including double-precision +types and matrices. +*/ +enum class VertexFormat: UnsignedInt { + /* Zero reserved for an invalid type (but not being a named value) */ + + Float = 1, /**< @ref Float */ + UnsignedByte, /**< @ref UnsignedByte */ + Byte, /**< @ref Byte */ + UnsignedShort, /**< @ref UnsignedShort */ + Short, /**< @ref Short */ + UnsignedInt, /**< @ref UnsignedInt */ + Int, /**< @ref Int */ + + /** + * @ref Vector2. Usually used for 2D positions and 2D texture coordinates. + */ + Vector2, + + /** + * @ref Vector3 or @ref Color3. Usually used for 3D positions, normals and + * three-component colors. + */ + Vector3, + + /** + * @ref Vector4 or @ref Color4. Usually used for four-component colors. + */ + Vector4 +}; + +/** +@brief Size of given vertex format +@m_since_latest +*/ +MAGNUM_EXPORT UnsignedInt vertexFormatSize(VertexFormat format); + +/** +@debugoperatorenum{VertexFormat} +@m_since_latest +*/ +MAGNUM_EXPORT Debug& operator<<(Debug& debug, VertexFormat value); + +} + +namespace Corrade { namespace Utility { + +/** +@configurationvalue{Magnum::VertexFormat} +@m_since_latest +*/ +template<> struct MAGNUM_EXPORT ConfigurationValue { + ConfigurationValue() = delete; + + /** + * @brief Write enum value as string + * + * If the value is invalid, returns empty string. + */ + static std::string toString(Magnum::VertexFormat value, ConfigurationValueFlags); + + /** + * @brief Read enum value as string + * + * If the value is invalid, returns a zero (invalid) type. + */ + static Magnum::VertexFormat fromString(const std::string& stringValue, ConfigurationValueFlags); +}; + +}} + +#endif From d46061b285c224cd1a0d3834a2ac4ec660f1f453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 10 Nov 2019 11:47:49 +0100 Subject: [PATCH 024/107] Make MeshPrimitive and MeshIndexType enums only 8bit. Otherwise they take up too much space. --- src/Magnum/GL/Test/MeshTest.cpp | 8 ++++---- src/Magnum/Magnum.h | 4 ++-- src/Magnum/Mesh.h | 4 ++-- src/Magnum/Test/MeshTest.cpp | 12 ++++++------ src/Magnum/Vk/Test/EnumsTest.cpp | 16 ++++++++-------- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/Magnum/GL/Test/MeshTest.cpp b/src/Magnum/GL/Test/MeshTest.cpp index e91279b581..ec199c5272 100644 --- a/src/Magnum/GL/Test/MeshTest.cpp +++ b/src/Magnum/GL/Test/MeshTest.cpp @@ -182,10 +182,10 @@ void MeshTest::mapPrimitiveInvalid() { Error redirectError{&out}; meshPrimitive(Magnum::MeshPrimitive{}); - meshPrimitive(Magnum::MeshPrimitive(0x123)); + meshPrimitive(Magnum::MeshPrimitive(0x12)); CORRADE_COMPARE(out.str(), "GL::meshPrimitive(): invalid primitive MeshPrimitive(0x0)\n" - "GL::meshPrimitive(): invalid primitive MeshPrimitive(0x123)\n"); + "GL::meshPrimitive(): invalid primitive MeshPrimitive(0x12)\n"); } void MeshTest::mapIndexType() { @@ -221,10 +221,10 @@ void MeshTest::mapIndexTypeInvalid() { Error redirectError{&out}; meshIndexType(Magnum::MeshIndexType(0x0)); - meshIndexType(Magnum::MeshIndexType(0x123)); + meshIndexType(Magnum::MeshIndexType(0x12)); CORRADE_COMPARE(out.str(), "GL::meshIndexType(): invalid type MeshIndexType(0x0)\n" - "GL::meshIndexType(): invalid type MeshIndexType(0x123)\n"); + "GL::meshIndexType(): invalid type MeshIndexType(0x12)\n"); } void MeshTest::debugPrimitive() { diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index 5def75eae0..793e009202 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -860,8 +860,8 @@ typedef BasicMutableCompressedImageView<1> MutableCompressedImageView1D; typedef BasicMutableCompressedImageView<2> MutableCompressedImageView2D; typedef BasicMutableCompressedImageView<3> MutableCompressedImageView3D; -enum class MeshPrimitive: UnsignedInt; -enum class MeshIndexType: UnsignedInt; +enum class MeshPrimitive: UnsignedByte; +enum class MeshIndexType: UnsignedByte; enum class VertexFormat: UnsignedInt; enum class PixelFormat: UnsignedInt; diff --git a/src/Magnum/Mesh.h b/src/Magnum/Mesh.h index 23bf1762e6..6427117929 100644 --- a/src/Magnum/Mesh.h +++ b/src/Magnum/Mesh.h @@ -52,7 +52,7 @@ For D3D, corresponds to @m_class{m-doc-external} [D3D_PRIMITIVE_TOPOLOGY](https: for Metal, corresponds to @m_class{m-doc-external} [MTLPrimitiveType](https://developer.apple.com/documentation/metal/mtlprimitivetype?language=objc). See documentation of each value for more information about the mapping. */ -enum class MeshPrimitive: UnsignedInt { +enum class MeshPrimitive: UnsignedByte { /* Zero reserved for an invalid type (but not being a named value) */ /** @@ -148,7 +148,7 @@ for more information about the mapping. Note that not every type is available there, use @ref Vk::hasVkIndexType() to check for its presence. @see @ref meshIndexTypeSize() */ -enum class MeshIndexType: UnsignedInt { +enum class MeshIndexType: UnsignedByte { /* Zero reserved for an invalid type (but not being a named value) */ /** diff --git a/src/Magnum/Test/MeshTest.cpp b/src/Magnum/Test/MeshTest.cpp index b1e63acf1e..5a492aef80 100644 --- a/src/Magnum/Test/MeshTest.cpp +++ b/src/Magnum/Test/MeshTest.cpp @@ -150,23 +150,23 @@ void MeshTest::indexTypeSizeInvalid() { Error redirectError{&out}; meshIndexTypeSize(MeshIndexType{}); - meshIndexTypeSize(MeshIndexType(0xdead)); + meshIndexTypeSize(MeshIndexType(0xfe)); CORRADE_COMPARE(out.str(), "meshIndexTypeSize(): invalid type MeshIndexType(0x0)\n" - "meshIndexTypeSize(): invalid type MeshIndexType(0xdead)\n"); + "meshIndexTypeSize(): invalid type MeshIndexType(0xfe)\n"); } void MeshTest::debugPrimitive() { std::ostringstream o; - Debug(&o) << MeshPrimitive::TriangleFan << MeshPrimitive(0xdead); - CORRADE_COMPARE(o.str(), "MeshPrimitive::TriangleFan MeshPrimitive(0xdead)\n"); + Debug(&o) << MeshPrimitive::TriangleFan << MeshPrimitive(0xfe); + CORRADE_COMPARE(o.str(), "MeshPrimitive::TriangleFan MeshPrimitive(0xfe)\n"); } void MeshTest::debugIndexType() { std::ostringstream o; - Debug(&o) << MeshIndexType::UnsignedShort << MeshIndexType(0xdead); - CORRADE_COMPARE(o.str(), "MeshIndexType::UnsignedShort MeshIndexType(0xdead)\n"); + Debug(&o) << MeshIndexType::UnsignedShort << MeshIndexType(0xfe); + CORRADE_COMPARE(o.str(), "MeshIndexType::UnsignedShort MeshIndexType(0xfe)\n"); } void MeshTest::configurationPrimitive() { diff --git a/src/Magnum/Vk/Test/EnumsTest.cpp b/src/Magnum/Vk/Test/EnumsTest.cpp index 3896e1a5e3..05cf727929 100644 --- a/src/Magnum/Vk/Test/EnumsTest.cpp +++ b/src/Magnum/Vk/Test/EnumsTest.cpp @@ -158,14 +158,14 @@ void EnumsTest::mapVkPrimitiveTopologyInvalid() { Error redirectError{&out}; hasVkPrimitiveTopology(Magnum::MeshPrimitive{}); - hasVkPrimitiveTopology(Magnum::MeshPrimitive(0x123)); + hasVkPrimitiveTopology(Magnum::MeshPrimitive(0x12)); vkPrimitiveTopology(Magnum::MeshPrimitive{}); - vkPrimitiveTopology(Magnum::MeshPrimitive(0x123)); + vkPrimitiveTopology(Magnum::MeshPrimitive(0x12)); CORRADE_COMPARE(out.str(), "Vk::hasVkPrimitiveTopology(): invalid primitive MeshPrimitive(0x0)\n" - "Vk::hasVkPrimitiveTopology(): invalid primitive MeshPrimitive(0x123)\n" + "Vk::hasVkPrimitiveTopology(): invalid primitive MeshPrimitive(0x12)\n" "Vk::vkPrimitiveTopology(): invalid primitive MeshPrimitive(0x0)\n" - "Vk::vkPrimitiveTopology(): invalid primitive MeshPrimitive(0x123)\n"); + "Vk::vkPrimitiveTopology(): invalid primitive MeshPrimitive(0x12)\n"); } void EnumsTest::mapVkIndexType() { @@ -218,14 +218,14 @@ void EnumsTest::mapVkIndexTypeInvalid() { Error redirectError{&out}; hasVkIndexType(Magnum::MeshIndexType(0x0)); - hasVkIndexType(Magnum::MeshIndexType(0x123)); + hasVkIndexType(Magnum::MeshIndexType(0x12)); vkIndexType(Magnum::MeshIndexType(0x0)); - vkIndexType(Magnum::MeshIndexType(0x123)); + vkIndexType(Magnum::MeshIndexType(0x12)); CORRADE_COMPARE(out.str(), "Vk::hasVkIndexType(): invalid type MeshIndexType(0x0)\n" - "Vk::hasVkIndexType(): invalid type MeshIndexType(0x123)\n" + "Vk::hasVkIndexType(): invalid type MeshIndexType(0x12)\n" "Vk::vkIndexType(): invalid type MeshIndexType(0x0)\n" - "Vk::vkIndexType(): invalid type MeshIndexType(0x123)\n"); + "Vk::vkIndexType(): invalid type MeshIndexType(0x12)\n"); } void EnumsTest::mapVkFormatPixelFormat() { From ecbe5718b4ab2f3a409cf53c271c398d8ee48d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 4 Nov 2019 20:28:20 +0100 Subject: [PATCH 025/107] Trade: a reworked MeshData class. With API analogous to the (relatively) new AnimationData -- with one buffer containing all index data and one buffer containing all vertex data, both meant to be uploaded as-is to the GPU. This will eventually replace MeshData2D and MeshData3D, backwards compatibility and wiring up to other APIs will be done in follow-up commits. --- doc/changelog.dox | 3 + doc/snippets/MagnumTrade.cpp | 65 ++ src/Magnum/Shaders/Generic.h | 10 +- src/Magnum/Trade/CMakeLists.txt | 8 +- .../Trade/Implementation/arrayUtilities.h | 50 + src/Magnum/Trade/MeshData.cpp | 357 +++++++ src/Magnum/Trade/MeshData.h | 793 ++++++++++++++ src/Magnum/Trade/Test/CMakeLists.txt | 2 + src/Magnum/Trade/Test/MeshDataTest.cpp | 969 ++++++++++++++++++ src/Magnum/Trade/Trade.h | 6 + src/Magnum/VertexFormat.h | 2 + 11 files changed, 2261 insertions(+), 4 deletions(-) create mode 100644 src/Magnum/Trade/Implementation/arrayUtilities.h create mode 100644 src/Magnum/Trade/MeshData.cpp create mode 100644 src/Magnum/Trade/MeshData.h create mode 100644 src/Magnum/Trade/Test/MeshDataTest.cpp diff --git a/doc/changelog.dox b/doc/changelog.dox index f4afb5015e..e48b9e9905 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -160,6 +160,9 @@ See also: @subsubsection changelog-latest-new-trade Trade library +- A new, redesigned @ref Trade::MeshData class that allows much more flexible + access to vertex/index data without unnecessary allocations and data + conversions or copies - Ability to import image mip levels via an additional parameter in @ref Trade::AbstractImporter::image2D(), @ref Trade::AbstractImporter::image2DLevelCount() and similar APIs for 1D diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index 23c4caa94c..d9600728e2 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -30,12 +30,15 @@ #include "Magnum/FileCallback.h" #include "Magnum/ImageView.h" +#include "Magnum/Mesh.h" #include "Magnum/PixelFormat.h" +#include "Magnum/MeshTools/Interleave.h" #include "Magnum/Animation/Player.h" #include "Magnum/MeshTools/Transform.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/AnimationData.h" #include "Magnum/Trade/ImageData.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/ObjectData2D.h" @@ -43,6 +46,8 @@ #include "Magnum/Trade/PhongMaterialData.h" #ifdef MAGNUM_TARGET_GL #include "Magnum/GL/Texture.h" +#include "Magnum/GL/Mesh.h" +#include "Magnum/Shaders/Phong.h" #endif using namespace Magnum; @@ -201,6 +206,66 @@ else } #endif +#ifdef MAGNUM_TARGET_GL +{ +Trade::MeshData data{MeshPrimitive::Points, 0}; +/* [MeshData-usage] */ +/* Check that we have at least positions and normals */ +GL::Mesh mesh{data.primitive()}; +if(!data.hasAttribute(Trade::MeshAttribute::Position) || + !data.hasAttribute(Trade::MeshAttribute::Normal)) + Fatal{} << "Oh well"; + +/* Interleave vertex data */ +GL::Buffer vertices; +vertices.setData(MeshTools::interleave(data.positions3DAsArray(), + data.normalsAsArray())); +mesh.addVertexBuffer(std::move(vertices), 0, + Shaders::Phong::Position{}, Shaders::Phong::Normal{}); + +/* Set up an index buffer, if the mesh is indexed*/ +if(data.isIndexed()) { + GL::Buffer indices; + indices.setData(data.indicesAsArray()); + mesh.setIndexBuffer(std::move(indices), 0, MeshIndexType::UnsignedInt) + .setCount(data.indexCount()); +} else mesh.setCount(data.vertexCount()); +/* [MeshData-usage] */ +} + +{ +Trade::MeshData data{MeshPrimitive::Points, 0}; +GL::Mesh mesh{data.primitive()}; +/* [MeshData-usage-advanced] */ +/* Upload the original packed vertex data */ +GL::Buffer vertices; +vertices.setData(data.vertexData()); + +/* Set up the position attribute */ +Shaders::Phong::Position position; +auto positionFormat = data.attributeFormat(Trade::MeshAttribute::Position); +if(positionFormat == VertexFormat::Vector2) + position = {Shaders::Phong::Position::Components::Two}; +else if(positionFormat == VertexFormat::Vector3) + position = {Shaders::Phong::Position::Components::Three}; +else Fatal{} << "Huh?"; +mesh.addVertexBuffer(vertices, + data.attributeOffset(Trade::MeshAttribute::Position), + data.attributeStride(Trade::MeshAttribute::Position), position); + +// Set up other attributes ... + +/* Upload the original packed index data */ +if(data.isIndexed()) { + GL::Buffer indices; + indices.setData(data.indexData()); + mesh.setIndexBuffer(std::move(indices), 0, data.indexType()) + .setCount(data.indexCount()); +} else mesh.setCount(data.vertexCount()); +/* [MeshData-usage-advanced] */ +} +#endif + { Trade::MeshData2D& foo(); Trade::MeshData2D& data = foo(); diff --git a/src/Magnum/Shaders/Generic.h b/src/Magnum/Shaders/Generic.h index dd3bf90332..18f2238823 100644 --- a/src/Magnum/Shaders/Generic.h +++ b/src/Magnum/Shaders/Generic.h @@ -78,21 +78,23 @@ template struct Generic { * @brief Vertex position * * @ref Magnum::Vector2 "Vector2" in 2D and @ref Magnum::Vector3 "Vector3" - * in 3D. + * in 3D. Corresponds to @ref Trade::MeshAttribute::Position. */ typedef GL::Attribute<0, T> Position; /** * @brief 2D texture coordinates * - * @ref Magnum::Vector2 "Vector2". + * @ref Magnum::Vector2 "Vector2". Corresponds to + * @ref Trade::MeshAttribute::TextureCoordinates. */ typedef GL::Attribute<1, Vector2> TextureCoordinates; /** * @brief Vertex normal * - * @ref Magnum::Vector3 "Vector3", defined only in 3D. + * @ref Magnum::Vector3 "Vector3", defined only in 3D. Corresponds to + * @ref Trade::MeshAttribute::Normal. */ typedef GL::Attribute<2, Vector3> Normal; @@ -108,6 +110,7 @@ template struct Generic { * @brief Three-component vertex color. * * @ref Magnum::Color3. Use either this or the @ref Color4 attribute. + * Corresponds to @ref Trade::MeshAttribute::Color. */ typedef GL::Attribute<3, Magnum::Color3> Color3; @@ -115,6 +118,7 @@ template struct Generic { * @brief Four-component vertex color. * * @ref Magnum::Color4. Use either this or the @ref Color3 attribute. + * Corresponds to @ref Trade::MeshAttribute::Color. */ typedef GL::Attribute<3, Magnum::Color4> Color4; diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index 89d5163136..fc21d1d781 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -41,6 +41,7 @@ set(MagnumTrade_GracefulAssert_SRCS AnimationData.cpp CameraData.cpp ImageData.cpp + MeshData.cpp ObjectData2D.cpp ObjectData3D.cpp PhongMaterialData.cpp) @@ -53,6 +54,7 @@ set(MagnumTrade_HEADERS CameraData.h ImageData.h LightData.h + MeshData.h MeshData2D.h MeshData3D.h MeshObjectData2D.h @@ -66,6 +68,9 @@ set(MagnumTrade_HEADERS visibility.h) +set(MagnumTrade_PRIVATE_HEADERS + Implementation/arrayUtilities.h) + if(NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/configure.h) @@ -74,7 +79,8 @@ endif() # Objects shared between main and test library add_library(MagnumTradeObjects OBJECT ${MagnumTrade_SRCS} - ${MagnumTrade_HEADERS}) + ${MagnumTrade_HEADERS} + ${MagnumTrade_PRIVATE_HEADERS}) target_include_directories(MagnumTradeObjects PUBLIC $ $) diff --git a/src/Magnum/Trade/Implementation/arrayUtilities.h b/src/Magnum/Trade/Implementation/arrayUtilities.h new file mode 100644 index 0000000000..fc1420fde1 --- /dev/null +++ b/src/Magnum/Trade/Implementation/arrayUtilities.h @@ -0,0 +1,50 @@ +#ifndef Magnum_Trade_Implementation_arrayUtilities_h +#define Magnum_Trade_Implementation_arrayUtilities_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include + +#include "Magnum/Magnum.h" + +namespace Magnum { namespace Trade { namespace Implementation { + +/* Can't use InPlaceInit as that uses a custom deleters. Compared to + InPlaceInit it does an an unnecessary default-initialization of all + elements */ +/** @todo isn't there some C++56 feature that would allow me to allocate + without calling constructors? */ +template Containers::Array initializerListToArrayWithDefaultDeleter(const std::initializer_list list) { + Containers::Array out{list.size()}; + /* FFS why initializer list doesn't have an operator[] */ + std::size_t i = 0; + for(auto&& item: list) out[i++] = item; + return out; +} + +}}} + +#endif diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp new file mode 100644 index 0000000000..67c67cd01f --- /dev/null +++ b/src/Magnum/Trade/MeshData.cpp @@ -0,0 +1,357 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "MeshData.h" + +#include + +#include "Magnum/Math/Color.h" +#include "Magnum/Trade/Implementation/arrayUtilities.h" + +namespace Magnum { namespace Trade { + +MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayView data) noexcept: type{type}, data{reinterpret_cast&>(data)} { + CORRADE_ASSERT(!data.empty(), + "Trade::MeshIndexData: index array can't be empty, create a non-indexed mesh instead", ); + CORRADE_ASSERT(data.size()%meshIndexTypeSize(type) == 0, + "Trade::MeshIndexData: view size" << data.size() << "does not correspond to" << type, ); +} + +MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data) noexcept: name{name}, format{format}, data{data} { + /** @todo support zero / negative stride? would be hard to transfer to GL */ + CORRADE_ASSERT(data.empty() || std::ptrdiff_t(vertexFormatSize(format)) <= data.stride(), + "Trade::MeshAttributeData: view stride" << data.stride() << "is not large enough to contain" << format, ); + CORRADE_ASSERT( + (name == MeshAttribute::Position && + (format == VertexFormat::Vector2 || + format == VertexFormat::Vector3)) || + (name == MeshAttribute::Normal && + (format == VertexFormat::Vector3)) || + (name == MeshAttribute::Color && + (format == VertexFormat::Vector3 || + format == VertexFormat::Vector4)) || + (name == MeshAttribute::TextureCoordinates && + (format == VertexFormat::Vector2)) || + isMeshAttributeCustom(name) /* can be any format */, + "Trade::MeshAttributeData:" << format << "is not a valid format for" << name, ); +} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: _indexType{indices.type}, _primitive{primitive}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{indices.data} { + /* Save vertex count. It's a strided array view, so the size is not + depending on type. */ + if(_attributes.empty()) { + CORRADE_ASSERT(indices.type != MeshIndexType{}, + "Trade::MeshData: indices are expected to be valid if there are no attributes and vertex count isn't passed explicitly", ); + /** @todo some better value? attributeless indexed with defined vertex count? */ + _vertexCount = 0; + } else _vertexCount = _attributes[0].data.size(); + + CORRADE_ASSERT(!_indices.empty() || !_indexData, + "Trade::MeshData: indexData passed for a non-indexed mesh", ); + CORRADE_ASSERT(_indices.empty() || (_indices.begin() >= _indexData.begin() && _indices.end() <= _indexData.end()), + "Trade::MeshData: indices [" << Debug::nospace << static_cast(_indices.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_indices.end()) << Debug::nospace << "] are not contained in passed indexData array [" << Debug::nospace << static_cast(_indexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_indexData.end()) << Debug::nospace << "]", ); + CORRADE_ASSERT(!_attributes.empty() || !_vertexData, + "Trade::MeshData: vertexData passed for an attribute-less mesh", ); + CORRADE_ASSERT(_vertexCount || !_vertexData, + "Trade::MeshData: vertexData passed for a mesh with zero vertices", ); + + #ifndef CORRADE_NO_ASSERT + /* Not checking what's already checked in MeshIndexData / MeshAttributeData + constructors */ + for(std::size_t i = 0; i != _attributes.size(); ++i) { + const MeshAttributeData& attribute = _attributes[i]; + CORRADE_ASSERT(attribute.data.size() == _vertexCount, + "Trade::MeshData: attribute" << i << "has" << attribute.data.size() << "vertices but" << _vertexCount << "expected", ); + CORRADE_ASSERT(attribute.data.empty() || (&attribute.data.front() >= _vertexData.begin() && &attribute.data.back() + vertexFormatSize(attribute.format) <= _vertexData.end()), + "Trade::MeshData: attribute" << i << "[" << Debug::nospace << static_cast(&attribute.data.front()) << Debug::nospace << ":" << Debug::nospace << static_cast(&attribute.data.back() + vertexFormatSize(attribute.format)) << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); + } + #endif +} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* const importerState): MeshData{primitive, std::move(indexData), indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, std::move(vertexData), std::move(attributes), importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, {}, {}, importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, const UnsignedInt vertexCount, const void* const importerState) noexcept: _vertexCount{vertexCount}, _indexType{}, _primitive{primitive}, _importerState{importerState} {} + +MeshData::~MeshData() = default; + +MeshData::MeshData(MeshData&&) noexcept = default; + +MeshData& MeshData::operator=(MeshData&&) noexcept = default; + +UnsignedInt MeshData::indexCount() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indexCount(): the mesh is not indexed", {}); + return _indices.size()/meshIndexTypeSize(_indexType); +} + +MeshIndexType MeshData::indexType() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indexType(): the mesh is not indexed", {}); + return _indexType; +} + +MeshAttribute MeshData::attributeName(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeName(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id].name; +} + +VertexFormat MeshData::attributeFormat(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeFormat(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id].format; +} + +std::size_t MeshData::attributeOffset(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return static_cast(_attributes[id].data.data()) - _vertexData.data(); +} + +UnsignedInt MeshData::attributeStride(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeStride(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id].data.stride(); +} + +UnsignedInt MeshData::attributeCount(const MeshAttribute name) const { + UnsignedInt count = 0; + for(const MeshAttributeData& attribute: _attributes) + if(attribute.name == name) ++count; + return count; +} + +UnsignedInt MeshData::attributeFor(const MeshAttribute name, UnsignedInt id) const { + for(std::size_t i = 0; i != _attributes.size(); ++i) { + if(_attributes[i].name != name) continue; + if(id-- == 0) return i; + } + + #ifdef CORRADE_NO_ASSERT + CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + #else + return ~UnsignedInt{}; + #endif +} + +VertexFormat MeshData::attributeFormat(MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeFormat(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeFormat(attributeId); +} + +std::size_t MeshData::attributeOffset(MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeOffset(attributeId); +} + +UnsignedInt MeshData::attributeStride(MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeStride(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeStride(attributeId); +} + +namespace { + +template void convertIndices(const Containers::ArrayView data, const Containers::ArrayView destination) { + const auto input = Containers::arrayCast(data); + for(std::size_t i = 0; i != input.size(); ++i) destination[i] = input[i]; +} + +} + +void MeshData::indicesInto(const Containers::ArrayView destination) const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indicesInto(): the mesh is not indexed", ); + CORRADE_ASSERT(destination.size() == indexCount(), "Trade::MeshData::indicesInto(): expected a view with" << indexCount() << "elements but got" << destination.size(), ); + + switch(_indexType) { + case MeshIndexType::UnsignedByte: return convertIndices(_indices, destination); + case MeshIndexType::UnsignedShort: return convertIndices(_indices, destination); + case MeshIndexType::UnsignedInt: return convertIndices(_indices, destination); + } + + CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::indicesAsArray() const { + /* Repeating the assert here because otherwise it would fire in + indexCount() which may be confusing */ + CORRADE_ASSERT(isIndexed(), "Trade::MeshData::indicesAsArray(): the mesh is not indexed", {}); + Containers::Array output{indexCount()}; + indicesInto(output); + return output; +} + +void MeshData::positions2DInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Position, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + + /* Copy 2D positions as-is, for 3D positions ignore Z */ + if(attribute.format == VertexFormat::Vector2 || + attribute.format == VertexFormat::Vector3) + Utility::copy(Containers::arrayCast(attribute.data), destination); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::positions2DAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + positions2DInto(out, id); + return out; +} + +void MeshData::positions3DInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Position, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions3DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions3DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + + /* For 2D positions copy the XY part to the first two components and then + fill the Z with a single value */ + if(attribute.format == VertexFormat::Vector2) { + Utility::copy(Containers::arrayCast(attribute.data), + Containers::arrayCast(destination)); + constexpr Float z[1]{0.0f}; + Utility::copy( + Containers::stridedArrayView(z).broadcasted<0>(_vertexCount), + Containers::arrayCast<2, Float>(destination).transposed<0, 1>()[2]); + /* Copy 3D positions as-is */ + } else if(attribute.format == VertexFormat::Vector3) { + Utility::copy(Containers::arrayCast(attribute.data), destination); + } else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::positions3DAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + positions3DInto(out, id); + return out; +} + +void MeshData::normalsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Normal, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal) << "normal attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::normalsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + + if(attribute.format == VertexFormat::Vector3) + Utility::copy(Containers::arrayCast(attribute.data), destination); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::normalsAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + normalsInto(out, id); + return out; +} + +void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::TextureCoordinates, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::textureCoordinates2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::TextureCoordinates) << "texture coordinate attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::textureCoordinates2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + + if(attribute.format == VertexFormat::Vector2) + Utility::copy(Containers::arrayCast(attribute.data), destination); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::textureCoordinates2DAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + textureCoordinates2DInto(out, id); + return out; +} + +void MeshData::colorsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Color, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::colorsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Color) << "color attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::colorsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + + /* For three-component colors copy the RGB part to the first three + components and then fill the alpha with a single value */ + if(attribute.format == VertexFormat::Vector3) { + Utility::copy(Containers::arrayCast(attribute.data), + Containers::arrayCast(destination)); + constexpr Float alpha[1]{1.0f}; + Utility::copy( + Containers::stridedArrayView(alpha).broadcasted<0>(_vertexCount), + Containers::arrayCast<2, Float>(destination).transposed<0, 1>()[3]); + /* Copy four-component colors as-is */ + } else if(attribute.format == VertexFormat::Vector4) { + Utility::copy(Containers::arrayCast(attribute.data), + Containers::arrayCast(destination)); + } else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::colorsAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + colorsInto(out, id); + return out; +} + +Containers::Array MeshData::releaseIndexData() { + _indexType = MeshIndexType{}; /* so isIndexed() returns false */ + _indices = nullptr; + return std::move(_indexData); +} + +Containers::Array MeshData::releaseVertexData() { + _attributes = nullptr; + return std::move(_vertexData); +} + +Debug& operator<<(Debug& debug, const MeshAttribute value) { + debug << "Trade::MeshAttribute" << Debug::nospace; + + if(UnsignedShort(value) >= UnsignedShort(MeshAttribute::Custom)) + return debug << "::Custom(" << Debug::nospace << (UnsignedShort(value) - UnsignedShort(MeshAttribute::Custom)) << Debug::nospace << ")"; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case MeshAttribute::value: return debug << "::" << Debug::nospace << #value; + _c(Position) + _c(Normal) + _c(TextureCoordinates) + _c(Color) + #undef _c + /* LCOV_EXCL_STOP */ + + /* To silence compiler warning about unhandled values */ + case MeshAttribute::Custom: CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedShort(value)) << Debug::nospace << ")"; +} + +}} diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h new file mode 100644 index 0000000000..6df4f05fab --- /dev/null +++ b/src/Magnum/Trade/MeshData.h @@ -0,0 +1,793 @@ +#ifndef Magnum_Trade_MeshData_h +#define Magnum_Trade_MeshData_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Trade::MeshData, @ref Magnum::Trade::MeshIndexData, @ref Magnum::Trade::MeshAttributeData, enum @ref Magnum::Trade::MeshAttribute, function @ref Magnum::Trade::isMeshAttributeCustom(), @ref Magnum::Trade::meshAttributeCustom() + * @m_since_latest + */ + +#include +#include + +#include "Magnum/Mesh.h" +#include "Magnum/VertexFormat.h" +#include "Magnum/Trade/Trade.h" +#include "Magnum/Trade/visibility.h" + +namespace Magnum { namespace Trade { + +/** +@brief Mesh attribute name +@m_since_latest + +@see @ref MeshData, @ref MeshAttributeData, @ref VertexFormat +*/ +/* 16 bits because 8 bits is not enough to cover all potential per-edge, + per-face, per-instance and per-meshlet attributes */ +enum class MeshAttribute: UnsignedShort { + /** + * Position. Type is usually @ref Magnum::Vector2 "Vector2" for 2D and + * @ref Magnum::Vector3 "Vector3" for 3D. Corresponds to + * @ref Shaders::Generic::Position. + * @see @ref VertexFormat::Vector2, @ref VertexFormat::Vector3, + * @ref MeshData::positions2DAsArray(), + * @ref MeshData::positions3DAsArray() + */ + Position, + + /** + * Normal. Type is usually @ref Magnum::Vector3 "Vector3". Corresponds to + * @ref Shaders::Generic::Normal. + * @see @ref VertexFormat::Vector3, @ref MeshData::normalsAsArray() + */ + Normal, + + /** + * Texture coordinates. Type is usually @ref Magnum::Vector2 "Vector2" for + * 2D coordinates. Corresponds to @ref Shaders::Generic::TextureCoordinates. + * @see @ref VertexFormat::Vector2, + * @ref MeshData::textureCoordinates2DAsArray() + */ + TextureCoordinates, + + /** + * Vertex color. Type is usually @ref Magnum::Vector3 "Vector3" or + * @ref Magnum::Vector4 "Vector4" (or @ref Color3 / @ref Color4). + * Corresponds to @ref Shaders::Generic::Color3 or + * @ref Shaders::Generic::Color4. + * @see @ref VertexFormat::Vector3, @ref VertexFormat::Vector4, + * @ref MeshData::colorsAsArray() + */ + Color, + + /** + * This and all higher values are for importer-specific attributes. Can be + * of any type. See documentation of a particular importer for details. + * @see @ref isMeshAttributeCustom(MeshAttribute) + * @ref meshAttributeCustom(MeshAttribute), + * @ref meshAttributeCustom(UnsignedShort) + */ + Custom = 32768 +}; + +/** +@debugoperatorenum{MeshAttribute} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, MeshAttribute value); + +/** +@brief Whether a mesh attribute is custom +@m_since_latest + +Returns @cpp true @ce if @p name has a value larger or equal to +@ref MeshAttribute::Custom, @cpp false @ce otherwise. +@see @ref meshAttributeCustom(UnsignedShort), + @ref meshAttributeCustom(MeshAttribute) +*/ +constexpr bool isMeshAttributeCustom(MeshAttribute name) { + return UnsignedShort(name) >= UnsignedShort(MeshAttribute::Custom); +} + +/** +@brief Create a custom mesh attribute +@m_since_latest + +Returns a custom mesh attribute with index @p id. The index is expected to be +less than the value of @ref MeshAttribute::Custom. Use +@ref meshAttributeCustom(MeshAttribute) to get the index back. +*/ +/* Constexpr so it's usable for creating compile-time MeshAttributeData + instances */ +constexpr MeshAttribute meshAttributeCustom(UnsignedShort id) { + return CORRADE_CONSTEXPR_ASSERT(id < UnsignedShort(MeshAttribute::Custom), + "Trade::meshAttributeCustom(): index" << id << "too large"), + MeshAttribute(UnsignedShort(MeshAttribute::Custom) + id); +} + +/** +@brief Get index of a custom mesh attribute +@m_since_latest + +Inverse to @ref meshAttributeCustom(UnsignedShort). Expects that the attribute +is custom. +@see @ref isMeshAttributeCustom() +*/ +constexpr UnsignedShort meshAttributeCustom(MeshAttribute name) { + return CORRADE_CONSTEXPR_ASSERT(isMeshAttributeCustom(name), + "Trade::meshAttributeCustom():" << name << "is not custom"), + UnsignedShort(name) - UnsignedShort(MeshAttribute::Custom); +} + +/** +@brief Mesh index data +@m_since_latest + +Convenience type for populating @ref MeshData. Has no accessors, as the data +are then accessible through @ref MeshData APIs. +@see @ref MeshAttributeData +*/ +class MAGNUM_TRADE_EXPORT MeshIndexData { + public: + /** @brief Construct for a non-indexed mesh */ + explicit MeshIndexData() noexcept: type{} {} + + /** + * @brief Construct with a runtime-specified index type + * @param type Mesh index type + * @param data Index data + * + * The @p data size is expected to correspond to given @p type (e.g., + * for @ref MeshIndexType::UnsignedInt the @p data array size should + * be divisible by 4). If you know the @p type at compile time, you can + * use one of the @ref MeshIndexData(Containers::ArrayView), + * @ref MeshIndexData(Containers::ArrayView) or + * @ref MeshIndexData(Containers::ArrayView) + * constructors, which infer the index type automatically. + */ + explicit MeshIndexData(MeshIndexType type, Containers::ArrayView data) noexcept; + + /** @brief Construct with unsigned byte indices */ + explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedByte, data} {} + + /** @brief Construct with unsigned short indices */ + explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedShort, data} {} + + /** @brief Construct with unsigned int indices */ + explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedInt, data} {} + + private: + /* Not prefixed with _ because we use them like public in MeshData */ + friend MeshData; + MeshIndexType type; + Containers::ArrayView data; +}; + +/** +@brief Mesh attribute data +@m_since_latest + +Convenience type for populating @ref MeshData. Has no accessors, as the data +are then accessible through @ref MeshData APIs. +*/ +class MAGNUM_TRADE_EXPORT MeshAttributeData { + public: + /** + * @brief Default constructor + * + * Leaves contents at unspecified values. Provided as a convenience for + * initialization of the attribute array for @ref MeshData, expected to + * be replaced with concrete values later. + */ + explicit MeshAttributeData() noexcept: name{}, format{}, data{} {} + + /** + * @brief Type-erased constructor + * @param name Attribute name + * @param format Vertex format + * @param data Attribute data + * + * Expects that @p data stride is large enough to fit @p type and that + * @p type corresponds to @p name. + */ + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data) noexcept; + + /** + * @brief Constructor + * @param name Attribute name + * @param data Attribute data + * + * Detects @ref VertexFormat based on @p T and calls + * @ref MeshAttributeData(MeshAttribute, VertexFormat, const Containers::StridedArrayView1D&). + */ + template explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept; + + /** @overload */ + template explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {} + + private: + /* Not prefixed with _ because we use them like public in MeshData */ + friend MeshData; + MeshAttribute name; + /* Here's some room for flags */ + VertexFormat format; + Containers::StridedArrayView1D data; +}; + +/** +@brief Mesh data +@m_since_latest + +Provides access to mesh vertex and index data, together with additional +information such as primitive type. + +@section Trade-MeshData-usage Basic usage + +The simplest usage is through the convenience functions @ref positions2DAsArray(), +@ref positions3DAsArray(), @ref normalsAsArray(), @ref textureCoordinates2DAsArray() +and @ref colorsAsArray(). Each of these takes an index (as there can be +multiple sets of texture coordinates, for example) and you're expected to check +for attribute presence first with either @ref hasAttribute() or +@ref attributeCount(MeshAttribute) const: + +@snippet MagnumTrade.cpp MeshData-usage + +@section Trade-MeshData-usage-advanced Advanced usage + +The @ref positions2DAsArray(), ... functions shown above always return a +newly-allocated @ref Corrade::Containers::Array instance with a clearly defined +type that's large enough to represent most data. While that's fine for many use +cases, sometimes you may want to minimize the import time of a large model or +the imported data may be already in a well-optimized layout and format that you +want to preserve. The @ref MeshData class internally stores a contiguous blob +of data, which you can directly upload, and then use provided metadata to let +the GPU know of the format and layout: + +@snippet MagnumTrade.cpp MeshData-usage-advanced +*/ +class MAGNUM_TRADE_EXPORT MeshData { + public: + /** + * @brief Construct an indexed mesh data + * @param primitive Primitive + * @param indexData Index data + * @param indices Index data description + * @param vertexData Vertex data + * @param attributes Description of all vertex attribute data + * @param importerState Importer-specific state + * + * The @p indices are expected to point to a sub-range of @p indexData. + * The @p attributes are expected to reference (sparse) sub-ranges of + * @p vertexData. If the mesh has no attributes, the @p indices are + * expected to be valid and non-empty. If you want to create an + * index-less attribute-less mesh, use + * @ref MeshData(MeshPrimitive, UnsignedInt, const void*) to specify + * desired vertex count. + */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + + /** + * @brief Construct a non-indexed mesh data + * @param primitive Primitive + * @param vertexData Vertex data + * @param attributes Description of all vertex attribute data + * @param importerState Importer-specific state + * + * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * with default-constructed @p indexData and @p indices arguments. + */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + + /** + * @brief Construct an attribute-less indexed mesh data + * @param primitive Primitive + * @param indexData Index data + * @param indices Index data description + * @param importerState Importer-specific state + * + * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * with default-constructed @p vertexData and @p attributes arguments. + * The @p indices are expected to be valid and non-empty. If you want + * to create an index-less attribute-less mesh, use + * @ref MeshData(MeshPrimitive, UnsignedInt, const void*) to specify + * desired vertex count. + */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an index-less attribute-less mesh data + * @param primitive Primitive + * @param vertexCount Desired count of vertices to draw + * @param importerState Importer-specific state + * + * Useful in case the drawing is fully driven by a shader. + */ + explicit MeshData(MeshPrimitive primitive, UnsignedInt vertexCount, const void* importerState = nullptr) noexcept; + + ~MeshData(); + + /** @brief Copying is not allowed */ + MeshData(const MeshData&) = delete; + + /** @brief Move constructor */ + MeshData(MeshData&&) noexcept; + + /** @brief Copying is not allowed */ + MeshData& operator=(const MeshData&) = delete; + + /** @brief Move assignment */ + MeshData& operator=(MeshData&&) noexcept; + + /** @brief Primitive */ + MeshPrimitive primitive() const { return _primitive; } + + /** + * @brief Raw index data + * + * Returns @cpp nullptr @ce if the mesh is not indexed. + * @see @ref isIndexed(), @ref indexCount(), @ref indexType(), + * @ref indices(), @ref releaseIndexData() + */ + Containers::ArrayView indexData() const & { return _indexData; } + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView indexData() const && = delete; + + /** + * @brief Raw vertex data + * + * Contains data for all vertex attributes. Returns @cpp nullptr @ce if + * the mesh has no attributes. + * @see @ref attributeCount(), @ref attributeName(), + * @ref attributeFormat(), @ref attribute(), + * @ref releaseVertexData() + */ + Containers::ArrayView vertexData() const & { return _vertexData; } + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView vertexData() const && = delete; + + /** @brief Whether the mesh is indexed */ + bool isIndexed() const { return _indexType != MeshIndexType{}; } + + /** + * @brief Index count + * + * Count of elements in the @ref indices() array. Expects that the + * mesh is indexed; returned value is always non-zero. See also + * @ref vertexCount() which returns count of elements in every + * @ref attribute() array, and @ref attributeCount() which returns + * count of different per-vertex attribute arrays. + * @see @ref isIndexed(), @ref indexType() + */ + UnsignedInt indexCount() const; + + /** + * @brief Index type + * + * Expects that the mesh is indexed. + * @see @ref isIndexed(), @ref attributeFormat() + */ + MeshIndexType indexType() const; + + /** + * @brief Mesh indices + * + * Expects that the mesh is indexed and that @p T corresponds to + * @ref indexType(). You can also use the non-templated + * @ref indicesAsArray() accessor to get indices converted to 32-bit, + * but note that such operation involves extra allocation and data + * conversion. + * @see @ref isIndexed(), @ref attribute() + */ + template Containers::ArrayView indices() const; + + /** + * @brief Mesh vertex count + * + * Count of elements in every attribute array returned by + * @ref attribute() (or, in case of an attribute-less mesh, the + * desired vertex count). See also @ref indexCount() which returns + * count of elements in the @ref indices() array, and + * @ref attributeCount() which returns count of different per-vertex + * attribute arrays. + */ + UnsignedInt vertexCount() const { return _vertexCount; } + + /** + * @brief Attribute array count + * + * Count of different per-vertex attribute arrays, or @cpp 0 @ce for an + * attribute-less mesh. See also @ref indexCount() which returns count + * of elements in the @ref indices() array and @ref vertexCount() which + * returns count of elements in every @ref attribute() array. + * @see @ref attributeCount(MeshAttribute) const + */ + UnsignedInt attributeCount() const { return _attributes.size(); } + + /** + * @brief Attribute name + * + * The @p id is expected to be smaller than @ref attributeCount() const. + * @see @ref attributeFormat(), @ref isMeshAttributeCustom() + */ + MeshAttribute attributeName(UnsignedInt id) const; + + /** + * @brief Attribute format + * + * The @p id is expected to be smaller than @ref attributeCount() const. + * You can also use @ref attributeFormat(MeshAttribute, UnsignedInt) const + * to directly get a type of given named attribute. + * @see @ref attributeName(), @ref indexType() + */ + VertexFormat attributeFormat(UnsignedInt id) const; + + /** + * @brief Attribute offset + * + * Byte offset of the first element of given attribute from the + * beginning of the @ref vertexData() array, or a byte difference + * between pointers returned from @ref vertexData() and a particular + * @ref attribute(). The @p id is expected to be smaller than + * @ref attributeCount() const. You can also use + * @ref attributeOffset(MeshAttribute, UnsignedInt) const to + * directly get an offset of given named attribute. + */ + std::size_t attributeOffset(UnsignedInt id) const; + + /** + * @brief Attribute stride + * + * Stride between consecutive elements of given attribute in the + * @ref vertexData() array. The @p id is expected to be smaller + * than @ref attributeCount() const. You can also use + * @ref attributeStride(MeshAttribute, UnsignedInt) const to + * directly get a stride of given named attribute. + */ + UnsignedInt attributeStride(UnsignedInt id) const; + + /** + * @brief Whether the mesh has given attribute + * + * @see @ref attributeCount(MeshAttribute) const + */ + bool hasAttribute(MeshAttribute name) const { + return attributeCount(name); + } + + /** + * @brief Count of given named attribute + * + * Unlike @ref attributeCount() const this returns count for given + * attribute name --- for example a mesh can have more than one set of + * texture coordinates. + * @see @ref hasAttribute() + */ + UnsignedInt attributeCount(MeshAttribute name) const; + + /** + * @brief Format of a named attribute + * + * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const. + * @see @ref attributeFormat(UnsignedInt) const + */ + VertexFormat attributeFormat(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Offset of a named attribute + * + * Byte offset of the first element of given named attribute from the + * beginning of the @ref vertexData() array. The @p id is expected to + * be smaller than @ref attributeCount(MeshAttribute) const. + * @see @ref attributeOffset(UnsignedInt) const + */ + std::size_t attributeOffset(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Stride of a named attribute + * + * Stride between consecutive elements of given named attribute in the + * @ref vertexData() array. The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const. + * @see @ref attributeStride(UnsignedInt) const + */ + UnsignedInt attributeStride(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Data for given attribute array + * + * The @p id is expected to be smaller than @ref attributeCount() const + * and @p T is expected to correspond to + * @ref attributeFormat(UnsignedInt) const. You can also use the + * non-templated @ref positions2DAsArray(), @ref positions3DAsArray(), + * @ref normalsAsArray(), @ref textureCoordinates2DAsArray() and + * @ref colorsAsArray() accessors to get common attributes converted to + * usual types, but note that these operations involve extra allocation + * and data conversion. + * @see @ref attribute(MeshAttribute, UnsignedInt) const + */ + template Containers::StridedArrayView1D attribute(UnsignedInt id) const; + + /** + * @brief Data for given named attribute array + * + * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const and @p T is expected to + * correspond to @ref attributeFormat(MeshAttribute, UnsignedInt) const. + * You can also use the non-templated @ref positions2DAsArray(), + * @ref positions3DAsArray(), @ref normalsAsArray(), + * @ref textureCoordinates2DAsArray() and @ref colorsAsArray() + * accessors to get common attributes converted to usual types, but + * note that these operations involve extra data conversion and an + * allocation. + * @see @ref attribute(UnsignedInt) const + */ + template Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Indices as 32-bit integers + * + * Convenience alternative to the templated @ref indices(). Converts + * the index array from an arbitrary underlying type and returns it in + * a newly-allocated array. + * @see @ref indicesInto() + */ + Containers::Array indicesAsArray() const; + + /** + * @brief Positions as 32-bit integers into a pre-allocated view + * + * Like @ref indicesAsArray(), but puts the result into @p destination + * instead of allocating a new array. Expects that @p destination is + * sized to contain exactly all data. + * @see @ref indexCount() + */ + void indicesInto(Containers::ArrayView destination) const; + + /** + * @brief Positions as 2D float vectors + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * with @ref MeshAttribute::Position as the first argument. Converts + * the position array from an arbitrary underlying type and returns it + * in a newly-allocated array. If the underlying type is + * three-component, the last component is dropped. + * @see @ref positions2DInto() + */ + Containers::Array positions2DAsArray(UnsignedInt id = 0) const; + + /** + * @brief Positions as 2D float vectors into a pre-allocated view + * + * Like @ref positions2DAsArray(), but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref vertexCount() + */ + void positions2DInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Positions as 3D float vectors + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * with @ref MeshAttribute::Position as the first argument. Converts + * the position array from an arbitrary underlying type and returns it + * in a newly-allocated array. If the underlying type is two-component, + * the Z component is set to @cpp 0.0f @ce. + * @see @ref positions3DInto() + */ + Containers::Array positions3DAsArray(UnsignedInt id = 0) const; + + /** + * @brief Positions as 3D float vectors into a pre-allocated view + * + * Like @ref positions3DAsArray(), but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref vertexCount() + */ + void positions3DInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Normals as 3D float vectors + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * with @ref MeshAttribute::Normal as the first argument. Converts the + * normal array from an arbitrary underlying type and returns it in a + * newly-allocated array. + * @see @ref normalsInto() + */ + Containers::Array normalsAsArray(UnsignedInt id = 0) const; + + /** + * @brief Normals as 3D float vectors into a pre-allocated view + * + * Like @ref normalsAsArray(), but puts the result into @p destination + * instead of allocating a new array. Expects that @p destination is + * sized to contain exactly all data. + * @see @ref vertexCount() + */ + void normalsInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Texture coordinates as 2D float vectors + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * with @ref MeshAttribute::TextureCoordinates as the first argument. + * Converts the texture coordinate array from an arbitrary underlying + * type and returns it in a newly-allocated array. + * @see @ref textureCoordinates2DInto() + */ + Containers::Array textureCoordinates2DAsArray(UnsignedInt id = 0) const; + + /** + * @brief Texture coordinates as 2D float vectors into a pre-allocated view + * + * Like @ref textureCoordinates2DAsArray(), but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref vertexCount() + */ + void textureCoordinates2DInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Colors as RGBA floats + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * with @ref MeshAttribute::Color as the first argument. Converts the + * color array from an arbitrary underlying type and returns it in a + * newly-allocated array. If the underlying type is three-component, + * the alpha component is set to @cpp 1.0f @ce. + * @see @ref colorsInto() + */ + Containers::Array colorsAsArray(UnsignedInt id = 0) const; + + /** + * @brief Colors as RGBA floats into a pre-allocated view + * + * Like @ref colorsAsArray(), but puts the result into @p destination + * instead of allocating a new array. Expects that @p destination is + * sized to contain exactly all data. + * @see @ref vertexCount() + */ + void colorsInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Release index data storage + * + * Releases the ownership of the index data array and resets internal + * index-related state to default. The mesh then behaves like + * non-indexed. + * @see @ref indexData() + */ + Containers::Array releaseIndexData(); + + /** + * @brief Release vertex data storage + * + * Releases the ownership of the index data array and resets internal + * attribute-related state to default. The mesh then behaves like if + * it has no attributes. + * @see @ref vertexData() + */ + Containers::Array releaseVertexData(); + + /** + * @brief Importer-specific state + * + * See @ref AbstractImporter::importerState() for more information. + */ + const void* importerState() const { return _importerState; } + + private: + UnsignedInt attributeFor(MeshAttribute name, UnsignedInt id) const; + + UnsignedInt _vertexCount; + MeshIndexType _indexType; + MeshPrimitive _primitive; + const void* _importerState; + Containers::Array _indexData, _vertexData; + Containers::Array _attributes; + /* MeshIndexData are "unpacked" in order to avoid excessive padding */ + Containers::ArrayView _indices; +}; + +#if !defined(CORRADE_NO_ASSERT) || defined(CORRADE_GRACEFUL_ASSERT) +namespace Implementation { + /* LCOV_EXCL_START */ + template constexpr MeshIndexType meshIndexTypeFor() { + /* C++ why there isn't an obvious way to do such a thing?! */ + static_assert(sizeof(T) == 0, "unsupported index type"); + return {}; + } + template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedByte; } + template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedShort; } + template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedInt; } + + template constexpr VertexFormat vertexFormatFor() { + /* C++ why there isn't an obvious way to do such a thing?! */ + static_assert(sizeof(T) == 0, "unsupported attribute type"); + return {}; + } + #ifndef DOXYGEN_GENERATING_OUTPUT + #define _c(format) \ + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::format; } + _c(Float) + _c(UnsignedByte) + _c(Byte) + _c(UnsignedShort) + _c(Short) + _c(UnsignedInt) + _c(Int) + _c(Vector2) + _c(Vector3) + _c(Vector4) + #undef _c + #endif + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector3; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector4; } + /* LCOV_EXCL_STOP */ +} +#endif + +template MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), Containers::arrayCast(data)} {} + +template Containers::ArrayView MeshData::indices() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indices(): the mesh is not indexed", {}); + CORRADE_ASSERT(Implementation::meshIndexTypeFor() == _indexType, + "Trade::MeshData::indices(): improper type requested for" << _indexType, nullptr); + return Containers::arrayCast(_indices); +} + +template Containers::StridedArrayView1D MeshData::attribute(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[id].format, + "Trade::MeshData::attribute(): improper type requested for" << _attributes[id].name << "of format" << _attributes[id].format, nullptr); + return Containers::arrayCast(_attributes[id].data); +} + +template Containers::StridedArrayView1D MeshData::attribute(MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attribute(attributeId); +} + +}} + +#endif diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 1ed9a94545..fc781c5c1c 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -47,6 +47,7 @@ corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTes corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) +corrade_add_test(TradeMeshDataTest MeshDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeMeshData2DTest MeshData2DTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeMeshData3DTest MeshData3DTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeObjectData2DTest ObjectData2DTest.cpp LIBRARIES MagnumTradeTestLib) @@ -56,6 +57,7 @@ corrade_add_test(TradeTextureDataTest TextureDataTest.cpp LIBRARIES MagnumTrade) set_property(TARGET TradeAnimationDataTest + TradeMeshDataTest APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") set_target_properties( diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp new file mode 100644 index 0000000000..c3cd6a6c57 --- /dev/null +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -0,0 +1,969 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include + +#include "Magnum/Math/Color.h" +#include "Magnum/Trade/MeshData.h" + +namespace Magnum { namespace Trade { namespace Test { namespace { + +struct MeshDataTest: TestSuite::Tester { + explicit MeshDataTest(); + + void customAttributeName(); + void customAttributeNameTooLarge(); + void customAttributeNameNotCustom(); + void debugAttributeName(); + + void constructIndex(); + void constructIndexZeroCount(); + void constructIndexTypeErased(); + void constructIndexTypeErasedWrongSize(); + + void constructAttribute(); + void constructAttributeCustom(); + void constructAttributeWrongFormat(); + void constructAttributeTypeErased(); + void constructAttributeTypeErasedWrongStride(); + + void construct(); + void constructIndexless(); + void constructIndexlessZeroVertices(); + void constructAttributeless(); + void constructIndexlessAttributeless(); + void constructIndexlessAttributelessZeroVertices(); + + void constructIndexDataButNotIndexed(); + void constructVertexDataButNoAttributes(); + void constructVertexDataButNoVertices(); + void constructAttributelessInvalidIndices(); + void constructIndicesNotContained(); + void constructAttributeNotContained(); + void constructInconsitentVertexCount(); + + void constructCopy(); + void constructMove(); + + template void indicesAsArray(); + void indicesIntoArrayInvalidSize(); + template void positions2DAsArray(); + void positions2DIntoArrayInvalidSize(); + template void positions3DAsArray(); + void positions3DIntoArrayInvalidSize(); + template void normalsAsArray(); + void normalsIntoArrayInvalidSize(); + template void textureCoordinates2DAsArray(); + void textureCoordinates2DIntoArrayInvalidSize(); + template void colorsAsArray(); + void colorsIntoArrayInvalidSize(); + + void indicesNotIndexed(); + void indicesWrongType(); + + void attributeNotFound(); + void attributeWrongType(); + + void releaseIndexData(); + void releaseVertexData(); +}; + +MeshDataTest::MeshDataTest() { + addTests({&MeshDataTest::customAttributeName, + &MeshDataTest::customAttributeNameTooLarge, + &MeshDataTest::customAttributeNameNotCustom, + &MeshDataTest::debugAttributeName, + + &MeshDataTest::constructIndex, + &MeshDataTest::constructIndexZeroCount, + &MeshDataTest::constructIndexTypeErased, + &MeshDataTest::constructIndexTypeErasedWrongSize, + + &MeshDataTest::constructAttribute, + &MeshDataTest::constructAttributeCustom, + &MeshDataTest::constructAttributeWrongFormat, + &MeshDataTest::constructAttributeTypeErased, + &MeshDataTest::constructAttributeTypeErasedWrongStride, + + &MeshDataTest::construct, + &MeshDataTest::constructIndexless, + &MeshDataTest::constructIndexlessZeroVertices, + &MeshDataTest::constructAttributeless, + &MeshDataTest::constructIndexlessAttributeless, + &MeshDataTest::constructIndexlessAttributelessZeroVertices, + + &MeshDataTest::constructIndexDataButNotIndexed, + &MeshDataTest::constructVertexDataButNoAttributes, + &MeshDataTest::constructVertexDataButNoVertices, + &MeshDataTest::constructAttributelessInvalidIndices, + &MeshDataTest::constructIndicesNotContained, + &MeshDataTest::constructAttributeNotContained, + &MeshDataTest::constructInconsitentVertexCount, + + &MeshDataTest::constructCopy, + &MeshDataTest::constructMove, + + &MeshDataTest::indicesAsArray, + &MeshDataTest::indicesAsArray, + &MeshDataTest::indicesAsArray, + &MeshDataTest::indicesIntoArrayInvalidSize, + &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DIntoArrayInvalidSize, + &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DIntoArrayInvalidSize, + &MeshDataTest::normalsAsArray, + &MeshDataTest::normalsIntoArrayInvalidSize, + &MeshDataTest::textureCoordinates2DAsArray, + &MeshDataTest::textureCoordinates2DIntoArrayInvalidSize, + &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsIntoArrayInvalidSize, + + &MeshDataTest::indicesNotIndexed, + &MeshDataTest::indicesWrongType, + + &MeshDataTest::attributeNotFound, + &MeshDataTest::attributeWrongType, + + &MeshDataTest::releaseIndexData, + &MeshDataTest::releaseVertexData}); +} + +void MeshDataTest::customAttributeName() { + CORRADE_VERIFY(!isMeshAttributeCustom(MeshAttribute::Position)); + CORRADE_VERIFY(!isMeshAttributeCustom(MeshAttribute(32767))); + CORRADE_VERIFY(isMeshAttributeCustom(MeshAttribute::Custom)); + CORRADE_VERIFY(isMeshAttributeCustom(MeshAttribute(65535))); + + CORRADE_COMPARE(UnsignedShort(meshAttributeCustom(0)), 32768); + CORRADE_COMPARE(UnsignedShort(meshAttributeCustom(8290)), 41058); + CORRADE_COMPARE(UnsignedShort(meshAttributeCustom(32767)), 65535); + + CORRADE_COMPARE(meshAttributeCustom(MeshAttribute::Custom), 0); + CORRADE_COMPARE(meshAttributeCustom(MeshAttribute(41058)), 8290); + CORRADE_COMPARE(meshAttributeCustom(MeshAttribute(65535)), 32767); + + constexpr bool is = isMeshAttributeCustom(MeshAttribute(41058)); + CORRADE_VERIFY(is); + constexpr MeshAttribute a = meshAttributeCustom(8290); + CORRADE_COMPARE(UnsignedShort(a), 41058); + constexpr UnsignedShort b = meshAttributeCustom(a); + CORRADE_COMPARE(b, 8290); +} + +void MeshDataTest::customAttributeNameTooLarge() { + std::ostringstream out; + Error redirectError{&out}; + meshAttributeCustom(32768); + CORRADE_COMPARE(out.str(), "Trade::meshAttributeCustom(): index 32768 too large\n"); +} + +void MeshDataTest::customAttributeNameNotCustom() { + std::ostringstream out; + Error redirectError{&out}; + meshAttributeCustom(MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(out.str(), "Trade::meshAttributeCustom(): Trade::MeshAttribute::TextureCoordinates is not custom\n"); +} + +void MeshDataTest::debugAttributeName() { + std::ostringstream out; + Debug{&out} << MeshAttribute::Position << meshAttributeCustom(73) << MeshAttribute(0x73); + CORRADE_COMPARE(out.str(), "Trade::MeshAttribute::Position Trade::MeshAttribute::Custom(73) Trade::MeshAttribute(0x73)\n"); +} + +using namespace Math::Literals; + +void MeshDataTest::constructIndex() { + { + Containers::Array indexData{3*1}; + auto indexView = Containers::arrayCast(indexData); + + MeshIndexData indices{indexView}; + MeshData data{MeshPrimitive::Points, std::move(indexData), indices}; + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedByte); + CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); + CORRADE_COMPARE(data.indexCount(), 3); + } { + Containers::Array indexData{3*2}; + auto indexView = Containers::arrayCast(indexData); + + MeshIndexData indices{indexView}; + MeshData data{MeshPrimitive::Points, std::move(indexData), indices}; + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); + CORRADE_COMPARE(data.indexCount(), 3); + } { + Containers::Array indexData{3*4}; + auto indexView = Containers::arrayCast(indexData); + + MeshIndexData indices{indexView}; + MeshData data{MeshPrimitive::Points, std::move(indexData), indices}; + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); + CORRADE_COMPARE(data.indexCount(), 3); + } +} + +void MeshDataTest::constructIndexZeroCount() { + std::ostringstream out; + Error redirectError{&out}; + MeshIndexData{MeshIndexType::UnsignedInt, nullptr}; + CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: index array can't be empty, create a non-indexed mesh instead\n"); +} + +void MeshDataTest::constructIndexTypeErased() { + Containers::Array indexData{3*2}; + auto indexView = Containers::arrayCast(indexData); + + MeshIndexData indices{MeshIndexType::UnsignedShort, indexData}; + MeshData data{MeshPrimitive::Points, std::move(indexData), indices}; + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); + CORRADE_COMPARE(data.indexCount(), 3); +} + +void MeshDataTest::constructIndexTypeErasedWrongSize() { + Containers::Array indexData{3*2}; + + std::ostringstream out; + Error redirectError{&out}; + MeshIndexData{MeshIndexType::UnsignedInt, indexData}; + CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: view size 6 does not correspond to MeshIndexType::UnsignedInt\n"); +} + +void MeshDataTest::constructAttribute() { + Containers::Array positionData{3*sizeof(Vector2)}; + auto positionView = Containers::arrayCast(positionData); + + MeshAttributeData positions{MeshAttribute::Position, positionView}; + MeshData data{MeshPrimitive::Points, std::move(positionData), {positions}}; + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(static_cast(data.attribute(0).data()), + positionView.data()); +} + +void MeshDataTest::constructAttributeCustom() { + Containers::Array idData{3*sizeof(Short)}; + auto idView = Containers::arrayCast(idData); + + MeshAttributeData ids{meshAttributeCustom(13), idView}; + MeshData data{MeshPrimitive::Points, std::move(idData), {ids}}; + CORRADE_COMPARE(data.attributeName(0), meshAttributeCustom(13)); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Short); + CORRADE_COMPARE(static_cast(data.attribute(0).data()), + idView.data()); +} + +void MeshDataTest::constructAttributeWrongFormat() { + Containers::Array positionData{3*sizeof(Vector2)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Color, Containers::arrayCast(positionData)}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n"); +} + +void MeshDataTest::constructAttributeTypeErased() { + Containers::Array positionData{3*sizeof(Vector3)}; + auto positionView = Containers::arrayCast(positionData); + + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(Containers::stridedArrayView(positionView))}; + MeshData data{MeshPrimitive::Points, std::move(positionData), {positions}}; + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector3); + CORRADE_COMPARE(static_cast(data.attribute(0).data()), + positionView.data()); +} + +void MeshDataTest::constructAttributeTypeErasedWrongStride() { + Containers::Array positionData{3*sizeof(Vector3)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(positionData)}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: view stride 1 is not large enough to contain VertexFormat::Vector3\n"); +} + +void MeshDataTest::construct() { + struct Vertex { + Vector3 position; + Vector3 normal; + Vector2 textureCoordinate; + Short id; + }; + + Containers::Array indexData{6*sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 2; + indexView[3] = 0; + indexView[4] = 2; + indexView[5] = 1; + + Containers::Array vertexData{3*sizeof(Vertex)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0].position = {0.1f, 0.2f, 0.3f}; + vertexView[1].position = {0.4f, 0.5f, 0.6f}; + vertexView[2].position = {0.7f, 0.8f, 0.9f}; + vertexView[0].normal = Vector3::xAxis(); + vertexView[1].normal = Vector3::yAxis(); + vertexView[2].normal = Vector3::zAxis(); + vertexView[0].textureCoordinate = {0.000f, 0.125f}; + vertexView[1].textureCoordinate = {0.250f, 0.375f}; + vertexView[2].textureCoordinate = {0.500f, 0.625f}; + vertexView[0].id = 15; + vertexView[1].id = -374; + vertexView[2].id = 22; + + int importerState; + MeshIndexData indices{indexView}; + MeshAttributeData positions{MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, &vertexView[0].position, vertexView.size(), sizeof(Vertex)}}; + MeshAttributeData normals{MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, &vertexView[0].normal, vertexView.size(), sizeof(Vertex)}}; + MeshAttributeData textureCoordinates{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{vertexData, &vertexView[0].textureCoordinate, vertexView.size(), sizeof(Vertex)}}; + MeshAttributeData ids{meshAttributeCustom(13), + Containers::StridedArrayView1D{vertexData, &vertexView[0].id, vertexView.size(), sizeof(Vertex)}}; + MeshData data{MeshPrimitive::Triangles, + std::move(indexData), indices, + /* Texture coordinates deliberately twice (though aliased) */ + std::move(vertexData), {positions, textureCoordinates, normals, textureCoordinates, ids}, &importerState}; + + /* Basics */ + CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(data.indexData()), indexView.data()); + CORRADE_COMPARE(static_cast(data.vertexData()), vertexView.data()); + CORRADE_COMPARE(data.importerState(), &importerState); + + /* Index access */ + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 6); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[0], 0); + CORRADE_COMPARE(data.indices()[2], 2); + CORRADE_COMPARE(data.indices()[5], 1); + + /* Attribute access by ID */ + CORRADE_COMPARE(data.vertexCount(), 3); + CORRADE_COMPARE(data.attributeCount(), 5); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeName(1), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(data.attributeName(2), MeshAttribute::Normal); + CORRADE_COMPARE(data.attributeName(3), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(data.attributeName(4), meshAttributeCustom(13)); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector3); + CORRADE_COMPARE(data.attributeFormat(1), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeFormat(2), VertexFormat::Vector3); + CORRADE_COMPARE(data.attributeFormat(3), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeFormat(4), VertexFormat::Short); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeOffset(1), 2*sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(2), sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(3), 2*sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(4), 2*sizeof(Vector3) + sizeof(Vector2)); + CORRADE_COMPARE(data.attributeStride(0), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(1), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(2), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(3), sizeof(Vertex)); + CORRADE_COMPARE(data.attribute(0)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.attribute(1)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.attribute(2)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.attribute(3)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.attribute(4)[1], -374); + + /* Attribute access by name */ + CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Position)); + CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Normal)); + CORRADE_VERIFY(data.hasAttribute(MeshAttribute::TextureCoordinates)); + CORRADE_VERIFY(data.hasAttribute(meshAttributeCustom(13))); + CORRADE_VERIFY(!data.hasAttribute(MeshAttribute::Color)); + CORRADE_VERIFY(!data.hasAttribute(meshAttributeCustom(23))); + CORRADE_COMPARE(data.attributeCount(MeshAttribute::Position), 1); + CORRADE_COMPARE(data.attributeCount(MeshAttribute::Normal), 1); + CORRADE_COMPARE(data.attributeCount(MeshAttribute::TextureCoordinates), 2); + CORRADE_COMPARE(data.attributeCount(meshAttributeCustom(13)), 1); + CORRADE_COMPARE(data.attributeCount(MeshAttribute::Color), 0); + CORRADE_COMPARE(data.attributeCount(meshAttributeCustom(23)), 0); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position), + VertexFormat::Vector3); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Normal), + VertexFormat::Vector3); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::TextureCoordinates, 0), + VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::TextureCoordinates, 1), + VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeFormat(meshAttributeCustom(13)), + VertexFormat::Short); + CORRADE_COMPARE(data.attributeOffset(MeshAttribute::Position), 0); + CORRADE_COMPARE(data.attributeOffset(MeshAttribute::Normal), sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(MeshAttribute::TextureCoordinates, 0), 2*sizeof(Vector3)); + CORRADE_COMPARE(data.attributeOffset(MeshAttribute::TextureCoordinates, 1), 2*sizeof(Vector3)); CORRADE_COMPARE(data.attributeOffset(meshAttributeCustom(13)), 2*sizeof(Vector3) + sizeof(Vector2)); + CORRADE_COMPARE(data.attributeStride(MeshAttribute::Position), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(MeshAttribute::Normal), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 0), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 1), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(meshAttributeCustom(13)), sizeof(Vertex)); + CORRADE_COMPARE(data.attribute(MeshAttribute::Position)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.attribute(MeshAttribute::Normal)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2], 22); +} + +void MeshDataTest::constructIndexless() { + Containers::Array vertexData{3*sizeof(Vector2)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0] = {0.1f, 0.2f}; + vertexView[1] = {0.4f, 0.5f}; + vertexView[2] = {0.7f, 0.8f}; + + int importerState; + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions}, &importerState}; + CORRADE_COMPARE(data.primitive(), MeshPrimitive::LineLoop); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 3); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position), VertexFormat::Vector2); + CORRADE_COMPARE(data.attribute(MeshAttribute::Position)[1], (Vector2{0.4f, 0.5f})); +} + +void MeshDataTest::constructIndexlessZeroVertices() { + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; + MeshData data{MeshPrimitive::LineLoop, nullptr, {positions}}; + CORRADE_COMPARE(data.primitive(), MeshPrimitive::LineLoop); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_COMPARE(data.vertexData(), nullptr); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 0); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position), VertexFormat::Vector2); +} + +void MeshDataTest::constructAttributeless() { + Containers::Array indexData{6*sizeof(UnsignedInt)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 2; + indexView[3] = 0; + indexView[4] = 2; + indexView[5] = 1; + + int importerState; + MeshIndexData indices{indexView}; + MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), indices, &importerState}; + CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_COMPARE(data.vertexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 6); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(data.indices()[0], 0); + CORRADE_COMPARE(data.indices()[2], 2); + CORRADE_COMPARE(data.indices()[5], 1); + + CORRADE_COMPARE(data.vertexCount(), 0); /** @todo what to return here? */ + CORRADE_COMPARE(data.attributeCount(), 0); +} + +void MeshDataTest::constructIndexlessAttributeless() { + int importerState; + MeshData data{MeshPrimitive::TriangleStrip, 37, &importerState}; + CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_COMPARE(data.vertexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 37); + CORRADE_COMPARE(data.attributeCount(), 0); +} + +void MeshDataTest::constructIndexlessAttributelessZeroVertices() { + int importerState; + MeshData data{MeshPrimitive::TriangleStrip, 0, &importerState}; + CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_COMPARE(data.vertexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 0); + CORRADE_COMPARE(data.attributeCount(), 0); +} + +void MeshDataTest::constructIndexDataButNotIndexed() { + Containers::Array indexData{6}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; + MeshData{MeshPrimitive::Points, std::move(indexData), MeshIndexData{}, nullptr, {positions}}; + CORRADE_COMPARE(out.str(), "Trade::MeshData: indexData passed for a non-indexed mesh\n"); +} + +void MeshDataTest::constructVertexDataButNoAttributes() { + Containers::Array indexData{6}; + Containers::Array vertexData{6}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Points, std::move(indexData), MeshIndexData{Containers::arrayCast(indexData)}, std::move(vertexData), {}}; + CORRADE_COMPARE(out.str(), "Trade::MeshData: vertexData passed for an attribute-less mesh\n"); +} + +void MeshDataTest::constructVertexDataButNoVertices() { + Containers::Array vertexData{6}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; + MeshData{MeshPrimitive::LineLoop, std::move(vertexData), {positions}}; + CORRADE_COMPARE(out.str(), "Trade::MeshData: vertexData passed for a mesh with zero vertices\n"); +} + +void MeshDataTest::constructAttributelessInvalidIndices() { + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Points, nullptr, MeshIndexData{}}; + CORRADE_COMPARE(out.str(), "Trade::MeshData: indices are expected to be valid if there are no attributes and vertex count isn't passed explicitly\n"); +} + +void MeshDataTest::constructIndicesNotContained() { + Containers::Array indexData{reinterpret_cast(0xbadda9), 6, [](char*, std::size_t){}}; + Containers::ArrayView indexData2{reinterpret_cast(0xdead), 3}; + MeshIndexData indices{indexData2}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Triangles, std::move(indexData), indices}; + MeshData{MeshPrimitive::Triangles, nullptr, indices}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: indices [0xdead:0xdeb3] are not contained in passed indexData array [0xbadda9:0xbaddaf]\n" + "Trade::MeshData: indices [0xdead:0xdeb3] are not contained in passed indexData array [0x0:0x0]\n"); +} + +void MeshDataTest::constructAttributeNotContained() { + Containers::Array vertexData{reinterpret_cast(0xbadda9), 24, [](char*, std::size_t){}}; + Containers::ArrayView vertexData2{reinterpret_cast(0xdead), 3}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast(vertexData)}; + MeshAttributeData positions2{MeshAttribute::Position, Containers::arrayView(vertexData2)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}}; + MeshData{MeshPrimitive::Triangles, nullptr, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: attribute 1 [0xdead:0xdec5] is not contained in passed vertexData array [0xbadda9:0xbaddc1]\n" + "Trade::MeshData: attribute 0 [0xbadda9:0xbaddc1] is not contained in passed vertexData array [0x0:0x0]\n"); +} + +void MeshDataTest::constructInconsitentVertexCount() { + Containers::Array vertexData{24}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast(vertexData)}; + MeshAttributeData positions2{MeshAttribute::Position, Containers::arrayCast(vertexData).prefix(2)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: attribute 1 has 2 vertices but 3 expected\n"); +} + +void MeshDataTest::constructCopy() { + CORRADE_VERIFY(!(std::is_constructible{})); + CORRADE_VERIFY(!(std::is_assignable{})); +} + +void MeshDataTest::constructMove() { + Containers::Array indexData{3*sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 0; + + Containers::Array vertexData{2*sizeof(Vector2)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0] = {0.1f, 0.2f}; + vertexView[1] = {0.4f, 0.5f}; + + int importerState; + MeshIndexData indices{indexView}; + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + MeshData a{MeshPrimitive::Triangles, std::move(indexData), indices, std::move(vertexData), {positions}, &importerState}; + + MeshData b{std::move(a)}; + + CORRADE_COMPARE(b.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(b.indexData()), indexView.data()); + CORRADE_COMPARE(static_cast(b.vertexData()), vertexView.data()); + CORRADE_COMPARE(b.importerState(), &importerState); + + CORRADE_VERIFY(b.isIndexed()); + CORRADE_COMPARE(b.indexCount(), 3); + CORRADE_COMPARE(b.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(b.indices()[1], 1); + CORRADE_COMPARE(b.indices()[2], 0); + + CORRADE_COMPARE(b.vertexCount(), 2); + CORRADE_COMPARE(b.attributeCount(), 1); + CORRADE_COMPARE(b.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(b.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(b.attributeOffset(0), 0); + CORRADE_COMPARE(b.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(b.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(b.attribute(0)[1], (Vector2{0.4f, 0.5f})); + + MeshData c{MeshPrimitive::LineLoop, 37}; + c = std::move(b); + + CORRADE_COMPARE(c.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(c.indexData()), indexView.data()); + CORRADE_COMPARE(static_cast(c.vertexData()), vertexView.data()); + CORRADE_COMPARE(c.importerState(), &importerState); + + CORRADE_VERIFY(c.isIndexed()); + CORRADE_COMPARE(c.indexCount(), 3); + CORRADE_COMPARE(c.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(c.indices()[1], 1); + CORRADE_COMPARE(c.indices()[2], 0); + + CORRADE_COMPARE(c.vertexCount(), 2); + CORRADE_COMPARE(c.attributeCount(), 1); + CORRADE_COMPARE(c.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(c.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(c.attributeOffset(0), 0); + CORRADE_COMPARE(c.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(c.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(c.attribute(0)[1], (Vector2{0.4f, 0.5f})); + + CORRADE_VERIFY(std::is_nothrow_move_constructible::value); + CORRADE_VERIFY(std::is_nothrow_move_assignable::value); +} + +template struct NameTraits; +#define _c(format) template<> struct NameTraits { \ + static const char* name() { return #format; } \ + }; +_c(Vector2) +_c(Vector3) +_c(Color3) +_c(Color4) +#undef _c + +template void MeshDataTest::indicesAsArray() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + Containers::Array indexData{3*sizeof(T)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 75; + indexView[1] = 131; + indexView[2] = 240; + + MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indexView}}; + CORRADE_COMPARE_AS(data.indicesAsArray(), + Containers::arrayView({75, 131, 240}), + TestSuite::Compare::Container); +} + +void MeshDataTest::indicesIntoArrayInvalidSize() { + Containers::Array indexData{3*sizeof(UnsignedInt)}; + MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{Containers::arrayCast(indexData)}}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt destination[2]; + data.indicesInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::indicesInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::positions2DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Vector2{2.0f, 1.0f}); + positionsView[1] = T::pad(Vector2{0.0f, -1.0f}); + positionsView[2] = T::pad(Vector2{-2.0f, 3.0f}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; + CORRADE_COMPARE_AS(data.positions2DAsArray(), + Containers::arrayView({{2.0f, 1.0f}, {0.0f, -1.0f}, {-2.0f, 3.0f}}), + TestSuite::Compare::Container); +} + +void MeshDataTest::positions2DIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Vector2)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Vector2 destination[2]; + data.positions2DInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::positions2DInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::positions3DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Vector3{2.0f, 1.0f, 0.3f}); + positionsView[1] = T::pad(Vector3{0.0f, -1.0f, 1.1f}); + positionsView[2] = T::pad(Vector3{-2.0f, 3.0f, 2.2f}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; + CORRADE_COMPARE_AS(data.positions3DAsArray(), Containers::arrayView({ + Vector3::pad(T::pad(Vector3{2.0f, 1.0f, 0.3f})), + Vector3::pad(T::pad(Vector3{0.0f, -1.0f, 1.1f})), + Vector3::pad(T::pad(Vector3{-2.0f, 3.0f, 2.2f})) + }), TestSuite::Compare::Container); +} + +void MeshDataTest::positions3DIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Vector3)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Vector3 destination[2]; + data.positions3DInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::positions3DInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::normalsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto normalsView = Containers::arrayCast(vertexData); + normalsView[0] = {2.0f, 1.0f, 0.3f}; + normalsView[1] = {0.0f, -1.0f, 1.1f}; + normalsView[2] = {-2.0f, 3.0f, 2.2f}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Normal, normalsView}}}; + CORRADE_COMPARE_AS(data.normalsAsArray(), Containers::arrayView({ + {2.0f, 1.0f, 0.3f}, {0.0f, -1.0f, 1.1f}, {-2.0f, 3.0f, 2.2f}, + }), TestSuite::Compare::Container); +} + +void MeshDataTest::normalsIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Vector3)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Normal, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Vector3 destination[2]; + data.normalsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::normalsInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::textureCoordinates2DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto textureCoordinatesView = Containers::arrayCast(vertexData); + textureCoordinatesView[0] = {2.0f, 1.0f}; + textureCoordinatesView[1] = {0.0f, -1.0f}; + textureCoordinatesView[2] = {-2.0f, 3.0f}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, textureCoordinatesView}}}; + CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(), Containers::arrayView({ + {2.0f, 1.0f}, {0.0f, -1.0f}, {-2.0f, 3.0f}, + }), TestSuite::Compare::Container); +} + +void MeshDataTest::textureCoordinates2DIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Vector2)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Vector2 destination[2]; + data.textureCoordinates2DInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::textureCoordinates2DInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::colorsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto colorsView = Containers::arrayCast(vertexData); + colorsView[0] = 0xff3366_rgbf; + colorsView[1] = 0x99aacc_rgbf; + colorsView[2] = 0x3377ff_rgbf; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Color, colorsView}}}; + CORRADE_COMPARE_AS(data.colorsAsArray(), Containers::arrayView({ + 0xff3366_rgbf, 0x99aacc_rgbf, 0x3377ff_rgbf + }), TestSuite::Compare::Container); +} + +void MeshDataTest::colorsIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Color4)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Color, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Color4 destination[2]; + data.colorsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::colorsInto(): expected a view with 3 elements but got 2\n"); +} + +void MeshDataTest::indicesNotIndexed() { + MeshData data{MeshPrimitive::Triangles, 37}; + + std::ostringstream out; + Error redirectError{&out}; + data.indexCount(); + data.indexType(); + data.indices(); + data.indicesAsArray(); + UnsignedInt a[1]; + data.indicesInto(a); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::indexCount(): the mesh is not indexed\n" + "Trade::MeshData::indexType(): the mesh is not indexed\n" + "Trade::MeshData::indices(): the mesh is not indexed\n" + "Trade::MeshData::indicesAsArray(): the mesh is not indexed\n" + "Trade::MeshData::indicesInto(): the mesh is not indexed\n"); +} + +void MeshDataTest::indicesWrongType() { + Containers::Array indexData{sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 57616; + MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indexView}}; + + std::ostringstream out; + Error redirectError{&out}; + data.indices(); + CORRADE_COMPARE(out.str(), "Trade::MeshData::indices(): improper type requested for MeshIndexType::UnsignedShort\n"); +} + +void MeshDataTest::attributeNotFound() { + MeshAttributeData colors1{MeshAttribute::Color, VertexFormat::Vector3, nullptr}; + MeshAttributeData colors2{MeshAttribute::Color, VertexFormat::Vector4, nullptr}; + MeshData data{MeshPrimitive::Points, nullptr, {colors1, colors2}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attributeName(2); + data.attributeFormat(2); + data.attributeOffset(2); + data.attributeStride(2); + data.attribute(2); + data.attributeFormat(MeshAttribute::Position); + data.attributeFormat(MeshAttribute::Color, 2); + data.attributeOffset(MeshAttribute::Position); + data.attributeOffset(MeshAttribute::Color, 2); + data.attributeStride(MeshAttribute::Position); + data.attributeStride(MeshAttribute::Color, 2); + data.attribute(MeshAttribute::Position); + data.attribute(MeshAttribute::Color, 2); + data.positions2DAsArray(); + data.positions3DAsArray(); + data.normalsAsArray(); + data.textureCoordinates2DAsArray(); + data.colorsAsArray(2); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::attributeName(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeFormat(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeOffset(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeStride(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attribute(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeFormat(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attributeFormat(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeOffset(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attributeOffset(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeStride(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attributeStride(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::positions2DInto(): index 0 out of range for 0 position attributes\n" + "Trade::MeshData::positions3DInto(): index 0 out of range for 0 position attributes\n" + "Trade::MeshData::normalsInto(): index 0 out of range for 0 normal attributes\n" + "Trade::MeshData::textureCoordinates2DInto(): index 0 out of range for 0 texture coordinate attributes\n" + "Trade::MeshData::colorsInto(): index 2 out of range for 2 color attributes\n"); +} + +void MeshDataTest::attributeWrongType() { + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, nullptr}; + MeshData data{MeshPrimitive::Points, nullptr, {positions}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attribute(MeshAttribute::Position); + CORRADE_COMPARE(out.str(), "Trade::MeshData::attribute(): improper type requested for Trade::MeshAttribute::Position of format VertexFormat::Vector3\n"); +} + +void MeshDataTest::releaseIndexData() { + Containers::Array indexData{6}; + auto indexView = Containers::arrayCast(indexData); + + MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), MeshIndexData{indexView}}; + CORRADE_VERIFY(data.isIndexed()); + + Containers::Array released = data.releaseIndexData(); + CORRADE_COMPARE(static_cast(released.data()), indexView.data()); + CORRADE_COMPARE(data.indexData(), nullptr); + CORRADE_VERIFY(!data.isIndexed()); +} + +void MeshDataTest::releaseVertexData() { + Containers::Array vertexData{16}; + auto vertexView = Containers::arrayCast(vertexData); + + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions, positions}}; + CORRADE_COMPARE(data.attributeCount(), 2); + + Containers::Array released = data.releaseVertexData(); + CORRADE_COMPARE(data.vertexData(), nullptr); + CORRADE_COMPARE(data.attributeCount(), 0); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Trade::Test::MeshDataTest) diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index 06c28ca496..0803127ac2 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -65,6 +65,12 @@ typedef ImageData<2> ImageData2D; typedef ImageData<3> ImageData3D; class LightData; + +enum class MeshAttribute: UnsignedShort; +class MeshIndexData; +class MeshAttributeData; +class MeshData; + class MeshData2D; class MeshData3D; class MeshObjectData2D; diff --git a/src/Magnum/VertexFormat.h b/src/Magnum/VertexFormat.h index 1ecdcb7712..ce7d3120a0 100644 --- a/src/Magnum/VertexFormat.h +++ b/src/Magnum/VertexFormat.h @@ -42,6 +42,8 @@ namespace Magnum { Like @ref PixelFormat, but for mesh attributes --- including double-precision types and matrices. +@see @ref Trade::MeshData, @ref Trade::MeshAttributeData, + @ref Trade::MeshAttribute */ enum class VertexFormat: UnsignedInt { /* Zero reserved for an invalid type (but not being a named value) */ From fb1fdf610595df01c48c6d40f9b14aec258c027f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 20 Feb 2020 14:19:31 +0100 Subject: [PATCH 026/107] Trade: implement Importer interfaces for the new MeshData. Deprecating of the old ones comes later. --- doc/changelog.dox | 3 +- src/Magnum/Trade/AbstractImporter.cpp | 54 +++ src/Magnum/Trade/AbstractImporter.h | 117 ++++++- src/Magnum/Trade/MeshData.h | 23 +- .../Trade/Test/AbstractImporterTest.cpp | 320 ++++++++++++++++++ 5 files changed, 510 insertions(+), 7 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index e48b9e9905..fd39bece2f 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -162,7 +162,8 @@ See also: - A new, redesigned @ref Trade::MeshData class that allows much more flexible access to vertex/index data without unnecessary allocations and data - conversions or copies + conversions or copies. Importers expose it through the new + @ref Trade::AbstractImporter::mesh() family of APIs. - Ability to import image mip levels via an additional parameter in @ref Trade::AbstractImporter::image2D(), @ref Trade::AbstractImporter::image2DLevelCount() and similar APIs for 1D diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index 3138c40db4..d9abe242e5 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -38,6 +38,7 @@ #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/ObjectData2D.h" @@ -403,6 +404,59 @@ Containers::Pointer AbstractImporter::doObject3D(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::object3D(): not implemented", {}); } +UnsignedInt AbstractImporter::meshCount() const { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshCount(): no file opened", {}); + return doMeshCount(); +} + +UnsignedInt AbstractImporter::doMeshCount() const { return 0; } + +Int AbstractImporter::meshForName(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshForName(): no file opened", {}); + return doMeshForName(name); +} + +Int AbstractImporter::doMeshForName(const std::string&) { return -1; } + +std::string AbstractImporter::meshName(const UnsignedInt id) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshName(): no file opened", {}); + CORRADE_ASSERT(id < doMeshCount(), "Trade::AbstractImporter::meshName(): index" << id << "out of range for" << doMeshCount() << "entries", {}); + return doMeshName(id); +} + +std::string AbstractImporter::doMeshName(UnsignedInt) { return {}; } + +Containers::Optional AbstractImporter::mesh(const UnsignedInt id) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh(): no file opened", {}); + CORRADE_ASSERT(id < doMeshCount(), "Trade::AbstractImporter::mesh(): index" << id << "out of range for" << doMeshCount() << "entries", {}); + Containers::Optional mesh = doMesh(id); + CORRADE_ASSERT(!mesh || (!mesh->_indexData.deleter() && !mesh->_vertexData.deleter() && !mesh->_attributes.deleter()), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter", {}); + return mesh; +} + +Containers::Optional AbstractImporter::doMesh(UnsignedInt) { + CORRADE_ASSERT(false, "Trade::AbstractImporter::mesh(): not implemented", {}); +} + +MeshAttribute AbstractImporter::meshAttributeForName(const std::string& name) { + const MeshAttribute out = doMeshAttributeForName(name); + CORRADE_ASSERT(out == MeshAttribute{} || isMeshAttributeCustom(out), + "Trade::AbstractImporter::meshAttributeForName(): implementation-returned" << out << "is neither custom nor invalid", {}); + return out; +} + +MeshAttribute AbstractImporter::doMeshAttributeForName(const std::string&) { + return {}; +} + +std::string AbstractImporter::meshAttributeName(MeshAttribute name) { + CORRADE_ASSERT(isMeshAttributeCustom(name), + "Trade::AbstractImporter::meshAttributeName():" << name << "is not custom", {}); + return doMeshAttributeName(meshAttributeCustom(name)); +} + +std::string AbstractImporter::doMeshAttributeName(UnsignedShort) { return {}; } + UnsignedInt AbstractImporter::mesh2DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh2DCount(): no file opened", {}); return doMesh2DCount(); diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index 5900873c6a..024c7ffaa7 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -159,8 +159,8 @@ expose internal state through various accessors: imported by @ref image1D(), @ref image2D() or @ref image3D() - @ref LightData::importerState() can expose importer state for a light imported by @ref light() -- @ref MeshData3D::importerState() can expose importer state for a mesh - imported by @ref mesh2D() or @ref mesh3D() +- @ref MeshData::importerState() can expose importer state for a mesh + imported by @ref mesh() - @ref ObjectData3D::importerState() can expose importer state for an object imported by @ref object2D() or @ref object3D() - @ref SceneData::importerState() can expose importer state for a scene @@ -680,6 +680,72 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ Containers::Pointer object3D(UnsignedInt id); + /** + * @brief Mesh count + * @m_since_latest + * + * Expects that a file is opened. + */ + UnsignedInt meshCount() const; + + /** + * @brief Mesh ID for given name + * @m_since_latest + * + * If no mesh for given name exists, returns @cpp -1 @ce. Expects that + * a file is opened. + * @see @ref meshName() + */ + Int meshForName(const std::string& name); + + /** + * @brief Mesh name + * @param id Mesh ID, from range [0, @ref meshCount()). + * @m_since_latest + * + * Expects that a file is opened. + * @see @ref meshForName() + */ + std::string meshName(UnsignedInt id); + + /** + * @brief Mesh + * @param id Mesh ID, from range [0, @ref meshCount()). + * @m_since_latest + * + * Returns given mesh or @ref Containers::NullOpt if importing failed. + * Expects that a file is opened. + */ + Containers::Optional mesh(UnsignedInt id); + + /** + * @brief Mesh attribute for given name + * @m_since_latest + * + * If the name is not recognized, returns a zero (invalid) + * @ref MeshAttribute, otherwise returns a custom mesh attribute. Note + * that the value returned by this function may depend on whether a + * file is opened or not and also be different for different files --- + * see documentation of a particular importer for more information. + * @see @ref isMeshAttributeCustom() + */ + MeshAttribute meshAttributeForName(const std::string& name); + + /** + * @brief String name for given custom mesh attribute + * @m_since_latest + * + * Given a custom @p name returned by @ref mesh() in a @ref MeshData, + * returns a string identifier. If a string representation is not + * available or @p name is not recognized, returns an empty string. + * Expects that @p name is custom. Note that the value returned by + * this function may depend on whether a file is opened or not and also + * be different for different files --- see documentation of a + * particular importer for more information. + * @see @ref isMeshAttributeCustom() + */ + std::string meshAttributeName(MeshAttribute name); + /** * @brief Two-dimensional mesh count * @@ -1164,6 +1230,53 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi /** @brief Implementation for @ref object3D() */ virtual Containers::Pointer doObject3D(UnsignedInt id); + /** + * @brief Implementation for @ref meshCount() + * @m_since_latest + * + * Default implementation returns @cpp 0 @ce. + */ + virtual UnsignedInt doMeshCount() const; + + /** + * @brief Implementation for @ref meshForName() + * @m_since_latest + * + * Default implementation returns @cpp -1 @ce. + */ + virtual Int doMeshForName(const std::string& name); + + /** + * @brief Implementation for @ref meshName() + * @m_since_latest + * + * Default implementation returns an empty string. + */ + virtual std::string doMeshName(UnsignedInt id); + + /** + * @brief Implementation for @ref mesh() + * @m_since_latest + */ + virtual Containers::Optional doMesh(UnsignedInt id); + + /** + * @brief Implementation for @ref meshAttributeForName() + * @m_since_latest + * + * Default implementation returns an invalid (zero) value. + */ + virtual MeshAttribute doMeshAttributeForName(const std::string& name); + + /** + * @brief Implementation for @ref meshAttributeName() + * @m_since_latest + * + * Receives the custom ID extracted via @ref meshAttributeCustom(MeshAttribute). + * Default implementation returns an empty string. + */ + virtual std::string doMeshAttributeName(UnsignedShort name); + /** * @brief Implementation for @ref mesh2DCount() * diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 6df4f05fab..de41b4d3b7 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -44,11 +44,16 @@ namespace Magnum { namespace Trade { @brief Mesh attribute name @m_since_latest -@see @ref MeshData, @ref MeshAttributeData, @ref VertexFormat +@see @ref MeshData, @ref MeshAttributeData, @ref VertexFormat, + @ref AbstractImporter::meshAttributeForName(), + @ref AbstractImporter::meshAttributeName() */ /* 16 bits because 8 bits is not enough to cover all potential per-edge, per-face, per-instance and per-meshlet attributes */ enum class MeshAttribute: UnsignedShort { + /* 0 reserved for an invalid value (returned from + AbstractImporter::meshAttributeForName()) */ + /** * Position. Type is usually @ref Magnum::Vector2 "Vector2" for 2D and * @ref Magnum::Vector3 "Vector3" for 3D. Corresponds to @@ -57,7 +62,7 @@ enum class MeshAttribute: UnsignedShort { * @ref MeshData::positions2DAsArray(), * @ref MeshData::positions3DAsArray() */ - Position, + Position = 1, /** * Normal. Type is usually @ref Magnum::Vector3 "Vector3". Corresponds to @@ -243,7 +248,8 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { @m_since_latest Provides access to mesh vertex and index data, together with additional -information such as primitive type. +information such as primitive type. Populated instances of this class are +returned from @ref AbstractImporter::mesh(). @section Trade-MeshData-usage Basic usage @@ -268,6 +274,8 @@ of data, which you can directly upload, and then use provided metadata to let the GPU know of the format and layout: @snippet MagnumTrade.cpp MeshData-usage-advanced + +@see @ref AbstractImporter::mesh() */ class MAGNUM_TRADE_EXPORT MeshData { public: @@ -441,7 +449,9 @@ class MAGNUM_TRADE_EXPORT MeshData { * @brief Attribute name * * The @p id is expected to be smaller than @ref attributeCount() const. - * @see @ref attributeFormat(), @ref isMeshAttributeCustom() + * @see @ref attributeFormat(), @ref isMeshAttributeCustom(), + * @ref AbstractImporter::meshAttributeForName(), + * @ref AbstractImporter::meshAttributeName() */ MeshAttribute attributeName(UnsignedInt id) const; @@ -714,6 +724,11 @@ class MAGNUM_TRADE_EXPORT MeshData { const void* importerState() const { return _importerState; } private: + /* For custom deleter checks. Not done in the constructors here because + the restriction is pointless when used outside of plugin + implementations. */ + friend AbstractImporter; + UnsignedInt attributeFor(MeshAttribute name, UnsignedInt id) const; UnsignedInt _vertexCount; diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 60f4d699d1..9f44d98d7b 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -37,6 +37,7 @@ #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/MeshObjectData2D.h" @@ -156,6 +157,25 @@ struct AbstractImporterTest: TestSuite::Tester { void object3DNoFile(); void object3DOutOfRange(); + void mesh(); + void meshCountNotImplemented(); + void meshCountNoFile(); + void meshForNameNotImplemented(); + void meshForNameNoFile(); + void meshNameNotImplemented(); + void meshNameNoFile(); + void meshNameOutOfRange(); + void meshNotImplemented(); + void meshNoFile(); + void meshOutOfRange(); + void meshCustomIndexDataDeleter(); + void meshCustomVertexDataDeleter(); + void meshCustomAttributesDeleter(); + + void meshAttributeName(); + void meshAttributeNameNotImplemented(); + void meshAttributeNameNotCustom(); + void mesh2D(); void mesh2DCountNotImplemented(); void mesh2DCountNoFile(); @@ -371,6 +391,25 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::object3DNoFile, &AbstractImporterTest::object3DOutOfRange, + &AbstractImporterTest::mesh, + &AbstractImporterTest::meshCountNotImplemented, + &AbstractImporterTest::meshCountNoFile, + &AbstractImporterTest::meshForNameNotImplemented, + &AbstractImporterTest::meshForNameNoFile, + &AbstractImporterTest::meshNameNotImplemented, + &AbstractImporterTest::meshNameNoFile, + &AbstractImporterTest::meshNameOutOfRange, + &AbstractImporterTest::meshNotImplemented, + &AbstractImporterTest::meshNoFile, + &AbstractImporterTest::meshOutOfRange, + &AbstractImporterTest::meshCustomIndexDataDeleter, + &AbstractImporterTest::meshCustomVertexDataDeleter, + &AbstractImporterTest::meshCustomAttributesDeleter, + + &AbstractImporterTest::meshAttributeName, + &AbstractImporterTest::meshAttributeNameNotImplemented, + &AbstractImporterTest::meshAttributeNameNotCustom, + &AbstractImporterTest::mesh2D, &AbstractImporterTest::mesh2DCountNotImplemented, &AbstractImporterTest::mesh2DCountNoFile, @@ -2031,6 +2070,287 @@ void AbstractImporterTest::object3DOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object3D(): index 8 out of range for 8 entries\n"); } +void AbstractImporterTest::mesh() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + Int doMeshForName(const std::string& name) override { + if(name == "eighth") return 7; + else return -1; + } + std::string doMeshName(UnsignedInt id) override { + if(id == 7) return "eighth"; + else return {}; + } + Containers::Optional doMesh(UnsignedInt id) override { + /* Verify that initializer list is converted to an array with + the default deleter and not something disallowed */ + if(id == 7) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, &state}; + else return {}; + } + } importer; + + CORRADE_COMPARE(importer.meshCount(), 8); + CORRADE_COMPARE(importer.meshForName("eighth"), 7); + CORRADE_COMPARE(importer.meshName(7), "eighth"); + + auto data = importer.mesh(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); +} + +void AbstractImporterTest::meshCountNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + CORRADE_COMPARE(importer.meshCount(), 0); +} + +void AbstractImporterTest::meshCountNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.meshCount(); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshCount(): no file opened\n"); +} + +void AbstractImporterTest::meshForNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + } importer; + + CORRADE_COMPARE(importer.meshForName(""), -1); +} + +void AbstractImporterTest::meshForNameNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.meshForName(""); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshForName(): no file opened\n"); +} + +void AbstractImporterTest::meshNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + CORRADE_COMPARE(importer.meshName(7), ""); +} + +void AbstractImporterTest::meshNameNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.meshName(42); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshName(): no file opened\n"); +} + +void AbstractImporterTest::meshNameOutOfRange() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.meshName(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshName(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::meshNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(7); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): not implemented\n"); +} + +void AbstractImporterTest::meshNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(42); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): no file opened\n"); +} + +void AbstractImporterTest::meshOutOfRange() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::meshCustomIndexDataDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt) override { + return MeshData{MeshPrimitive::Triangles, Containers::Array{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}}; + } + + char data[1]; + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(0); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractImporterTest::meshCustomVertexDataDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt) override { + return MeshData{MeshPrimitive::Triangles, Containers::Array{nullptr, 0, [](char*, std::size_t) {}}, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}}; + } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(0); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractImporterTest::meshCustomAttributesDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt) override { + return MeshData{MeshPrimitive::Triangles, nullptr, Containers::Array{&positions, 1, [](MeshAttributeData*, std::size_t) {}}}; + } + + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, nullptr}; + } importer; + + std::ostringstream out; + Error redirectError{&out}; + + importer.mesh(0); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractImporterTest::meshAttributeName() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + MeshAttribute doMeshAttributeForName(const std::string& name) override { + if(name == "SMOOTH_GROUP_ID") return meshAttributeCustom(37); + return MeshAttribute{}; + } + + std::string doMeshAttributeName(UnsignedShort id) override { + if(id == 37) return "SMOOTH_GROUP_ID"; + return ""; + } + } importer; + + CORRADE_COMPARE(importer.meshAttributeForName("SMOOTH_GROUP_ID"), meshAttributeCustom(37)); + CORRADE_COMPARE(importer.meshAttributeName(meshAttributeCustom(37)), "SMOOTH_GROUP_ID"); +} + +void AbstractImporterTest::meshAttributeNameNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + CORRADE_COMPARE(importer.meshAttributeForName(""), MeshAttribute{}); + CORRADE_COMPARE(importer.meshAttributeName(meshAttributeCustom(37)), ""); +} + +void AbstractImporterTest::meshAttributeNameNotCustom() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + + MeshAttribute doMeshAttributeForName(const std::string&) override { + return MeshAttribute::Position; + } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.meshAttributeForName("SMOOTH_GROUP_ID"); + importer.meshAttributeName(MeshAttribute::Position); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::meshAttributeForName(): implementation-returned Trade::MeshAttribute::Position is neither custom nor invalid\n" + "Trade::AbstractImporter::meshAttributeName(): Trade::MeshAttribute::Position is not custom\n"); +} + void AbstractImporterTest::mesh2D() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } From fcd38cabc78342019aab0a7e3171f75cec4cf136 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 10 Nov 2019 21:13:37 +0100 Subject: [PATCH 027/107] Trade: new enum for describing data ownership. Will be used for MeshData that don't own the memory. --- src/Magnum/Trade/CMakeLists.txt | 2 + src/Magnum/Trade/Data.cpp | 53 +++++++++++++++++ src/Magnum/Trade/Data.h | 87 ++++++++++++++++++++++++++++ src/Magnum/Trade/Test/CMakeLists.txt | 1 + src/Magnum/Trade/Test/DataTest.cpp | 62 ++++++++++++++++++++ src/Magnum/Trade/Trade.h | 9 ++- 6 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 src/Magnum/Trade/Data.cpp create mode 100644 src/Magnum/Trade/Data.h create mode 100644 src/Magnum/Trade/Test/DataTest.cpp diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index fc21d1d781..28afc0091f 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -27,6 +27,7 @@ find_package(Corrade REQUIRED PluginManager) set(MagnumTrade_SRCS AbstractMaterialData.cpp + Data.cpp LightData.cpp MeshData2D.cpp MeshData3D.cpp @@ -52,6 +53,7 @@ set(MagnumTrade_HEADERS AbstractMaterialData.h AnimationData.h CameraData.h + Data.h ImageData.h LightData.h MeshData.h diff --git a/src/Magnum/Trade/Data.cpp b/src/Magnum/Trade/Data.cpp new file mode 100644 index 0000000000..a0bc474e22 --- /dev/null +++ b/src/Magnum/Trade/Data.cpp @@ -0,0 +1,53 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Data.h" + +#include + +namespace Magnum { namespace Trade { + +Debug& operator<<(Debug& debug, const DataFlag value) { + debug << "Trade::DataFlag" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(v) case DataFlag::v: return debug << "::" #v; + _c(Owned) + _c(Mutable) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const DataFlags value) { + return Containers::enumSetDebugOutput(debug, value, "Trade::DataFlags{}", { + DataFlag::Owned, + DataFlag::Mutable}); +} + +}} diff --git a/src/Magnum/Trade/Data.h b/src/Magnum/Trade/Data.h new file mode 100644 index 0000000000..9f2abcaf5b --- /dev/null +++ b/src/Magnum/Trade/Data.h @@ -0,0 +1,87 @@ +#ifndef Magnum_Trade_Data_h +#define Magnum_Trade_Data_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Enum @ref Magnum::Trade::DataFlag, enum set @ref Magnum::Trade::DataFlags + * @m_since_latest + */ + +#include + +#include "Magnum/Magnum.h" +#include "Magnum/Trade/visibility.h" + +namespace Magnum { namespace Trade { + +/** +@brief Data flag +@m_since_latest + +@see @ref DataFlags, @ref MeshData::dataFlags() +*/ +enum class DataFlag: UnsignedByte { + /** + * Data are owned by the instance. If this flag is not set, the instance + * might be for example referencing a memory-mapped file or a constant + * memory. + */ + Owned = 1 << 0, + + /** + * Data are mutable. If this flag is not set, the instance might be for + * example referencing a readonly memory-mapped file or a constant memory. + */ + Mutable = 2 << 0 + + /** @todo owned by importer, owned by the GPU, ... */ +}; + +/** +@debugoperatorenum{DataFlag} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlag value); + +/** +@brief Data flags +@m_since_latest + +@see @ref MeshData::dataFlags() +*/ +typedef Containers::EnumSet DataFlags; + +CORRADE_ENUMSET_OPERATORS(DataFlags) + +/** +@debugoperatorenum{DataFlags} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlags value); + +}} + +#endif diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index fc781c5c1c..2c9177347c 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -44,6 +44,7 @@ target_include_directories(TradeAbstractImporterTest PRIVATE ${CMAKE_CURRENT_BIN corrade_add_test(TradeAnimationDataTest AnimationDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTestLib) +corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) diff --git a/src/Magnum/Trade/Test/DataTest.cpp b/src/Magnum/Trade/Test/DataTest.cpp new file mode 100644 index 0000000000..e1253d498b --- /dev/null +++ b/src/Magnum/Trade/Test/DataTest.cpp @@ -0,0 +1,62 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include + +#include "Magnum/Trade/Data.h" + +namespace Magnum { namespace Trade { namespace Test { namespace { + +struct DataTest: TestSuite::Tester { + explicit DataTest(); + + void debugDataFlag(); + void debugDataFlags(); +}; + +DataTest::DataTest() { + addTests({&DataTest::debugDataFlag, + &DataTest::debugDataFlags}); +} + +void DataTest::debugDataFlag() { + std::ostringstream out; + + Debug{&out} << DataFlag::Owned << DataFlag(0xf0); + CORRADE_COMPARE(out.str(), "Trade::DataFlag::Owned Trade::DataFlag(0xf0)\n"); +} + +void DataTest::debugDataFlags() { + std::ostringstream out; + + Debug{&out} << (DataFlag::Owned|DataFlag::Mutable) << DataFlags{}; + CORRADE_COMPARE(out.str(), "Trade::DataFlag::Owned|Trade::DataFlag::Mutable Trade::DataFlags{}\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Trade::Test::DataTest) diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index 0803127ac2..36c16f70bb 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -29,12 +29,12 @@ * @brief Forward declarations for the @ref Magnum::Trade namespace */ -#include "Magnum/Types.h" +#include + +#include "Magnum/Magnum.h" #ifdef MAGNUM_BUILD_DEPRECATED #include - -#include "Magnum/Magnum.h" #endif namespace Magnum { namespace Trade { @@ -59,6 +59,9 @@ class AnimationData; enum class CameraType: UnsignedByte; class CameraData; +enum class DataFlag: UnsignedByte; +typedef Containers::EnumSet DataFlags; + template class ImageData; typedef ImageData<1> ImageData1D; typedef ImageData<2> ImageData2D; From 8ea86b05f936f0f8534f120ca18a30f5dbfc3d42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 11 Nov 2019 16:42:48 +0100 Subject: [PATCH 028/107] Trade: implement mutable data access in MeshData. Turns out the design wasn't so simple after all. AnimationData and ImageData classes will follow with similar changes. --- doc/snippets/MagnumTrade.cpp | 15 + src/Magnum/Trade/Data.cpp | 4 + src/Magnum/Trade/Data.h | 10 +- .../Trade/Implementation/arrayUtilities.h | 5 +- src/Magnum/Trade/MeshData.cpp | 62 ++- src/Magnum/Trade/MeshData.h | 287 ++++++++++++- src/Magnum/Trade/Test/MeshDataTest.cpp | 403 +++++++++++++++++- 7 files changed, 766 insertions(+), 20 deletions(-) diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index d9600728e2..82296fae2e 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -266,6 +266,21 @@ if(data.isIndexed()) { } #endif +{ +Trade::MeshData data{MeshPrimitive::Points, 0}; +/* [MeshData-usage-mutable] */ +/* Check prerequisites */ +if(!(data.vertexDataFlags() & Trade::DataFlag::Mutable) || + !data.hasAttribute(Trade::MeshAttribute::Position) || + data.attributeFormat(Trade::MeshAttribute::Position) != VertexFormat::Vector3) + Fatal{} << "Oh well"; + +/* Scale the mesh two times */ +MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{2.0f}), + data.mutableAttribute(Trade::MeshAttribute::Position)); +/* [MeshData-usage-mutable] */ +} + { Trade::MeshData2D& foo(); Trade::MeshData2D& data = foo(); diff --git a/src/Magnum/Trade/Data.cpp b/src/Magnum/Trade/Data.cpp index a0bc474e22..b7efc41c89 100644 --- a/src/Magnum/Trade/Data.cpp +++ b/src/Magnum/Trade/Data.cpp @@ -50,4 +50,8 @@ Debug& operator<<(Debug& debug, const DataFlags value) { DataFlag::Mutable}); } +namespace Implementation { + void nonOwnedArrayDeleter(char*, std::size_t) { /* does nothing */ } +} + }} diff --git a/src/Magnum/Trade/Data.h b/src/Magnum/Trade/Data.h index 9f2abcaf5b..bc74cce2f3 100644 --- a/src/Magnum/Trade/Data.h +++ b/src/Magnum/Trade/Data.h @@ -41,7 +41,8 @@ namespace Magnum { namespace Trade { @brief Data flag @m_since_latest -@see @ref DataFlags, @ref MeshData::dataFlags() +@see @ref DataFlags, @ref MeshData::indexDataFlags(), + @ref MeshData::vertexDataFlags() */ enum class DataFlag: UnsignedByte { /** @@ -70,7 +71,7 @@ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlag value); @brief Data flags @m_since_latest -@see @ref MeshData::dataFlags() +@see @ref MeshData::indexDataFlags(), @ref MeshData::vertexDataFlags() */ typedef Containers::EnumSet DataFlags; @@ -82,6 +83,11 @@ CORRADE_ENUMSET_OPERATORS(DataFlags) */ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlags value); +namespace Implementation { + /* Used internally by MeshData */ + MAGNUM_TRADE_EXPORT void nonOwnedArrayDeleter(char*, std::size_t); +} + }} #endif diff --git a/src/Magnum/Trade/Implementation/arrayUtilities.h b/src/Magnum/Trade/Implementation/arrayUtilities.h index fc1420fde1..4ae2efd2a0 100644 --- a/src/Magnum/Trade/Implementation/arrayUtilities.h +++ b/src/Magnum/Trade/Implementation/arrayUtilities.h @@ -32,9 +32,8 @@ namespace Magnum { namespace Trade { namespace Implementation { -/* Can't use InPlaceInit as that uses a custom deleters. Compared to - InPlaceInit it does an an unnecessary default-initialization of all - elements */ +/* Can't use InPlaceInit as that uses custom deleters. Compared to InPlaceInit + it does an an unnecessary default-initialization of all elements. */ /** @todo isn't there some C++56 feature that would allow me to allocate without calling constructors? */ template Containers::Array initializerListToArrayWithDefaultDeleter(const std::initializer_list list) { diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 67c67cd01f..79c693610f 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -58,7 +58,12 @@ MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexForma "Trade::MeshAttributeData:" << format << "is not a valid format for" << name, ); } -MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: _indexType{indices.type}, _primitive{primitive}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{indices.data} { +Containers::Array meshAttributeDataNonOwningArray(const Containers::ArrayView view) { + /* Ugly, eh? */ + return Containers::Array{const_cast(view.data()), view.size(), reinterpret_cast(Trade::Implementation::nonOwnedArrayDeleter)}; +} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: _indexType{indices.type}, _primitive{primitive}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{indices.data} { /* Save vertex count. It's a strided array view, so the size is not depending on type. */ if(_attributes.empty()) { @@ -92,13 +97,54 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* const importerState): MeshData{primitive, std::move(indexData), indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), importerState} { + CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); + CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); + _indexDataFlags = indexDataFlags; + _vertexDataFlags = vertexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, indexDataFlags, indexData, indices, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, std::move(vertexData), std::move(attributes), importerState} { + CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); + _indexDataFlags = indexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, indexDataFlags, indexData, indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), importerState} { + CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); + _vertexDataFlags = vertexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, std::move(indexData), indices, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} + MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, std::move(vertexData), std::move(attributes), importerState} {} MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), importerState} { + CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); + _vertexDataFlags = vertexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const void* const importerState): MeshData{primitive, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} + MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, {}, {}, importerState} {} -MeshData::MeshData(const MeshPrimitive primitive, const UnsignedInt vertexCount, const void* const importerState) noexcept: _vertexCount{vertexCount}, _indexType{}, _primitive{primitive}, _importerState{importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, importerState} { + CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), + "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); + _indexDataFlags = indexDataFlags; +} + +MeshData::MeshData(const MeshPrimitive primitive, const UnsignedInt vertexCount, const void* const importerState) noexcept: _vertexCount{vertexCount}, _indexType{}, _primitive{primitive}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState} {} MeshData::~MeshData() = default; @@ -106,6 +152,18 @@ MeshData::MeshData(MeshData&&) noexcept = default; MeshData& MeshData::operator=(MeshData&&) noexcept = default; +Containers::ArrayView MeshData::mutableIndexData() & { + CORRADE_ASSERT(_indexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableIndexData(): index data not mutable", {}); + return _indexData; +} + +Containers::ArrayView MeshData::mutableVertexData() & { + CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableVertexData(): vertex data not mutable", {}); + return _vertexData; +} + UnsignedInt MeshData::indexCount() const { CORRADE_ASSERT(isIndexed(), "Trade::MeshData::indexCount(): the mesh is not indexed", {}); diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index de41b4d3b7..e5ac1865b7 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -35,8 +35,8 @@ #include "Magnum/Mesh.h" #include "Magnum/VertexFormat.h" +#include "Magnum/Trade/Data.h" #include "Magnum/Trade/Trade.h" -#include "Magnum/Trade/visibility.h" namespace Magnum { namespace Trade { @@ -243,6 +243,16 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { Containers::StridedArrayView1D data; }; +/** @relatesalso MeshAttributeData +@brief Create a non-owning array of @ref MeshAttributeData items +@m_since_latest + +Useful when you have the attribute definitions statically defined (for example +when the vertex data themselves are already defined at compile time) and don't +want to allocate just to pass those to @ref MeshData. +*/ +Containers::Array MAGNUM_TRADE_EXPORT meshAttributeDataNonOwningArray(Containers::ArrayView view); + /** @brief Mesh data @m_since_latest @@ -275,6 +285,20 @@ the GPU know of the format and layout: @snippet MagnumTrade.cpp MeshData-usage-advanced +@section Trade-MeshData-usage-mutable Mutable data access + +The interfaces implicitly provide @cpp const @ce views on the contained index +and vertex data through the @ref indexData(), @ref vertexData(), +@ref indices() and @ref attribute() accessors. This is done because in general +case the data can also refer to a memory-mapped file or constant memory. In +cases when it's desirable to modify the data in-place, there's the +@ref mutableIndexData(), @ref mutableVertexData(), @ref mutableIndices() and +@ref mutableAttribute() set of functions. To use these, you need to check that +the data are mutable using @ref indexDataFlags() or @ref vertexDataFlags() +first. The following snippet applies a transformation to the mesh data: + +@snippet MagnumTrade.cpp MeshData-usage-mutable + @see @ref AbstractImporter::mesh() */ class MAGNUM_TRADE_EXPORT MeshData { @@ -295,6 +319,12 @@ class MAGNUM_TRADE_EXPORT MeshData { * index-less attribute-less mesh, use * @ref MeshData(MeshPrimitive, UnsignedInt, const void*) to specify * desired vertex count. + * + * The @ref indexDataFlags() / @ref vertexDataFlags() are implicitly + * set to a combination of @ref DataFlag::Owned and + * @ref DataFlag::Mutable. For non-owned data use the + * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, const MeshIndexData&, DataFlags, Containers::ArrayView, Containers::Array&&, const void*) + * constructor or its variants instead. */ explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; @@ -302,6 +332,77 @@ class MAGNUM_TRADE_EXPORT MeshData { /* Not noexcept because allocation happens inside */ explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + /** + * @brief Construct indexed mesh data with non-owned index and vertex data + * @param primitive Primitive + * @param indexDataFlags Index data flags + * @param indexData View on index data + * @param indices Index data description + * @param vertexDataFlags Vertex data flags + * @param vertexData View on vertex data + * @param attributes Description of all vertex attribute data + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed vertex and index + * data. The @p indexDataFlags / @p vertexDataFlags parameters can + * contain @ref DataFlag::Mutable to indicate the external data can be + * modified, and is expected to *not* have @ref DataFlag::Owned set. + */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const void* importerState = nullptr); + + /** + * @brief Construct indexed mesh data with non-owned index data + * @param primitive Primitive + * @param indexDataFlags Index data flags + * @param indexData View on index data + * @param indices Index data description + * @param vertexData Vertex data + * @param attributes Description of all vertex attribute data + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed index data. The + * @p indexDataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. The @ref vertexDataFlags() are + * implicitly set to a combination of @ref DataFlag::Owned and + * @ref DataFlag::Mutable. + */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + + /** + * @brief Construct indexed mesh data with non-owned vertex data + * @param primitive Primitive + * @param indexData Index data + * @param indices Index data description + * @param vertexDataFlags Vertex data flags + * @param vertexData View on vertex data + * @param attributes Description of all vertex attribute data + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed vertex data. The + * @p vertexDataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. The @ref indexDataFlags() are + * implicitly set to a combination of @ref DataFlag::Owned and + * @ref DataFlag::Mutable. + */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const void* importerState = nullptr); + /** * @brief Construct a non-indexed mesh data * @param primitive Primitive @@ -311,6 +412,14 @@ class MAGNUM_TRADE_EXPORT MeshData { * * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) * with default-constructed @p indexData and @p indices arguments. + * + * The @ref vertexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For consistency, + * the @ref indexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there + * isn't any data to own or to mutate. For non-owned data use the + * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, Containers::Array&&, const void*) + * constructor instead. */ explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; @@ -318,6 +427,29 @@ class MAGNUM_TRADE_EXPORT MeshData { /* Not noexcept because allocation happens inside */ explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + /** + * @brief Construct a non-owned non-indexed mesh data + * @param primitive Primitive + * @param vertexDataFlags Vertex data flags + * @param vertexData View on vertex data + * @param attributes Description of all vertex attribute data + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p vertexDataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. For consistency, the + * @ref indexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there + * isn't any data to own or to mutate. + */ + explicit MeshData(MeshPrimitive primitive, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + + /** @overload */ + /* Not noexcept because allocation happens inside */ + explicit MeshData(MeshPrimitive primitive, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const void* importerState = nullptr); + /** * @brief Construct an attribute-less indexed mesh data * @param primitive Primitive @@ -331,16 +463,47 @@ class MAGNUM_TRADE_EXPORT MeshData { * to create an index-less attribute-less mesh, use * @ref MeshData(MeshPrimitive, UnsignedInt, const void*) to specify * desired vertex count. + * + * The @ref indexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For consistency, + * the @ref vertexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there + * isn't any data to own or to mutate. For non-owned data use the + * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, const MeshIndexData&, const void*) + * constructor instead. */ explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const void* importerState = nullptr) noexcept; + /** + * @brief Construct a non-owned attribute-less indexed mesh data + * @param primitive Primitive + * @param indexDataFlags Index data flags + * @param indexData View on index data + * @param indices Index data description + * @param importerState Importer-specific state + * + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, const void*) + * creates an instance that doesn't own the passed data. The + * @p indexDataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. For consistency, the + * @ref vertexDataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there + * isn't any data to own or to mutate. + */ + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, const void* importerState = nullptr) noexcept; + /** * @brief Construct an index-less attribute-less mesh data * @param primitive Primitive * @param vertexCount Desired count of vertices to draw * @param importerState Importer-specific state * - * Useful in case the drawing is fully driven by a shader. + * Useful in case the drawing is fully driven by a shader. For + * consistency, the @ref indexDataFlags() / @ref vertexDataFlags() are + * implicitly set to a combination of @ref DataFlag::Owned and + * @ref DataFlag::Mutable, even though there isn't any data to own or + * to mutate. */ explicit MeshData(MeshPrimitive primitive, UnsignedInt vertexCount, const void* importerState = nullptr) noexcept; @@ -358,6 +521,22 @@ class MAGNUM_TRADE_EXPORT MeshData { /** @brief Move assignment */ MeshData& operator=(MeshData&&) noexcept; + /** + * @brief Index data flags + * + * @see @ref releaseIndexData(), @ref mutableIndexData(), + * @ref mutableIndices() + */ + DataFlags indexDataFlags() const { return _indexDataFlags; } + + /** + * @brief Vertex data flags + * + * @see @ref releaseVertexData(), @ref mutableVertexData(), + * @ref mutableAttribute() + */ + DataFlags vertexDataFlags() const { return _vertexDataFlags; } + /** @brief Primitive */ MeshPrimitive primitive() const { return _primitive; } @@ -366,13 +545,25 @@ class MAGNUM_TRADE_EXPORT MeshData { * * Returns @cpp nullptr @ce if the mesh is not indexed. * @see @ref isIndexed(), @ref indexCount(), @ref indexType(), - * @ref indices(), @ref releaseIndexData() + * @ref indices(), @ref mutableIndexData(), @ref releaseIndexData() */ Containers::ArrayView indexData() const & { return _indexData; } /** @brief Taking a view to a r-value instance is not allowed */ Containers::ArrayView indexData() const && = delete; + /** + * @brief Mutable raw index data + * + * Like @ref indexData(), but returns a non-const view. Expects that + * the mesh is mutable. + * @see @ref indexDataFlags() + */ + Containers::ArrayView mutableIndexData() &; + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView mutableIndexData() && = delete; + /** * @brief Raw vertex data * @@ -380,13 +571,25 @@ class MAGNUM_TRADE_EXPORT MeshData { * the mesh has no attributes. * @see @ref attributeCount(), @ref attributeName(), * @ref attributeFormat(), @ref attribute(), - * @ref releaseVertexData() + * @ref mutableVertexData(), @ref releaseVertexData() */ Containers::ArrayView vertexData() const & { return _vertexData; } /** @brief Taking a view to a r-value instance is not allowed */ Containers::ArrayView vertexData() const && = delete; + /** + * @brief Mutable raw vertex data + * + * Like @ref vertexData(), but returns a non-const view. Expects that + * the mesh is mutable. + * @see @ref vertexDataFlags() + */ + Containers::ArrayView mutableVertexData() &; + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView mutableVertexData() && = delete; + /** @brief Whether the mesh is indexed */ bool isIndexed() const { return _indexType != MeshIndexType{}; } @@ -418,10 +621,19 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref indicesAsArray() accessor to get indices converted to 32-bit, * but note that such operation involves extra allocation and data * conversion. - * @see @ref isIndexed(), @ref attribute() + * @see @ref isIndexed(), @ref attribute(), @ref mutableIndices() */ template Containers::ArrayView indices() const; + /** + * @brief Mutable mesh indices + * + * Like @ref indices() const, but returns a mutable view. Expects that + * the mesh is mutable. + * @see @ref indexDataFlags() + */ + template Containers::ArrayView mutableIndices(); + /** * @brief Mesh vertex count * @@ -548,10 +760,20 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref colorsAsArray() accessors to get common attributes converted to * usual types, but note that these operations involve extra allocation * and data conversion. - * @see @ref attribute(MeshAttribute, UnsignedInt) const + * @see @ref attribute(MeshAttribute, UnsignedInt) const, + * @ref mutableAttribute(MeshAttribute, UnsignedInt) */ template Containers::StridedArrayView1D attribute(UnsignedInt id) const; + /** + * @brief Mutable data for given attribute array + * + * Like @ref attribute(UnsignedInt) const, but returns a mutable view. + * Expects that the mesh is mutable. + * @see @ref vertexDataFlags() + */ + template Containers::StridedArrayView1D mutableAttribute(UnsignedInt id); + /** * @brief Data for given named attribute array * @@ -564,10 +786,20 @@ class MAGNUM_TRADE_EXPORT MeshData { * accessors to get common attributes converted to usual types, but * note that these operations involve extra data conversion and an * allocation. - * @see @ref attribute(UnsignedInt) const + * @see @ref attribute(UnsignedInt) const, + * @ref mutableAttribute(MeshAttribute, UnsignedInt) */ template Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0) const; + /** + * @brief Mutable data for given named attribute array + * + * Like @ref attribute(MeshAttribute, UnsignedInt) const, but returns a + * mutable view. Expects that the mesh is mutable. + * @see @ref vertexDataFlags() + */ + template Containers::StridedArrayView1D mutableAttribute(MeshAttribute name, UnsignedInt id = 0); + /** * @brief Indices as 32-bit integers * @@ -701,8 +933,10 @@ class MAGNUM_TRADE_EXPORT MeshData { * * Releases the ownership of the index data array and resets internal * index-related state to default. The mesh then behaves like - * non-indexed. - * @see @ref indexData() + * non-indexed. Note that the returned array has a custom no-op deleter + * when the data are not owned by the mesh, and while the returned + * array type is mutable, the actual memory might be not. + * @see @ref indexData(), @ref indexDataFlags() */ Containers::Array releaseIndexData(); @@ -711,8 +945,10 @@ class MAGNUM_TRADE_EXPORT MeshData { * * Releases the ownership of the index data array and resets internal * attribute-related state to default. The mesh then behaves like if - * it has no attributes. - * @see @ref vertexData() + * it has no attributes. Note that the returned array has a custom + * no-op deleter when the data are not owned by the mesh, and while the + * returned array type is mutable, the actual memory might be not. + * @see @ref vertexData(), @ref vertexDataFlags() */ Containers::Array releaseVertexData(); @@ -734,6 +970,7 @@ class MAGNUM_TRADE_EXPORT MeshData { UnsignedInt _vertexCount; MeshIndexType _indexType; MeshPrimitive _primitive; + DataFlags _indexDataFlags, _vertexDataFlags; const void* _importerState; Containers::Array _indexData, _vertexData; Containers::Array _attributes; @@ -789,6 +1026,16 @@ template Containers::ArrayView MeshData::indices() const { return Containers::arrayCast(_indices); } +template Containers::ArrayView MeshData::mutableIndices() { + CORRADE_ASSERT(_indexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableIndices(): index data not mutable", {}); + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::mutableIndices(): the mesh is not indexed", {}); + CORRADE_ASSERT(Implementation::meshIndexTypeFor() == _indexType, + "Trade::MeshData::mutableIndices(): improper type requested for" << _indexType, nullptr); + return Containers::arrayCast(reinterpret_cast&>(_indices)); +} + template Containers::StridedArrayView1D MeshData::attribute(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); @@ -797,12 +1044,30 @@ template Containers::StridedArrayView1D MeshData::attribute(Un return Containers::arrayCast(_attributes[id].data); } +template Containers::StridedArrayView1D MeshData::mutableAttribute(UnsignedInt id) { + CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[id].format, + "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[id].name << "of format" << _attributes[id].format, nullptr); + return Containers::arrayCast(reinterpret_cast&>(_attributes[id].data)); +} + template Containers::StridedArrayView1D MeshData::attribute(MeshAttribute name, UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(name, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); return attribute(attributeId); } +template Containers::StridedArrayView1D MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { + CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return mutableAttribute(attributeId); +} + }} #endif diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index c3cd6a6c57..7f6a0caac4 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -51,6 +51,7 @@ struct MeshDataTest: TestSuite::Tester { void constructAttributeWrongFormat(); void constructAttributeTypeErased(); void constructAttributeTypeErasedWrongStride(); + void constructAttributeNonOwningArray(); void construct(); void constructIndexless(); @@ -59,6 +60,12 @@ struct MeshDataTest: TestSuite::Tester { void constructIndexlessAttributeless(); void constructIndexlessAttributelessZeroVertices(); + void constructNotOwned(); + void constructIndicesNotOwned(); + void constructVerticesNotOwned(); + void constructIndexlessNotOwned(); + void constructAttributelessNotOwned(); + void constructIndexDataButNotIndexed(); void constructVertexDataButNoAttributes(); void constructVertexDataButNoVertices(); @@ -66,6 +73,12 @@ struct MeshDataTest: TestSuite::Tester { void constructIndicesNotContained(); void constructAttributeNotContained(); void constructInconsitentVertexCount(); + void constructNotOwnedIndexFlagOwned(); + void constructNotOwnedVertexFlagOwned(); + void constructIndicesNotOwnedFlagOwned(); + void constructVerticesNotOwnedFlagOwned(); + void constructIndexlessNotOwnedFlagOwned(); + void constructAttributelessNotOwnedFlagOwned(); void constructCopy(); void constructMove(); @@ -83,6 +96,8 @@ struct MeshDataTest: TestSuite::Tester { template void colorsAsArray(); void colorsIntoArrayInvalidSize(); + void mutableAccessNotAllowed(); + void indicesNotIndexed(); void indicesWrongType(); @@ -93,6 +108,24 @@ struct MeshDataTest: TestSuite::Tester { void releaseVertexData(); }; +struct { + const char* name; + DataFlags indexDataFlags, vertexDataFlags; +} NotOwnedData[] { + {"", {}, {}}, + {"indices mutable", DataFlag::Mutable, {}}, + {"vertices mutable", {}, DataFlag::Mutable}, + {"both mutable", DataFlag::Mutable, DataFlag::Mutable} +}; + +struct { + const char* name; + DataFlags dataFlags; +} SingleNotOwnedData[] { + {"", {}}, + {"mutable", DataFlag::Mutable} +}; + MeshDataTest::MeshDataTest() { addTests({&MeshDataTest::customAttributeName, &MeshDataTest::customAttributeNameTooLarge, @@ -109,21 +142,36 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttributeWrongFormat, &MeshDataTest::constructAttributeTypeErased, &MeshDataTest::constructAttributeTypeErasedWrongStride, + &MeshDataTest::constructAttributeNonOwningArray, &MeshDataTest::construct, &MeshDataTest::constructIndexless, &MeshDataTest::constructIndexlessZeroVertices, &MeshDataTest::constructAttributeless, &MeshDataTest::constructIndexlessAttributeless, - &MeshDataTest::constructIndexlessAttributelessZeroVertices, + &MeshDataTest::constructIndexlessAttributelessZeroVertices}); - &MeshDataTest::constructIndexDataButNotIndexed, + addInstancedTests({&MeshDataTest::constructNotOwned}, + Containers::arraySize(NotOwnedData)); + addInstancedTests({&MeshDataTest::constructIndicesNotOwned, + &MeshDataTest::constructVerticesNotOwned, + &MeshDataTest::constructIndexlessNotOwned, + &MeshDataTest::constructAttributelessNotOwned}, + Containers::arraySize(SingleNotOwnedData)); + + addTests({&MeshDataTest::constructIndexDataButNotIndexed, &MeshDataTest::constructVertexDataButNoAttributes, &MeshDataTest::constructVertexDataButNoVertices, &MeshDataTest::constructAttributelessInvalidIndices, &MeshDataTest::constructIndicesNotContained, &MeshDataTest::constructAttributeNotContained, &MeshDataTest::constructInconsitentVertexCount, + &MeshDataTest::constructNotOwnedIndexFlagOwned, + &MeshDataTest::constructNotOwnedVertexFlagOwned, + &MeshDataTest::constructIndicesNotOwnedFlagOwned, + &MeshDataTest::constructVerticesNotOwnedFlagOwned, + &MeshDataTest::constructIndexlessNotOwnedFlagOwned, + &MeshDataTest::constructAttributelessNotOwnedFlagOwned, &MeshDataTest::constructCopy, &MeshDataTest::constructMove, @@ -146,6 +194,8 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::colorsAsArray, &MeshDataTest::colorsIntoArrayInvalidSize, + &MeshDataTest::mutableAccessNotAllowed, + &MeshDataTest::indicesNotIndexed, &MeshDataTest::indicesWrongType, @@ -312,6 +362,13 @@ void MeshDataTest::constructAttributeTypeErasedWrongStride() { CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: view stride 1 is not large enough to contain VertexFormat::Vector3\n"); } +void MeshDataTest::constructAttributeNonOwningArray() { + const MeshAttributeData data[3]; + Containers::Array array = meshAttributeDataNonOwningArray(data); + CORRADE_COMPARE(array.size(), 3); + CORRADE_COMPARE(static_cast(array.data()), data); +} + void MeshDataTest::construct() { struct Vertex { Vector3 position; @@ -360,9 +417,13 @@ void MeshDataTest::construct() { std::move(vertexData), {positions, textureCoordinates, normals, textureCoordinates, ids}, &importerState}; /* Basics */ + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); CORRADE_COMPARE(static_cast(data.indexData()), indexView.data()); CORRADE_COMPARE(static_cast(data.vertexData()), vertexView.data()); + CORRADE_COMPARE(static_cast(data.mutableIndexData()), indexView.data()); + CORRADE_COMPARE(static_cast(data.mutableVertexData()), vertexView.data()); CORRADE_COMPARE(data.importerState(), &importerState); /* Index access */ @@ -400,6 +461,11 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attribute(2)[2], Vector3::zAxis()); CORRADE_COMPARE(data.attribute(3)[1], (Vector2{0.250f, 0.375f})); CORRADE_COMPARE(data.attribute(4)[1], -374); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.mutableAttribute(1)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.mutableAttribute(2)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.mutableAttribute(3)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.mutableAttribute(4)[1], -374); /* Attribute access by name */ CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Position)); @@ -438,6 +504,11 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f})); CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2], 22); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Normal)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2], 22); } void MeshDataTest::constructIndexless() { @@ -450,6 +521,10 @@ void MeshDataTest::constructIndexless() { int importerState; MeshAttributeData positions{MeshAttribute::Position, vertexView}; MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions}, &importerState}; + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + /* These are empty so it doesn't matter, but this is a nice non-restrictive + default */ + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::LineLoop); CORRADE_COMPARE(data.indexData(), nullptr); CORRADE_COMPARE(data.importerState(), &importerState); @@ -487,6 +562,10 @@ void MeshDataTest::constructAttributeless() { int importerState; MeshIndexData indices{indexView}; MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), indices, &importerState}; + /* These are empty so it doesn't matter, but this is a nice non-restrictive + default */ + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); CORRADE_COMPARE(data.vertexData(), nullptr); CORRADE_COMPARE(data.importerState(), &importerState); @@ -502,9 +581,215 @@ void MeshDataTest::constructAttributeless() { CORRADE_COMPARE(data.attributeCount(), 0); } +void MeshDataTest::constructNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + UnsignedShort indexData[]{0, 1, 0}; + Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + int importerState; + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + MeshData data{MeshPrimitive::Triangles, instanceData.indexDataFlags, Containers::arrayView(indexData), indices, instanceData.vertexDataFlags, Containers::arrayView(vertexData), {positions}, &importerState}; + + CORRADE_COMPARE(data.indexDataFlags(), instanceData.indexDataFlags); + CORRADE_COMPARE(data.vertexDataFlags(), instanceData.vertexDataFlags); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(data.indexData()), +indexData); + CORRADE_COMPARE(static_cast(data.vertexData()), +vertexData); + if(instanceData.indexDataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableIndexData()), +indexData); + if(instanceData.vertexDataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableVertexData()), +vertexData); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[1], 1); + CORRADE_COMPARE(data.indices()[2], 0); + if(instanceData.indexDataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableIndices()[1], 1); + CORRADE_COMPARE(data.mutableIndices()[2], 0); + } + + CORRADE_COMPARE(data.vertexCount(), 2); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(data.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.attribute(0)[1], (Vector2{0.4f, 0.5f})); + if(instanceData.vertexDataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableAttribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector2{0.4f, 0.5f})); + } +} + +void MeshDataTest::constructIndicesNotOwned() { + auto&& instanceData = SingleNotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + UnsignedShort indexData[]{0, 1, 0}; + Containers::Array vertexData{2*sizeof(Vector2)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0] = {0.1f, 0.2f}; + vertexView[1] = {0.4f, 0.5f}; + + int importerState; + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + MeshData data{MeshPrimitive::Triangles, instanceData.dataFlags, Containers::arrayView(indexData), indices, std::move(vertexData), {positions}, &importerState}; + + CORRADE_COMPARE(data.indexDataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(data.indexData()), +indexData); + CORRADE_COMPARE(static_cast(data.vertexData()), vertexView.data()); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableIndexData()), +indexData); + CORRADE_COMPARE(static_cast(data.mutableVertexData()), vertexView.data()); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[1], 1); + CORRADE_COMPARE(data.indices()[2], 0); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableIndices()[1], 1); + CORRADE_COMPARE(data.mutableIndices()[2], 0); + } + + CORRADE_COMPARE(data.vertexCount(), 2); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(data.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.attribute(0)[1], (Vector2{0.4f, 0.5f})); + CORRADE_COMPARE(data.mutableAttribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector2{0.4f, 0.5f})); +} + +void MeshDataTest::constructVerticesNotOwned() { + auto&& instanceData = SingleNotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + Containers::Array indexData{3*sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 0; + Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + int importerState; + MeshIndexData indices{indexView}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + MeshData data{MeshPrimitive::Triangles, std::move(indexData), indices, instanceData.dataFlags, Containers::arrayView(vertexData), {positions}, &importerState}; + + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_COMPARE(static_cast(data.indexData()), indexView.data()); + CORRADE_COMPARE(static_cast(data.vertexData()), +vertexData); + CORRADE_COMPARE(static_cast(data.mutableIndexData()), indexView.data()); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableVertexData()), +vertexData); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[1], 1); + CORRADE_COMPARE(data.indices()[2], 0); + CORRADE_COMPARE(data.mutableIndices()[1], 1); + CORRADE_COMPARE(data.mutableIndices()[2], 0); + + CORRADE_COMPARE(data.vertexCount(), 2); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeStride(0), sizeof(Vector2)); + CORRADE_COMPARE(data.attribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.attribute(0)[1], (Vector2{0.4f, 0.5f})); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableAttribute(0)[0], (Vector2{0.1f, 0.2f})); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector2{0.4f, 0.5f})); + } +} + +void MeshDataTest::constructIndexlessNotOwned() { + auto&& instanceData = SingleNotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + int importerState; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + MeshData data{MeshPrimitive::LineLoop, instanceData.dataFlags, vertexData, {positions}, &importerState}; + + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::LineLoop); + CORRADE_COMPARE(data.indexData(), nullptr); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(data.mutableIndexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(data.vertexCount(), 2); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position), VertexFormat::Vector2); + CORRADE_COMPARE(data.attribute(MeshAttribute::Position)[1], (Vector2{0.4f, 0.5f})); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position)[1], (Vector2{0.4f, 0.5f})); +} + +void MeshDataTest::constructAttributelessNotOwned() { + auto&& instanceData = SingleNotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + UnsignedShort indexData[]{0, 1, 0}; + + int importerState; + MeshIndexData indices{indexData}; + MeshData data{MeshPrimitive::TriangleStrip, instanceData.dataFlags, indexData, indices, &importerState}; + CORRADE_COMPARE(data.indexDataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_COMPARE(data.vertexData(), nullptr); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(data.mutableVertexData(), nullptr); + CORRADE_COMPARE(data.importerState(), &importerState); + + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indices()[0], 0); + CORRADE_COMPARE(data.indices()[1], 1); + CORRADE_COMPARE(data.indices()[2], 0); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(data.mutableIndices()[0], 0); + CORRADE_COMPARE(data.mutableIndices()[1], 1); + CORRADE_COMPARE(data.mutableIndices()[2], 0); + } + + CORRADE_COMPARE(data.vertexCount(), 0); /** @todo what to return here? */ + CORRADE_COMPARE(data.attributeCount(), 0); +} + void MeshDataTest::constructIndexlessAttributeless() { int importerState; MeshData data{MeshPrimitive::TriangleStrip, 37, &importerState}; + /* These are both empty so it doesn't matter, but this is a nice + non-restrictive default */ + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); CORRADE_COMPARE(data.indexData(), nullptr); CORRADE_COMPARE(data.vertexData(), nullptr); @@ -606,6 +891,91 @@ void MeshDataTest::constructInconsitentVertexCount() { "Trade::MeshData: attribute 1 has 2 vertices but 3 expected\n"); } +void MeshDataTest::constructNotOwnedIndexFlagOwned() { + const UnsignedShort indexData[]{0, 1, 0}; + const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, indexData, indices, {}, vertexData, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned index data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructNotOwnedVertexFlagOwned() { + const UnsignedShort indexData[]{0, 1, 0}; + const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, {}, indexData, indices, DataFlag::Owned, vertexData, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned vertex data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructIndicesNotOwnedFlagOwned() { + UnsignedShort indexData[]{0, 1, 0}; + Containers::Array vertexData{2*sizeof(Vector2)}; + auto vertexView = Containers::arrayCast(vertexData); + vertexView[0] = {0.1f, 0.2f}; + vertexView[1] = {0.4f, 0.5f}; + + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, indexData, indices, std::move(vertexData), {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned index data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructVerticesNotOwnedFlagOwned() { + Containers::Array indexData{3*sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData); + indexView[0] = 0; + indexView[1] = 1; + indexView[2] = 0; + Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + MeshIndexData indices{indexView}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, std::move(indexData), indices, DataFlag::Owned, vertexData, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned vertex data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructIndexlessNotOwnedFlagOwned() { + const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, vertexData, {positions}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned vertex data but Trade::DataFlag::Owned\n"); +} + +void MeshDataTest::constructAttributelessNotOwnedFlagOwned() { + const UnsignedShort indexData[]{0, 1, 0}; + MeshIndexData indices{indexData}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, indexData, indices}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: can't construct with non-owned index data but Trade::DataFlag::Owned\n"); +} + void MeshDataTest::constructCopy() { CORRADE_VERIFY(!(std::is_constructible{})); CORRADE_VERIFY(!(std::is_assignable{})); @@ -630,6 +1000,8 @@ void MeshDataTest::constructMove() { MeshData b{std::move(a)}; + CORRADE_COMPARE(b.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(b.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(b.primitive(), MeshPrimitive::Triangles); CORRADE_COMPARE(static_cast(b.indexData()), indexView.data()); CORRADE_COMPARE(static_cast(b.vertexData()), vertexView.data()); @@ -653,6 +1025,8 @@ void MeshDataTest::constructMove() { MeshData c{MeshPrimitive::LineLoop, 37}; c = std::move(b); + CORRADE_COMPARE(c.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(c.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(c.primitive(), MeshPrimitive::Triangles); CORRADE_COMPARE(static_cast(c.indexData()), indexView.data()); CORRADE_COMPARE(static_cast(c.vertexData()), vertexView.data()); @@ -851,6 +1225,31 @@ void MeshDataTest::colorsIntoArrayInvalidSize() { "Trade::MeshData::colorsInto(): expected a view with 3 elements but got 2\n"); } +void MeshDataTest::mutableAccessNotAllowed() { + const UnsignedShort indexData[]{0, 1, 0}; + const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; + + MeshIndexData indices{indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + MeshData data{MeshPrimitive::Triangles, {}, indexData, indices, {}, vertexData, {positions}}; + CORRADE_COMPARE(data.indexDataFlags(), DataFlags{}); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlags{}); + + std::ostringstream out; + Error redirectError{&out}; + data.mutableIndexData(); + data.mutableVertexData(); + data.mutableIndices(); + data.mutableAttribute(0); + data.mutableAttribute(MeshAttribute::Position); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::mutableIndexData(): index data not mutable\n" + "Trade::MeshData::mutableVertexData(): vertex data not mutable\n" + "Trade::MeshData::mutableIndices(): index data not mutable\n" + "Trade::MeshData::mutableAttribute(): vertex data not mutable\n" + "Trade::MeshData::mutableAttribute(): vertex data not mutable\n"); +} + void MeshDataTest::indicesNotIndexed() { MeshData data{MeshPrimitive::Triangles, 37}; From a3ab27f7b9039ade9032b4355238e40a6be7e510 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 11 Nov 2019 16:54:30 +0100 Subject: [PATCH 029/107] Trade: return TrackView with const types from AnimationData. Follows the change done in 954798a9ba32ae1110b50835a219c5887d6fd897. --- doc/changelog.dox | 2 ++ src/Magnum/Trade/AnimationData.cpp | 2 +- src/Magnum/Trade/AnimationData.h | 20 +++++++------- src/Magnum/Trade/Test/AnimationDataTest.cpp | 30 ++++++++++----------- 4 files changed, 28 insertions(+), 26 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index fd39bece2f..bcc9c3d087 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -390,6 +390,8 @@ See also: @ref Animation library was changed to allow mutable access to the keys & values it references. Existing code needs to be changed to say @cpp TrackView @ce instead of @cpp TrackView @ce. + Following this change, @ref Trade::AnimationData now also return instances + with @cpp const @ce types. - The 4-argument @ref GL::DynamicAttribute constructor was not marked as @cpp explicit @ce by mistake, it's done now to enforce readability in long expressions. diff --git a/src/Magnum/Trade/AnimationData.cpp b/src/Magnum/Trade/AnimationData.cpp index e63a1be592..612a3005fc 100644 --- a/src/Magnum/Trade/AnimationData.cpp +++ b/src/Magnum/Trade/AnimationData.cpp @@ -70,7 +70,7 @@ UnsignedInt AnimationData::trackTarget(UnsignedInt id) const { return _tracks[id]._target; } -const Animation::TrackViewStorage& AnimationData::track(UnsignedInt id) const { +const Animation::TrackViewStorage& AnimationData::track(UnsignedInt id) const { CORRADE_ASSERT(id < _tracks.size(), "Trade::AnimationData::track(): index out of range", _tracks[id]._view); return _tracks[id]._view; } diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index 698c3439b5..74ad5db9c0 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -237,14 +237,14 @@ class AnimationTrackData { * @param target Track target * @param view Type-erased @ref Animation::TrackView instance */ - /*implicit*/ AnimationTrackData(AnimationTrackType type, AnimationTrackType resultType, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{resultType}, _targetType{targetType}, _target{target}, _view{view} {} + /*implicit*/ AnimationTrackData(AnimationTrackType type, AnimationTrackType resultType, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{resultType}, _targetType{targetType}, _target{target}, _view{view} {} /** @overload * * Equivalent to the above with @p type used as both value type and * result type. */ - /*implicit*/ AnimationTrackData(AnimationTrackType type, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{type}, _targetType{targetType}, _target{target}, _view{view} {} + /*implicit*/ AnimationTrackData(AnimationTrackType type, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{type}, _targetType{targetType}, _target{target}, _view{view} {} private: friend AnimationData; @@ -252,7 +252,7 @@ class AnimationTrackData { AnimationTrackType _type, _resultType; AnimationTrackTargetType _targetType; UnsignedInt _target; - Animation::TrackViewStorage _view; + Animation::TrackViewStorage _view; }; /** @@ -400,7 +400,7 @@ class MAGNUM_TRADE_EXPORT AnimationData { * checked version below to access a concrete @ref Animation::TrackView * type. */ - const Animation::TrackViewStorage& track(UnsignedInt id) const; + const Animation::TrackViewStorage& track(UnsignedInt id) const; /** * @brief Track data @@ -414,7 +414,7 @@ class MAGNUM_TRADE_EXPORT AnimationData { * use the view or you need to release the data array using * @ref release() and manage its lifetime yourself. */ - template> const Animation::TrackView& track(UnsignedInt id) const; + template> const Animation::TrackView& track(UnsignedInt id) const; /** * @brief Release data storage @@ -505,11 +505,11 @@ namespace Implementation { } #endif -template const Animation::TrackView& AnimationData::track(UnsignedInt id) const { - const Animation::TrackViewStorage& storage = track(id); - CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._type, "Trade::AnimationData::track(): improper type requested for" << _tracks[id]._type, (static_cast&>(storage))); - CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._resultType, "Trade::AnimationData::track(): improper result type requested for" << _tracks[id]._resultType, (static_cast&>(storage))); - return static_cast&>(storage); +template const Animation::TrackView& AnimationData::track(UnsignedInt id) const { + const Animation::TrackViewStorage& storage = track(id); + CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._type, "Trade::AnimationData::track(): improper type requested for" << _tracks[id]._type, (static_cast&>(storage))); + CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._resultType, "Trade::AnimationData::track(): improper result type requested for" << _tracks[id]._resultType, (static_cast&>(storage))); + return static_cast&>(storage); } }} diff --git a/src/Magnum/Trade/Test/AnimationDataTest.cpp b/src/Magnum/Trade/Test/AnimationDataTest.cpp index 11258c7fb4..3215a0d17a 100644 --- a/src/Magnum/Trade/Test/AnimationDataTest.cpp +++ b/src/Magnum/Trade/Test/AnimationDataTest.cpp @@ -92,14 +92,14 @@ void AnimationDataTest::construct() { AnimationData data{std::move(buffer), Containers::Array{Containers::InPlaceInit, { {AnimationTrackType::Vector3, AnimationTrackTargetType::Translation3D, 42, - Animation::TrackView{ + Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, {view, &view[0].position, view.size(), sizeof(Data)}, Animation::Interpolation::Constant, animationInterpolatorFor(Animation::Interpolation::Constant)}}, {AnimationTrackType::Quaternion, AnimationTrackTargetType::Rotation3D, 1337, - Animation::TrackView{ + Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, {view, &view[0].rotation, view.size(), sizeof(Data)}, Animation::Interpolation::Linear, @@ -117,7 +117,7 @@ void AnimationDataTest::construct() { CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType::Translation3D); CORRADE_COMPARE(data.trackTarget(0), 42); - Animation::TrackView track = data.track(0); + Animation::TrackView track = data.track(0); CORRADE_COMPARE(track.keys().size(), 3); CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); @@ -128,7 +128,7 @@ void AnimationDataTest::construct() { CORRADE_COMPARE(data.trackTargetType(1), AnimationTrackTargetType::Rotation3D); CORRADE_COMPARE(data.trackTarget(1), 1337); - Animation::TrackView track = data.track(1); + Animation::TrackView track = data.track(1); CORRADE_COMPARE(track.keys().size(), 3); CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Linear); @@ -154,13 +154,13 @@ void AnimationDataTest::constructImplicitDuration() { AnimationData data{std::move(buffer), Containers::Array{Containers::InPlaceInit, { {AnimationTrackType::Bool, AnimationTrackTargetType(129), 0, - Animation::TrackView{ + Animation::TrackView{ {view, &view[0].time, 2, sizeof(Data)}, {view, &view[0].value, 2, sizeof(Data)}, Animation::Interpolation::Constant}}, {AnimationTrackType::Bool, AnimationTrackTargetType(130), 1, - Animation::TrackView{ + Animation::TrackView{ {view, &view[2].time, 2, sizeof(Data)}, {view, &view[2].value, 2, sizeof(Data)}, Animation::Interpolation::Linear}} @@ -175,7 +175,7 @@ void AnimationDataTest::constructImplicitDuration() { CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType(129)); CORRADE_COMPARE(data.trackTarget(0), 0); - Animation::TrackView track = data.track(0); + Animation::TrackView track = data.track(0); CORRADE_COMPARE(track.duration(), (Range1D{1.0f, 5.0f})); CORRADE_COMPARE(track.keys().size(), 2); CORRADE_COMPARE(track.values().size(), 2); @@ -187,7 +187,7 @@ void AnimationDataTest::constructImplicitDuration() { CORRADE_COMPARE(data.trackTargetType(1), AnimationTrackTargetType(130)); CORRADE_COMPARE(data.trackTarget(1), 1); - Animation::TrackView track = data.track(1); + Animation::TrackView track = data.track(1); CORRADE_COMPARE(track.duration(), (Range1D{3.0f, 7.0f})); CORRADE_COMPARE(track.keys().size(), 2); CORRADE_COMPARE(track.values().size(), 2); @@ -223,14 +223,14 @@ void AnimationDataTest::constructMove() { AnimationData a{std::move(buffer), Containers::Array{Containers::InPlaceInit, { {AnimationTrackType::Vector3, AnimationTrackTargetType::Translation3D, 42, - Animation::TrackView{ + Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, {view, &view[0].position, view.size(), sizeof(Data)}, Animation::Interpolation::Constant, animationInterpolatorFor(Animation::Interpolation::Constant)}}, {AnimationTrackType::Quaternion, AnimationTrackTargetType::Rotation3D, 1337, - Animation::TrackView{ + Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, {view, &view[0].rotation, view.size(), sizeof(Data)}, Animation::Interpolation::Linear, @@ -250,7 +250,7 @@ void AnimationDataTest::constructMove() { CORRADE_COMPARE(b.trackTargetType(0), AnimationTrackTargetType::Translation3D); CORRADE_COMPARE(b.trackTarget(0), 42); - Animation::TrackView track = b.track(0); + Animation::TrackView track = b.track(0); CORRADE_COMPARE(track.keys().size(), 3); CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); @@ -261,7 +261,7 @@ void AnimationDataTest::constructMove() { CORRADE_COMPARE(b.trackTargetType(1), AnimationTrackTargetType::Rotation3D); CORRADE_COMPARE(b.trackTarget(1), 1337); - Animation::TrackView track = b.track(1); + Animation::TrackView track = b.track(1); CORRADE_COMPARE(track.keys().size(), 3); CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Linear); @@ -283,7 +283,7 @@ void AnimationDataTest::constructMove() { CORRADE_COMPARE(c.trackTargetType(0), AnimationTrackTargetType::Translation3D); CORRADE_COMPARE(c.trackTarget(0), 42); - Animation::TrackView track = c.track(0); + Animation::TrackView track = c.track(0); CORRADE_COMPARE(track.keys().size(), 3); CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); @@ -294,7 +294,7 @@ void AnimationDataTest::constructMove() { CORRADE_COMPARE(c.trackTargetType(1), AnimationTrackTargetType::Rotation3D); CORRADE_COMPARE(c.trackTarget(1), 1337); - Animation::TrackView track = c.track(1); + Animation::TrackView track = c.track(1); CORRADE_COMPARE(track.keys().size(), 3); CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Linear); @@ -326,7 +326,7 @@ void AnimationDataTest::trackCustomResultType() { {AnimationTrackType::Vector3i, AnimationTrackType::Vector3, AnimationTrackTargetType::Scaling3D, 0, - Animation::TrackView{ + Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, {view, &view[0].position, view.size(), sizeof(Data)}, [](const Vector3i& a, const Vector3i& b, Float t) -> Vector3 { From 0fd62194c8e18a7c77f3f1050996292aea35fb9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 11 Nov 2019 23:21:02 +0100 Subject: [PATCH 030/107] Trade: mutable access in AnimationData. Follows the change done in MeshData. --- doc/changelog.dox | 9 +- doc/snippets/MagnumTrade.cpp | 17 ++ src/Magnum/Trade/AnimationData.cpp | 29 ++- src/Magnum/Trade/AnimationData.h | 132 ++++++++++++-- src/Magnum/Trade/Data.h | 7 +- src/Magnum/Trade/Test/AnimationDataTest.cpp | 188 +++++++++++++++++++- 6 files changed, 362 insertions(+), 20 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index bcc9c3d087..e3e87a5595 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -168,6 +168,11 @@ See also: @ref Trade::AbstractImporter::image2D(), @ref Trade::AbstractImporter::image2DLevelCount() and similar APIs for 1D and 3D images +- The @ref Trade::AnimationData class received support for mutable data + access with new constructors and the + @ref Trade::AnimationData::mutableData() "mutableData()" and + @ref Trade::AnimationData::mutableTrack() "mutableTrack()" accessors. See + @ref Trade-AnimationData-usage-mutable for more information. @subsubsection changelog-latest-new-vk Vk library @@ -391,7 +396,9 @@ See also: values it references. Existing code needs to be changed to say @cpp TrackView @ce instead of @cpp TrackView @ce. Following this change, @ref Trade::AnimationData now also return instances - with @cpp const @ce types. + with @cpp const @ce types and the non-const + @ref Trade::AnimationData::data() was renamed to + @ref Trade::AnimationData::mutableData() "mutableData()". - The 4-argument @ref GL::DynamicAttribute constructor was not marked as @cpp explicit @ce by mistake, it's done now to enforce readability in long expressions. diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index 82296fae2e..922237381e 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -174,6 +174,23 @@ Containers::Array animationData = data->release(); /* Take ownership */ /* [AnimationData-usage] */ } +{ +Trade::AnimationData data{nullptr, {}}; +/* [AnimationData-usage-mutable] */ +for(UnsignedInt i = 0; i != data.trackCount(); ++i) { + if(data.trackTargetType(i) != Trade::AnimationTrackTargetType::Translation3D) + continue; + /* Check prerequisites */ + if(!(data.dataFlags() & Trade::DataFlag::Mutable) || + data.trackType(i) != Trade::AnimationTrackType::Vector2) + Fatal{} << "Oops"; + + MeshTools::transformVectorsInPlace(Matrix4::scaling(Vector3::yScale(-1.0f)), + data.mutableTrack(i).values()); +} +/* [AnimationData-usage-mutable] */ +} + { /* [ImageData-construction] */ Containers::Array data; diff --git a/src/Magnum/Trade/AnimationData.cpp b/src/Magnum/Trade/AnimationData.cpp index 612a3005fc..5bfbfba504 100644 --- a/src/Magnum/Trade/AnimationData.cpp +++ b/src/Magnum/Trade/AnimationData.cpp @@ -32,9 +32,15 @@ namespace Magnum { namespace Trade { -AnimationData::AnimationData(Containers::Array&& data, Containers::Array&& tracks, const Range1D& duration, const void* importerState) noexcept: _duration{duration}, _data{std::move(data)}, _tracks{std::move(tracks)}, _importerState{importerState} {} +AnimationData::AnimationData(Containers::Array&& data, Containers::Array&& tracks, const Range1D& duration, const void* importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _duration{duration}, _data{std::move(data)}, _tracks{std::move(tracks)}, _importerState{importerState} {} -AnimationData::AnimationData(Containers::Array&& data, Containers::Array&& tracks, const void* importerState) noexcept: _data{std::move(data)}, _tracks{std::move(tracks)}, _importerState{importerState} { +AnimationData::AnimationData(const DataFlags dataFlags, const Containers::ArrayView data, Containers::Array&& tracks, const Range1D& duration, const void* importerState) noexcept: AnimationData{Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, std::move(tracks), duration, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::AnimationData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +AnimationData::AnimationData(Containers::Array&& data, Containers::Array&& tracks, const void* importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _data{std::move(data)}, _tracks{std::move(tracks)}, _importerState{importerState} { if(!_tracks.empty()) { /* Reset duration to duration of the first track so it properly support cases where tracks don't start at 0 */ @@ -44,12 +50,24 @@ AnimationData::AnimationData(Containers::Array&& data, Containers::Array data, Containers::Array&& tracks, const void* importerState) noexcept: AnimationData{Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, std::move(tracks), importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::AnimationData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + AnimationData::~AnimationData() = default; AnimationData::AnimationData(AnimationData&&) noexcept = default; AnimationData& AnimationData::operator=(AnimationData&&) noexcept = default; +Containers::ArrayView AnimationData::mutableData() & { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::AnimationData::mutableData(): the animation is not mutable", {}); + return _data; +} + AnimationTrackType AnimationData::trackType(UnsignedInt id) const { CORRADE_ASSERT(id < _tracks.size(), "Trade::AnimationData::trackType(): index out of range", {}); return _tracks[id]._type; @@ -75,6 +93,13 @@ const Animation::TrackViewStorage& AnimationData::track(UnsignedInt return _tracks[id]._view; } +const Animation::TrackViewStorage& AnimationData::mutableTrack(UnsignedInt id) { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::AnimationData::mutableTrack(): the animation is not mutable", reinterpret_cast&>(_tracks[id]._view)); + CORRADE_ASSERT(id < _tracks.size(), "Trade::AnimationData::track(): index out of range", reinterpret_cast&>(_tracks[id]._view)); + return reinterpret_cast&>(_tracks[id]._view); +} + template auto animationInterpolatorFor(Animation::Interpolation interpolation) -> R(*)(const V&, const V&, Float) { return Animation::interpolatorFor(interpolation); } diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index 74ad5db9c0..4ca38958d3 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -32,6 +32,7 @@ #include "Magnum/Magnum.h" #include "Magnum/Math/Math.h" #include "Magnum/Animation/Track.h" +#include "Magnum/Trade/Data.h" #include "Magnum/Trade/Trade.h" #include "Magnum/Trade/visibility.h" @@ -278,12 +279,26 @@ array is then updated during calls to @ref Animation::Player::advance(). It's also possible to directly update object transformations using callbacks, among other things. See documentation of the @ref Animation::Player class for more information. + +@section Trade-AnimationData-usage-mutable Mutable data access + +The interfaces implicitly provide @cpp const @ce views on the contained +keyframe data through the @ref data() and @ref track() accessors. This is done +because in general case the data can also refer to a memory-mapped file or +constant memory. In cases when it's desirable to modify the data in-place, +there's the @ref mutableData() and @ref mutableTrack() set of functions. To use +these, you need to check that the data are mutable using @ref dataFlags() +first. The following snippet inverts the Y coordinate of a translation +animation: + +@snippet MagnumTrade.cpp AnimationData-usage-mutable + @experimental */ class MAGNUM_TRADE_EXPORT AnimationData { public: /** - * @brief Construct with implicit duration + * @brief Construct an animation data * @param data Buffer containing all keyframe data for this * animation clip * @param tracks Track data @@ -292,11 +307,33 @@ class MAGNUM_TRADE_EXPORT AnimationData { * Each item of @p track should have an @ref Animation::TrackView * instance pointing its key/value views to @p data. The @ref duration() * is automatically calculated from durations of all tracks. + * + * The @ref dataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For non-owned data + * use the @ref AnimationData(DataFlags, Containers::ArrayView, Containers::Array&&, const void*) + * constructor instead. */ explicit AnimationData(Containers::Array&& data, Containers::Array&& tracks, const void* importerState = nullptr) noexcept; /** - * @brief Construct with explicit duration + * @brief Construct a non-owned animation data + * @param dataFlags Data flags + * @param data View on a buffer containing all keyframe data + * for this animation clip + * @param tracks Track data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref AnimationData(Containers::Array&&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit AnimationData(DataFlags dataFlags, Containers::ArrayView data, Containers::Array&& tracks, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an animation data with explicit duration * @param data Buffer containing all keyframe data for this * animation clip * @param tracks Track data @@ -305,9 +342,32 @@ class MAGNUM_TRADE_EXPORT AnimationData { * * Each item of @p track should have an @ref Animation::TrackView * instance pointing its key/value views to @p data. + * + * The @ref dataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For non-owned data + * use the @ref AnimationData(DataFlags, Containers::ArrayView, Containers::Array&&, const Range1D&, const void*) + * constructor instead. */ explicit AnimationData(Containers::Array&& data, Containers::Array&& tracks, const Range1D& duration, const void* importerState = nullptr) noexcept; + /** + * @brief Construct a non-owned animation data with explicit duration + * @param dataFlags Data flags + * @param data View on a buffer containing all keyframe data + * for this animation clip + * @param tracks Track data + * @param duration Animation track duration + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref AnimationData(Containers::Array&&, Containers::Array&&, const Range1D&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit AnimationData(DataFlags dataFlags, Containers::ArrayView data, Containers::Array&& tracks, const Range1D& duration, const void* importerState = nullptr) noexcept; + ~AnimationData(); /** @brief Copying is not allowed */ @@ -322,18 +382,40 @@ class MAGNUM_TRADE_EXPORT AnimationData { /** @brief Move assignment */ AnimationData& operator=(AnimationData&&) noexcept; + /** + * @brief Data flags + * @m_since_latest + * + * @see @ref release(), @ref mutableData(), @ref mutableTrack() + */ + DataFlags dataFlags() const { return _dataFlags; } + /** * @brief Raw data * * Contains data for all tracks contained in this clip. - * @see @ref release() + * @see @ref release(), @ref mutableData() */ - Containers::ArrayView data() & { return _data; } - Containers::ArrayView data() && = delete; /**< @overload */ - - /** @overload */ Containers::ArrayView data() const & { return _data; } - Containers::ArrayView data() const && = delete; /**< @overload */ + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView data() const && = delete; + + /** + * @brief Mutable raw data + * @m_since_latest + * + * Like @ref data(), but returns a non-const view. Expects that the + * animation is mutable. + * @see @ref dataFlags() + */ + Containers::ArrayView mutableData() &; + + /** + * @brief Taking a view to a r-value instance is not allowed + * @m_since_latest + */ + Containers::ArrayView mutableData() && = delete; /** @brief Duration */ Range1D duration() const { return _duration; } @@ -402,6 +484,16 @@ class MAGNUM_TRADE_EXPORT AnimationData { */ const Animation::TrackViewStorage& track(UnsignedInt id) const; + /** + * @brief Mutable track data storage + * @m_since_latest + * + * Like @ref track(), but returns a mutable view. Expects that the + * animation is mutable. + * @see @ref dataFlags() + */ + const Animation::TrackViewStorage& mutableTrack(UnsignedInt id); + /** * @brief Track data * @tparam V Track value type @@ -416,12 +508,24 @@ class MAGNUM_TRADE_EXPORT AnimationData { */ template> const Animation::TrackView& track(UnsignedInt id) const; + /** + * @brief Mutable track data + * @m_since_latest + * + * Like @ref track(), but returns a mutable view. Expects that the + * animation is mutable. + * @see @ref dataFlags() + */ + template> const Animation::TrackView& mutableTrack(UnsignedInt id); + /** * @brief Release data storage * * Releases the ownership of the data array and resets internal state - * to default. - * @see @ref data() + * to default. Note that the returned array has a custom no-op deleter + * when the data are not owned by the animation, and while the returned + * array type is mutable, the actual memory might be not. + * @see @ref data(), @ref dataFlags() */ Containers::Array release() { return std::move(_data); } @@ -438,6 +542,7 @@ class MAGNUM_TRADE_EXPORT AnimationData { implementations. */ friend AbstractImporter; + DataFlags _dataFlags; Range1D _duration; Containers::Array _data; Containers::Array _tracks; @@ -512,6 +617,13 @@ template const Animation::TrackView& return static_cast&>(storage); } +template const Animation::TrackView& AnimationData::mutableTrack(UnsignedInt id) { + const Animation::TrackViewStorage& storage = mutableTrack(id); + CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._type, "Trade::AnimationData::mutableTrack(): improper type requested for" << _tracks[id]._type, (static_cast&>(storage))); + CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._resultType, "Trade::AnimationData::mutableTrack(): improper result type requested for" << _tracks[id]._resultType, (static_cast&>(storage))); + return static_cast&>(storage); +} + }} #endif diff --git a/src/Magnum/Trade/Data.h b/src/Magnum/Trade/Data.h index bc74cce2f3..b3b04dcfd5 100644 --- a/src/Magnum/Trade/Data.h +++ b/src/Magnum/Trade/Data.h @@ -41,8 +41,8 @@ namespace Magnum { namespace Trade { @brief Data flag @m_since_latest -@see @ref DataFlags, @ref MeshData::indexDataFlags(), - @ref MeshData::vertexDataFlags() +@see @ref DataFlags, @ref AnimationData::dataFlags(), + @ref MeshData::indexDataFlags(), @ref MeshData::vertexDataFlags() */ enum class DataFlag: UnsignedByte { /** @@ -71,7 +71,8 @@ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlag value); @brief Data flags @m_since_latest -@see @ref MeshData::indexDataFlags(), @ref MeshData::vertexDataFlags() +@see @ref AnimationData::dataFlags(), @ref MeshData::indexDataFlags(), + @ref MeshData::vertexDataFlags() */ typedef Containers::EnumSet DataFlags; diff --git a/src/Magnum/Trade/Test/AnimationDataTest.cpp b/src/Magnum/Trade/Test/AnimationDataTest.cpp index 3215a0d17a..5830e926db 100644 --- a/src/Magnum/Trade/Test/AnimationDataTest.cpp +++ b/src/Magnum/Trade/Test/AnimationDataTest.cpp @@ -36,15 +36,21 @@ struct AnimationDataTest: TestSuite::Tester { explicit AnimationDataTest(); void construct(); + void constructNotOwned(); void constructImplicitDuration(); void constructImplicitDurationEmpty(); + void constructImplicitDurationNotOwned(); + void constructNotOwnedFlagOwned(); + void constructImplicitDurationNotOwnedFlagOwned(); + void constructCopy(); void constructMove(); void constructTrackDataDefault(); - void trackCustomResultType(); + void mutableAccessNotAllowed(); + void trackCustomResultType(); void trackWrongIndex(); void trackWrongType(); void trackWrongResultType(); @@ -53,17 +59,34 @@ struct AnimationDataTest: TestSuite::Tester { void debugAnimationTrackTargetType(); }; +struct { + const char* name; + DataFlags dataFlags; +} NotOwnedData[] { + {"", {}}, + {"mutable", DataFlag::Mutable}, +}; + AnimationDataTest::AnimationDataTest() { addTests({&AnimationDataTest::construct, &AnimationDataTest::constructImplicitDuration, - &AnimationDataTest::constructImplicitDurationEmpty, + &AnimationDataTest::constructImplicitDurationEmpty}); + + addInstancedTests({&AnimationDataTest::constructNotOwned, + &AnimationDataTest::constructImplicitDurationNotOwned}, + Containers::arraySize(NotOwnedData)); + + addTests({&AnimationDataTest::constructNotOwnedFlagOwned, + &AnimationDataTest::constructImplicitDurationNotOwnedFlagOwned, + &AnimationDataTest::constructCopy, &AnimationDataTest::constructMove, &AnimationDataTest::constructTrackDataDefault, - &AnimationDataTest::trackCustomResultType, + &AnimationDataTest::mutableAccessNotAllowed, + &AnimationDataTest::trackCustomResultType, &AnimationDataTest::trackWrongIndex, &AnimationDataTest::trackWrongType, &AnimationDataTest::trackWrongResultType, @@ -106,8 +129,10 @@ void AnimationDataTest::construct() { animationInterpolatorFor(Animation::Interpolation::Linear)}} }}, {-1.0f, 7.0f}, &state}; + CORRADE_COMPARE(data.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.duration(), (Range1D{-1.0f, 7.0f})); - CORRADE_COMPARE(data.data().size(), sizeof(Data)*3); + CORRADE_COMPARE(static_cast(data.data().data()), view.data()); + CORRADE_COMPARE(static_cast(data.mutableData().data()), view.data()); CORRADE_COMPARE(data.trackCount(), 2); CORRADE_COMPARE(data.importerState(), &state); @@ -122,6 +147,12 @@ void AnimationDataTest::construct() { CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); CORRADE_COMPARE(track.at(2.5f), (Vector3{3.0f, 1.0f, 0.1f})); + + Animation::TrackView mutableTrack = data.mutableTrack(0); + CORRADE_COMPARE(mutableTrack.keys().size(), 3); + CORRADE_COMPARE(mutableTrack.values().size(), 3); + CORRADE_COMPARE(mutableTrack.interpolation(), Animation::Interpolation::Constant); + CORRADE_COMPARE(mutableTrack.at(2.5f), (Vector3{3.0f, 1.0f, 0.1f})); } { CORRADE_COMPARE(data.trackType(1), AnimationTrackType::Quaternion); CORRADE_COMPARE(data.trackResultType(1), AnimationTrackType::Quaternion); @@ -133,6 +164,8 @@ void AnimationDataTest::construct() { CORRADE_COMPARE(track.values().size(), 3); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Linear); CORRADE_COMPARE(track.at(2.5f), Quaternion::rotation(32.5_degf, Vector3::yAxis())); + + /* Testing the mutable track just once is enough */ } } @@ -166,6 +199,7 @@ void AnimationDataTest::constructImplicitDuration() { Animation::Interpolation::Linear}} }}, &state}; + CORRADE_COMPARE(data.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.duration(), (Range1D{1.0f, 7.0f})); CORRADE_COMPARE(data.trackCount(), 2); CORRADE_COMPARE(data.importerState(), &state); @@ -181,6 +215,13 @@ void AnimationDataTest::constructImplicitDuration() { CORRADE_COMPARE(track.values().size(), 2); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); CORRADE_COMPARE(track.at(6.0f), false); + + Animation::TrackView mutableTrack = data.mutableTrack(0); + CORRADE_COMPARE(mutableTrack.duration(), (Range1D{1.0f, 5.0f})); + CORRADE_COMPARE(mutableTrack.keys().size(), 2); + CORRADE_COMPARE(mutableTrack.values().size(), 2); + CORRADE_COMPARE(mutableTrack.interpolation(), Animation::Interpolation::Constant); + CORRADE_COMPARE(mutableTrack.at(6.0f), false); } { CORRADE_COMPARE(data.trackType(1), AnimationTrackType::Bool); CORRADE_COMPARE(data.trackResultType(1), AnimationTrackType::Bool); @@ -193,6 +234,8 @@ void AnimationDataTest::constructImplicitDuration() { CORRADE_COMPARE(track.values().size(), 2); CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Linear); CORRADE_COMPARE(track.at(4.5f), true); + + /* Testing the mutable track just once is enough */ } } @@ -201,6 +244,119 @@ void AnimationDataTest::constructImplicitDurationEmpty() { CORRADE_COMPARE(data.duration(), Range1D{}); } +void AnimationDataTest::constructNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + std::pair keyframes[] { + {0.0f, {3.0f, 1.0f, 0.1f}}, + {5.0f, {0.3f, 0.6f, 1.0f}} + }; + + const int state = 5; + AnimationData data{instanceData.dataFlags, keyframes, Containers::Array{Containers::InPlaceInit, { + {AnimationTrackType::Vector3, + AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + keyframes, + Animation::Interpolation::Constant, + animationInterpolatorFor(Animation::Interpolation::Constant)}} + }}, {-1.0f, 7.0f}, &state}; + + CORRADE_COMPARE(data.dataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.duration(), (Range1D{-1.0f, 7.0f})); + CORRADE_COMPARE(static_cast(data.data().data()), keyframes); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableData().data()), keyframes); + CORRADE_COMPARE(data.trackCount(), 1); + CORRADE_COMPARE(data.importerState(), &state); + + { + CORRADE_COMPARE(data.trackType(0), AnimationTrackType::Vector3); + CORRADE_COMPARE(data.trackResultType(0), AnimationTrackType::Vector3); + CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType::Translation3D); + CORRADE_COMPARE(data.trackTarget(0), 42); + + Animation::TrackView track = data.track(0); + CORRADE_COMPARE(track.keys().size(), 2); + CORRADE_COMPARE(track.values().size(), 2); + CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); + CORRADE_COMPARE(track.at(2.5f), (Vector3{3.0f, 1.0f, 0.1f})); + + if(instanceData.dataFlags & DataFlag::Mutable) { + Animation::TrackView mutableTrack = data.mutableTrack(0); + CORRADE_COMPARE(mutableTrack.keys().size(), 2); + CORRADE_COMPARE(mutableTrack.values().size(), 2); + CORRADE_COMPARE(mutableTrack.interpolation(), Animation::Interpolation::Constant); + CORRADE_COMPARE(mutableTrack.at(2.5f), (Vector3{3.0f, 1.0f, 0.1f})); + } + } +} + +void AnimationDataTest::constructImplicitDurationNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + std::pair keyframes[] { + {1.0f, true}, + {5.0f, false} + }; + + const int state = 5; + AnimationData data{instanceData.dataFlags, keyframes, Containers::Array{Containers::InPlaceInit, { + {AnimationTrackType::Bool, + AnimationTrackTargetType(129), 0, + Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, + }}, &state}; + + CORRADE_COMPARE(data.dataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(data.duration(), (Range1D{1.0f, 5.0f})); + CORRADE_COMPARE(static_cast(data.data().data()), keyframes); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(data.mutableData().data()), keyframes); + CORRADE_COMPARE(data.trackCount(), 1); + CORRADE_COMPARE(data.importerState(), &state); + + { + CORRADE_COMPARE(data.trackType(0), AnimationTrackType::Bool); + CORRADE_COMPARE(data.trackResultType(0), AnimationTrackType::Bool); + CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType(129)); + CORRADE_COMPARE(data.trackTarget(0), 0); + + Animation::TrackView track = data.track(0); + CORRADE_COMPARE(track.duration(), (Range1D{1.0f, 5.0f})); + CORRADE_COMPARE(track.keys().size(), 2); + CORRADE_COMPARE(track.values().size(), 2); + CORRADE_COMPARE(track.interpolation(), Animation::Interpolation::Constant); + CORRADE_COMPARE(track.at(3.0f), true); + + if(instanceData.dataFlags & DataFlag::Mutable) { + Animation::TrackView mutableTrack = data.mutableTrack(0); + CORRADE_COMPARE(mutableTrack.duration(), (Range1D{1.0f, 5.0f})); + CORRADE_COMPARE(mutableTrack.keys().size(), 2); + CORRADE_COMPARE(mutableTrack.values().size(), 2); + CORRADE_COMPARE(mutableTrack.interpolation(), Animation::Interpolation::Constant); + CORRADE_COMPARE(mutableTrack.at(3.0f), true); + } + } +} + +void AnimationDataTest::constructNotOwnedFlagOwned() { + std::ostringstream out; + Error redirectError{&out}; + AnimationData data{DataFlag::Owned, nullptr, {}, {-1.0f, 7.0f}}; + CORRADE_COMPARE(out.str(), + "Trade::AnimationData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + +void AnimationDataTest::constructImplicitDurationNotOwnedFlagOwned() { + std::ostringstream out; + Error redirectError{&out}; + AnimationData data{DataFlag::Owned, nullptr, {}}; + CORRADE_COMPARE(out.str(), + "Trade::AnimationData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + void AnimationDataTest::constructCopy() { CORRADE_VERIFY(!(std::is_constructible{})); CORRADE_VERIFY(!(std::is_assignable{})); @@ -310,6 +466,30 @@ void AnimationDataTest::constructTrackDataDefault() { CORRADE_VERIFY(true); /* no public accessors here, so nothing to check */ } +void AnimationDataTest::mutableAccessNotAllowed() { + const std::pair keyframes[] { + {1.0f, true}, + {5.0f, false} + }; + + AnimationData data{{}, keyframes, Containers::Array{Containers::InPlaceInit, { + {AnimationTrackType::Bool, + AnimationTrackTargetType(129), 0, + Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, + }}}; + CORRADE_COMPARE(data.dataFlags(), DataFlags{}); + + std::ostringstream out; + Error redirectError{&out}; + data.mutableData(); + data.mutableTrack(0); + data.mutableTrack(0); + CORRADE_COMPARE(out.str(), + "Trade::AnimationData::mutableData(): the animation is not mutable\n" + "Trade::AnimationData::mutableTrack(): the animation is not mutable\n" + "Trade::AnimationData::mutableTrack(): the animation is not mutable\n"); +} + void AnimationDataTest::trackCustomResultType() { using namespace Math::Literals; From b71e50b023d131d1a0843937c3d49847cd7d5ef2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 12 Nov 2019 13:10:04 +0100 Subject: [PATCH 031/107] Trade: make AnimationData::release() consistent with MeshData. --- src/Magnum/Trade/AnimationData.cpp | 5 +++++ src/Magnum/Trade/AnimationData.h | 9 ++++---- src/Magnum/Trade/Test/AnimationDataTest.cpp | 23 +++++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/Magnum/Trade/AnimationData.cpp b/src/Magnum/Trade/AnimationData.cpp index 5bfbfba504..dca88ba8d6 100644 --- a/src/Magnum/Trade/AnimationData.cpp +++ b/src/Magnum/Trade/AnimationData.cpp @@ -100,6 +100,11 @@ const Animation::TrackViewStorage& AnimationData::mutableTrack(UnsignedIn return reinterpret_cast&>(_tracks[id]._view); } +Containers::Array AnimationData::release() { + _tracks = nullptr; + return std::move(_data); +} + template auto animationInterpolatorFor(Animation::Interpolation interpolation) -> R(*)(const V&, const V&, Float) { return Animation::interpolatorFor(interpolation); } diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index 4ca38958d3..0935c4813f 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -522,12 +522,13 @@ class MAGNUM_TRADE_EXPORT AnimationData { * @brief Release data storage * * Releases the ownership of the data array and resets internal state - * to default. Note that the returned array has a custom no-op deleter - * when the data are not owned by the animation, and while the returned - * array type is mutable, the actual memory might be not. + * to default. The animation then behaves like it's empty. Note that + * the returned array has a custom no-op deleter when the data are not + * owned by the animation, and while the returned array type is + * mutable, the actual memory might be not. * @see @ref data(), @ref dataFlags() */ - Containers::Array release() { return std::move(_data); } + Containers::Array release(); /** * @brief Importer-specific state diff --git a/src/Magnum/Trade/Test/AnimationDataTest.cpp b/src/Magnum/Trade/Test/AnimationDataTest.cpp index 5830e926db..c61228eef4 100644 --- a/src/Magnum/Trade/Test/AnimationDataTest.cpp +++ b/src/Magnum/Trade/Test/AnimationDataTest.cpp @@ -55,6 +55,8 @@ struct AnimationDataTest: TestSuite::Tester { void trackWrongType(); void trackWrongResultType(); + void release(); + void debugAnimationTrackType(); void debugAnimationTrackTargetType(); }; @@ -91,6 +93,8 @@ AnimationDataTest::AnimationDataTest() { &AnimationDataTest::trackWrongType, &AnimationDataTest::trackWrongResultType, + &AnimationDataTest::release, + &AnimationDataTest::debugAnimationTrackType, &AnimationDataTest::debugAnimationTrackTargetType}); } @@ -566,6 +570,25 @@ void AnimationDataTest::trackWrongResultType() { CORRADE_COMPARE(out.str(), "Trade::AnimationData::track(): improper result type requested for Trade::AnimationTrackType::Vector3\n"); } +void AnimationDataTest::release() { + const std::pair keyframes[] { + {1.0f, true}, + {5.0f, false} + }; + + AnimationData data{{}, keyframes, Containers::Array{Containers::InPlaceInit, { + {AnimationTrackType::Bool, + AnimationTrackTargetType(129), 0, + Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, + }}}; + CORRADE_COMPARE(data.trackCount(), 1); + + Containers::Array released = data.release(); + CORRADE_COMPARE(data.data(), nullptr); + CORRADE_COMPARE(data.trackCount(), 0); + CORRADE_COMPARE(static_cast(released.data()), keyframes); +} + void AnimationDataTest::debugAnimationTrackType() { std::ostringstream out; From 6ed0df26c6ee1ddd2b7cbe4dda36df5b73a7d446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 13 Nov 2019 20:45:51 +0100 Subject: [PATCH 032/107] Trade: mutable access in ImageData consistent with Animation/MeshData. --- doc/changelog.dox | 10 +- doc/snippets/MagnumTrade.cpp | 14 + src/Magnum/Image.cpp | 4 +- src/Magnum/ImageView.cpp | 2 +- src/Magnum/Implementation/ImageProperties.h | 4 +- src/Magnum/Trade/Data.h | 7 +- src/Magnum/Trade/ImageData.cpp | 88 ++++- src/Magnum/Trade/ImageData.h | 277 ++++++++++++-- src/Magnum/Trade/Test/ImageDataTest.cpp | 376 +++++++++++++++++++- 9 files changed, 719 insertions(+), 63 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index e3e87a5595..dedff6a26a 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -171,8 +171,10 @@ See also: - The @ref Trade::AnimationData class received support for mutable data access with new constructors and the @ref Trade::AnimationData::mutableData() "mutableData()" and - @ref Trade::AnimationData::mutableTrack() "mutableTrack()" accessors. See - @ref Trade-AnimationData-usage-mutable for more information. + @ref Trade::AnimationData::mutableTrack() "mutableTrack()" accessors. + Equivalent APIs are exposed in both @ref Trade::ImageData and + @ref Trade::MeshData as well. See @ref Trade-AnimationData-usage-mutable + for more information. @subsubsection changelog-latest-new-vk Vk library @@ -405,6 +407,10 @@ See also: - The @ref Magnum/Math/FunctionsBatch.h header is no longer included from @ref Magnum/Math/Functions.h for backwards compatibility in order to speed up compile times. +- Non-const @ref Trade::ImageData::data() and @ref Trade::ImageData::pixels() + were renamed to @ref Trade::ImageData::mutableData() and + @ref Trade::ImageData::mutablePixels() to follow the new + @ref Trade::MeshData API and similar changes in @ref Trade::AnimationData. - @ref Platform::GlfwApplication::setMinWindowSize() / @ref Platform::GlfwApplication::setMaxWindowSize() and equivalent APIs in @ref Platform::Sdl2Application now premultiply the value with diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index 922237381e..e5cbce5423 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -223,6 +223,20 @@ else } #endif +{ +Trade::ImageData2D data{PixelFormat::RGB8Unorm, {}, nullptr}; +/* [ImageData-usage-mutable] */ +if(data.isCompressed() || + data.format() != PixelFormat::RGB8Unorm || + !(data.dataFlags() & Trade::DataFlag::Mutable)) + Fatal{} << ":("; + +for(auto&& row: data.mutablePixels()) + for(Color3ub& pixel: row) + pixel = Math::gather<'b', 'g', 'r'>(pixel); +/* [ImageData-usage-mutable] */ +} + #ifdef MAGNUM_TARGET_GL { Trade::MeshData data{MeshPrimitive::Points, 0}; diff --git a/src/Magnum/Image.cpp b/src/Magnum/Image.cpp index 30be47f01d..fed0d70df4 100644 --- a/src/Magnum/Image.cpp +++ b/src/Magnum/Image.cpp @@ -73,11 +73,11 @@ template std::pair Containers::StridedArrayView Image::pixels() { - return Implementation::imagePixelView(*this); + return Implementation::imagePixelView(*this, data()); } template Containers::StridedArrayView Image::pixels() const { - return Implementation::imagePixelView(*this); + return Implementation::imagePixelView(*this, data()); } template Containers::Array Image::release() { diff --git a/src/Magnum/ImageView.cpp b/src/Magnum/ImageView.cpp index 805bc7b28e..98ba949936 100644 --- a/src/Magnum/ImageView.cpp +++ b/src/Magnum/ImageView.cpp @@ -63,7 +63,7 @@ template void ImageView::setData template auto ImageView::pixels() const -> Containers::StridedArrayView { if(!_data && !_data.size()) return {}; - return Implementation::imagePixelView(*this); + return Implementation::imagePixelView(*this, data()); } template CompressedImageView::CompressedImageView(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const Containers::ArrayView data) noexcept: _storage{storage}, _format{format}, _size{size}, _data{reinterpret_cast(data.data()), data.size()} {} diff --git a/src/Magnum/Implementation/ImageProperties.h b/src/Magnum/Implementation/ImageProperties.h index 0786d6f856..dd495254c7 100644 --- a/src/Magnum/Implementation/ImageProperties.h +++ b/src/Magnum/Implementation/ImageProperties.h @@ -45,7 +45,7 @@ template std::pair::pad(dataProperties.first), Math::Vector::pad(dataProperties.second)); } -template Containers::StridedArrayView imagePixelView(Image& image) { +template Containers::StridedArrayView imagePixelView(Image& image, const Data data) { const std::pair, VectorTypeFor> properties = image.dataProperties(); /* Size in the last dimension is byte size of the pixel, the remaining @@ -70,7 +70,7 @@ template Containers::StridedArrayV static_assert(sizeof(decltype(image.data().front())) == 1, "pointer arithmetic expects image data type to have 1 byte"); - return {image.data().suffix(properties.first[dimensions - 1]), image.data() + properties.first.sum(), size, stride}; + return {data.suffix(properties.first[dimensions - 1]), data + properties.first.sum(), size, stride}; } }} diff --git a/src/Magnum/Trade/Data.h b/src/Magnum/Trade/Data.h index b3b04dcfd5..59d98e922d 100644 --- a/src/Magnum/Trade/Data.h +++ b/src/Magnum/Trade/Data.h @@ -42,7 +42,8 @@ namespace Magnum { namespace Trade { @m_since_latest @see @ref DataFlags, @ref AnimationData::dataFlags(), - @ref MeshData::indexDataFlags(), @ref MeshData::vertexDataFlags() + @ref ImageData::dataFlags(), @ref MeshData::indexDataFlags(), + @ref MeshData::vertexDataFlags() */ enum class DataFlag: UnsignedByte { /** @@ -71,8 +72,8 @@ MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, DataFlag value); @brief Data flags @m_since_latest -@see @ref AnimationData::dataFlags(), @ref MeshData::indexDataFlags(), - @ref MeshData::vertexDataFlags() +@see @ref AnimationData::dataFlags(), @ref ImageData::dataFlags(), + @ref MeshData::indexDataFlags(), @ref MeshData::vertexDataFlags() */ typedef Containers::EnumSet DataFlags; diff --git a/src/Magnum/Trade/ImageData.cpp b/src/Magnum/Trade/ImageData.cpp index b390fd3570..8b2a2c43f3 100644 --- a/src/Magnum/Trade/ImageData.cpp +++ b/src/Magnum/Trade/ImageData.cpp @@ -35,17 +35,59 @@ namespace Magnum { namespace Trade { template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, format, {}, Magnum::pixelSize(format), size, std::move(data), importerState} {} +template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +template ImageData::ImageData(const PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{{}, format, size, std::move(data), importerState} {} + +template ImageData::ImageData(const PixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + template ImageData::ImageData(const PixelStorage storage, const UnsignedInt format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, pixelFormatWrap(format), formatExtra, pixelSize, size, std::move(data), importerState} {} -template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: _compressed{false}, _storage{storage}, _format{format}, _formatExtra{formatExtra}, _pixelSize{pixelSize}, _size{size}, _data{std::move(data)}, _importerState{importerState} { +template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _compressed{false}, _storage{storage}, _format{format}, _formatExtra{formatExtra}, _pixelSize{pixelSize}, _size{size}, _data{std::move(data)}, _importerState{importerState} { CORRADE_ASSERT(Magnum::Implementation::imageDataSize(*this) <= _data.size(), "Trade::ImageData: data too small, got" << _data.size() << "but expected at least" << Magnum::Implementation::imageDataSize(*this) << "bytes", ); } -template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: _compressed{true}, _compressedStorage{storage}, _compressedFormat{format}, _size{size}, _data{std::move(data)}, _importerState{importerState} {} +template ImageData::ImageData(const PixelStorage storage, const UnsignedInt format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, pixelFormatWrap(format), formatExtra, pixelSize, size, dataFlags, data, importerState} {} + +template ImageData::ImageData(const PixelStorage storage, const PixelFormat format, const UnsignedInt formatExtra, const UnsignedInt pixelSize, const VectorTypeFor& size, const DataFlags dataFlags, Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, format, formatExtra, pixelSize, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _compressed{true}, _compressedStorage{storage}, _compressedFormat{format}, _size{size}, _data{std::move(data)}, _importerState{importerState} {} + +template ImageData::ImageData(const CompressedPixelStorage storage, const CompressedPixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +template ImageData::ImageData(const CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{{}, format, size, std::move(data), importerState} {} + +template ImageData::ImageData(const CompressedPixelFormat format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} template ImageData::ImageData(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, compressedPixelFormatWrap(format), size, std::move(data), importerState} {} -template ImageData::ImageData(ImageData&& other) noexcept: _compressed{std::move(other._compressed)}, _size{std::move(other._size)}, _data{std::move(other._data)}, _importerState{std::move(other._importerState)} { +template ImageData::ImageData(const CompressedPixelStorage storage, const UnsignedInt format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, format, size, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::ImageData: can't construct a non-owned instance with" << dataFlags, ); + _dataFlags = dataFlags; +} + +template ImageData::ImageData(ImageData&& other) noexcept: _dataFlags{other._dataFlags}, _compressed{std::move(other._compressed)}, _size{std::move(other._size)}, _data{std::move(other._data)}, _importerState{std::move(other._importerState)} { if(_compressed) { new(&_compressedStorage) CompressedPixelStorage{std::move(other._compressedStorage)}; _compressedFormat = std::move(other._compressedFormat); @@ -66,6 +108,7 @@ template ImageData::ImageData(ImageData ImageData& ImageData::operator=(ImageData&& other) noexcept { using std::swap; + swap(_dataFlags, other._dataFlags); swap(_compressed, other._compressed); if(_compressed) { swap(_compressedStorage, other._compressedStorage); @@ -115,22 +158,25 @@ template UnsignedInt ImageData::pixelSize() template std::pair, VectorTypeFor> ImageData::dataProperties() const { CORRADE_ASSERT(!_compressed, "Trade::ImageData::dataProperties(): the image is compressed", {}); - return Implementation::imageDataProperties(*this); + return Magnum::Implementation::imageDataProperties(*this); } -template Containers::StridedArrayView ImageData::pixels() { - CORRADE_ASSERT(!_compressed, "Trade::ImageData::pixels(): the image is compressed", {}); - return Implementation::imagePixelView(*this); +template Containers::ArrayView ImageData::mutableData() & { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::ImageData::mutableData(): the image is not mutable", {}); + return _data; } template Containers::StridedArrayView ImageData::pixels() const { CORRADE_ASSERT(!_compressed, "Trade::ImageData::pixels(): the image is compressed", {}); - return Implementation::imagePixelView(*this); + return Magnum::Implementation::imagePixelView(*this, data()); } -template ImageData::operator BasicMutableImageView() { - CORRADE_ASSERT(!_compressed, "Trade::ImageData: the image is compressed", (BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size})); - return BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size, _data}; +template Containers::StridedArrayView ImageData::mutablePixels() { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::ImageData::mutablePixels(): the image is not mutable", {}); + CORRADE_ASSERT(!_compressed, "Trade::ImageData::mutablePixels(): the image is compressed", {}); + return Magnum::Implementation::imagePixelView(*this, mutableData()); } template ImageData::operator BasicImageView() const { @@ -138,11 +184,11 @@ template ImageData::operator BasicImageView< return BasicImageView{_storage, _format, _formatExtra, _pixelSize, _size, _data}; } -template ImageData::operator BasicMutableCompressedImageView() { - CORRADE_ASSERT(_compressed, "Trade::ImageData: the image is not compressed", (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, _size})); - return BasicMutableCompressedImageView{ - _compressedStorage, - _compressedFormat, _size, _data}; +template ImageData::operator BasicMutableImageView() { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::ImageData: the image is not mutable", (BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size})); + CORRADE_ASSERT(!_compressed, "Trade::ImageData: the image is compressed", (BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size})); + return BasicMutableImageView{_storage, _format, _formatExtra, _pixelSize, _size, _data}; } template ImageData::operator BasicCompressedImageView() const { @@ -152,6 +198,16 @@ template ImageData::operator BasicCompressed _compressedFormat, _size, _data}; } +template ImageData::operator BasicMutableCompressedImageView() { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::ImageData: the image is not mutable", + (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, _size})); + CORRADE_ASSERT(_compressed, "Trade::ImageData: the image is not compressed", (BasicMutableCompressedImageView{_compressedStorage, _compressedFormat, _size})); + return BasicMutableCompressedImageView{ + _compressedStorage, + _compressedFormat, _size, _data}; +} + template Containers::Array ImageData::release() { Containers::Array data{std::move(_data)}; _size = {}; diff --git a/src/Magnum/Trade/ImageData.h b/src/Magnum/Trade/ImageData.h index ea025c0bd9..7caaf4c9ee 100644 --- a/src/Magnum/Trade/ImageData.h +++ b/src/Magnum/Trade/ImageData.h @@ -33,6 +33,7 @@ #include "Magnum/DimensionTraits.h" #include "Magnum/PixelStorage.h" +#include "Magnum/Trade/Data.h" #include "Magnum/Trade/Trade.h" #include "Magnum/Trade/visibility.h" @@ -87,6 +88,18 @@ compressed properties through @ref compressedStorage() and @snippet MagnumTrade.cpp ImageData-usage +@section Trade-ImageData-usage-mutable Mutable data access + +The interfaces implicitly provide @cpp const @ce views on the contained +pixel data through the @ref data() and @ref pixels() accessors. This is done +because in general case the data can also refer to a memory-mapped file or +constant memory. In cases when it's desirable to modify the data in-place, +there's the @ref mutableData() and @ref mutablePixels() set of functions. To +use these, you need to check that the data are mutable using @ref dataFlags() +first. The following snippet flips the R and B channels of the imported image: + +@snippet MagnumTrade.cpp ImageData-usage-mutable + @see @ref ImageData1D, @ref ImageData2D, @ref ImageData3D, @ref Image-pixel-views */ @@ -97,7 +110,7 @@ template class ImageData { }; /** - * @brief Construct uncompressed image data + * @brief Construct an uncompressed image data * @param storage Storage of pixel data * @param format Format of pixel data * @param size Image size @@ -106,11 +119,34 @@ template class ImageData { * * The @p data array is expected to be of proper size for given * parameters. + * + * The @ref dataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For non-owned data + * use the @ref ImageData(PixelStorage, PixelFormat, const VectorTypeFor&, DataFlags, Containers::ArrayView, const void*) + * constructor instead. */ explicit ImageData(PixelStorage storage, PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct uncompressed image data + * @brief Construct a non-owned uncompressed image data + * @param storage Storage of pixel data + * @param format Format of pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelStorage, PixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(PixelStorage storage, PixelFormat format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an uncompressed image data * @param format Format of pixel data * @param size Image size * @param data Image data @@ -119,10 +155,27 @@ template class ImageData { * Equivalent to calling @ref ImageData(PixelStorage, PixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) * with default-constructed @ref PixelStorage. */ - explicit ImageData(PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept: ImageData{{}, format, size, std::move(data), importerState} {} + explicit ImageData(PixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a non-owned uncompressed image data + * @param format Format of pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(PixelFormat format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; /** - * @brief Construct uncompressed image data with implementation-specific pixel format + * @brief Construct an uncompressed image data with implementation-specific pixel format * @param storage Storage of pixel data * @param format Format of pixel data * @param formatExtra Additional pixel format specifier @@ -139,7 +192,10 @@ template class ImageData { * @ref Magnum::PixelFormat "PixelFormat". * * The @p data array is expected to be of proper size for given - * parameters. + * parameters. The @ref dataFlags() are implicitly set to a combination + * of @ref DataFlag::Owned and @ref DataFlag::Mutable. For non-owned + * data use the @ref ImageData(PixelStorage, UnsignedInt, UnsignedInt, UnsignedInt, const VectorTypeFor&, DataFlags, Containers::ArrayView, const void*) + * constructor instead. */ explicit ImageData(PixelStorage storage, UnsignedInt format, UnsignedInt formatExtra, UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; @@ -151,7 +207,35 @@ template class ImageData { explicit ImageData(PixelStorage storage, PixelFormat format, UnsignedInt formatExtra, UnsignedInt pixelSize, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct uncompressed image data with implementation-specific pixel format + * @brief Construct a non-owned uncompressed image data with implementation-specific pixel format + * @param storage Storage of pixel data + * @param format Format of pixel data + * @param formatExtra Additional pixel format specifier + * @param pixelSize Size of a pixel in given format + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelStorage, UnsignedInt, UnsignedInt, UnsignedInt, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(PixelStorage storage, UnsignedInt format, UnsignedInt formatExtra, UnsignedInt pixelSize, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** @overload + * @m_since_latest + * + * Equivalent to the above for @p format already wrapped with + * @ref pixelFormatWrap(). + */ + explicit ImageData(PixelStorage storage, PixelFormat format, UnsignedInt formatExtra, UnsignedInt pixelSize, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an uncompressed image data with implementation-specific pixel format * @param storage Storage of pixel data * @param format Format of pixel data * @param formatExtra Additional pixel format specifier @@ -166,7 +250,26 @@ template class ImageData { template explicit ImageData(PixelStorage storage, T format, U formatExtra, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct uncompressed image data with implementation-specific pixel format + * @brief Construct a non-owned uncompressed image data with implementation-specific pixel format + * @param storage Storage of pixel data + * @param format Format of pixel data + * @param formatExtra Additional pixel format specifier + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelStorage, T, U, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + template explicit ImageData(PixelStorage storage, T format, U formatExtra, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct an uncompressed image data with implementation-specific pixel format * @param storage Storage of pixel data * @param format Format of pixel data * @param size Image size @@ -180,7 +283,25 @@ template class ImageData { template explicit ImageData(PixelStorage storage, T format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct compressed image data + * @brief Construct a non-owned uncompressed image data with implementation-specific pixel format + * @param storage Storage of pixel data + * @param format Format of pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data view on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(PixelStorage, T, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + template explicit ImageData(PixelStorage storage, T format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a compressed image data * @param storage Storage of compressed pixel data * @param format Format of compressed pixel data * @param size Image size @@ -190,7 +311,25 @@ template class ImageData { explicit ImageData(CompressedPixelStorage storage, CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; /** - * @brief Construct compressed image data + * @brief Construct a non-owned compressed image data + * @param storage Storage of compressed pixel data + * @param format Format of compressed pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(CompressedPixelStorage, CompressedPixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(CompressedPixelStorage storage, CompressedPixelFormat format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a compressed image data * @param format Format of compressed pixel data * @param size Image size * @param data Image data @@ -199,10 +338,27 @@ template class ImageData { * Equivalent to calling @ref ImageData(CompressedPixelStorage, CompressedPixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) * with default-constructed @ref CompressedPixelStorage. */ - explicit ImageData(CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept: ImageData{{}, format, size, std::move(data), importerState} {} + explicit ImageData(CompressedPixelFormat format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; + + /** + * @brief Construct a non-owned compressed image data + * @param format Format of compressed pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(CompressedPixelFormat, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit ImageData(CompressedPixelFormat format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; /** - * @brief Construct compressed image data + * @brief Construct a compressed image data * @param storage Storage of compressed pixel data * @param format Format of compressed pixel data * @param size Image size @@ -214,6 +370,24 @@ template class ImageData { */ template explicit ImageData(CompressedPixelStorage storage, T format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; + /** + * @brief Construct a non-owned compressed image data + * @param storage Storage of compressed pixel data + * @param format Format of compressed pixel data + * @param size Image size + * @param dataFlags Data flags + * @param data View on image data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref ImageData(CompressedPixelStorage, T, const VectorTypeFor&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + template explicit ImageData(CompressedPixelStorage storage, T format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + /** * @brief Construct from existing data with attached importer state * @@ -237,6 +411,12 @@ template class ImageData { /** @brief Move assignment */ ImageData& operator=(ImageData&& other) noexcept; + /** + * @brief Data flags + * @m_since_latest + */ + DataFlags dataFlags() const { return _dataFlags; } + /** @brief Whether the image is compressed */ bool isCompressed() const { return _compressed; } @@ -253,6 +433,9 @@ template class ImageData { /** * @brief Conversion to a mutable view * @m_since{2019,10} + * + * The image is expected to be uncompressed and mutable. + * @see @ref isCompressed(), @ref dataFlags() */ /*implicit*/ operator BasicMutableImageView(); @@ -269,6 +452,9 @@ template class ImageData { /** * @brief Conversion to a mutable compressed view * @m_since{2019,10} + * + * The image is expected to be compressed and mutable. + * @see @ref isCompressed(), @ref dataFlags() */ /*implicit*/ operator BasicMutableCompressedImageView(); @@ -358,9 +544,6 @@ template class ImageData { * * @see @ref release(), @ref pixels() */ - Containers::ArrayView data() & { return _data; } - - /** @overload */ Containers::ArrayView data() const & { return _data; } /** @@ -369,7 +552,9 @@ template class ImageData { * * Unlike @ref data(), which returns a view, this is equivalent to * @ref release() to avoid a dangling view when the temporary instance - * goes out of scope. + * goes out of scope. Note that the returned array has a custom no-op + * deleter when the data are not owned by the image, and while the + * returned array type is mutable, the actual memory might be not. * @todoc stupid doxygen can't link to & overloads ffs */ Containers::Array data() && { return release(); } @@ -380,6 +565,16 @@ template class ImageData { */ Containers::Array data() const && = delete; + /** + * @brief Mutable image data + * @m_since_latest + * + * Like @ref data(), but returns a non-const view. Expects that the + * image is mutable. + * @see @ref dataFlags() + */ + Containers::ArrayView mutableData() &; + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Image data in a particular type @@ -414,8 +609,17 @@ template class ImageData { * @see @ref isCompressed(), * @ref Corrade::Containers::StridedArrayView::isContiguous() */ - Containers::StridedArrayView pixels(); - Containers::StridedArrayView pixels() const; /**< @overload */ + Containers::StridedArrayView pixels() const; + + /** + * @brief Mutable view on pixel data + * @m_since_latest + * + * Like @ref pixels() const, but returns a non-const view. Expects that + * the image is mutable. + * @see @ref dataFlags() + */ + Containers::StridedArrayView mutablePixels(); /** * @brief View on pixel data with a concrete pixel type @@ -426,26 +630,35 @@ template class ImageData { * correct type for given @ref format() --- checking it on the library * side is not possible for the general case. */ - template Containers::StridedArrayView pixels() { + template Containers::StridedArrayView pixels() const { /* Deliberately not adding a StridedArrayView include, it should work without since this is a templated function and we declare arrayCast() above to satisfy two-phase lookup. */ - return Containers::arrayCast(pixels()); + return Containers::arrayCast(pixels()); } /** - * @overload + * @brief Mutable view on pixel data with a concrete pixel type * @m_since{2019,10} + * + * Like @ref pixels() const, but returns a non-const view. Expects that + * the image is mutable. + * @see @ref dataFlags() */ - template Containers::StridedArrayView pixels() const { - return Containers::arrayCast(pixels()); + template Containers::StridedArrayView mutablePixels() { + /* Deliberately not adding a StridedArrayView include, it should + work without since this is a templated function */ + return Containers::arrayCast(mutablePixels()); } /** * @brief Release data storage * * Releases the ownership of the data array and resets internal state - * to default. + * to default. The image then behaves like it's empty. Note that + * the returned array has a custom no-op deleter when the data are not + * owned by the image, and while the returned array type is mutable, + * the actual memory might be not. * @see @ref data() */ Containers::Array release(); @@ -465,6 +678,9 @@ template class ImageData { explicit ImageData(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor& size, Containers::Array&& data, const void* importerState = nullptr) noexcept; + explicit ImageData(CompressedPixelStorage storage, UnsignedInt format, const VectorTypeFor& size, DataFlags dataFlags, Containers::ArrayView data, const void* importerState = nullptr) noexcept; + + DataFlags _dataFlags; bool _compressed; union { PixelStorage _storage; @@ -495,16 +711,31 @@ template template ImageData template ImageData::ImageData(const PixelStorage storage, const T format, const U formatExtra, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), UnsignedInt(formatExtra), Magnum::Implementation::pixelSizeAdl(format, formatExtra), size, dataFlags, data, importerState} { + static_assert(sizeof(T) <= 4 && sizeof(U) <= 4, + "format types larger than 32bits are not supported"); +} + template template ImageData::ImageData(const PixelStorage storage, const T format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), {}, Magnum::Implementation::pixelSizeAdl(format), size, std::move(data), importerState} { static_assert(sizeof(T) <= 4, "format types larger than 32bits are not supported"); } +template template ImageData::ImageData(const PixelStorage storage, const T format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), {}, Magnum::Implementation::pixelSizeAdl(format), size, dataFlags, data, importerState} { + static_assert(sizeof(T) <= 4, + "format types larger than 32bits are not supported"); +} + template template ImageData::ImageData(const CompressedPixelStorage storage, const T format, const VectorTypeFor& size, Containers::Array&& data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), size, std::move(data), importerState} { static_assert(sizeof(T) <= 4, "format types larger than 32bits are not supported"); } +template template ImageData::ImageData(const CompressedPixelStorage storage, const T format, const VectorTypeFor& size, const DataFlags dataFlags, const Containers::ArrayView data, const void* const importerState) noexcept: ImageData{storage, UnsignedInt(format), size, dataFlags, data, importerState} { + static_assert(sizeof(T) <= 4, + "format types larger than 32bits are not supported"); +} + }} #endif diff --git a/src/Magnum/Trade/Test/ImageDataTest.cpp b/src/Magnum/Trade/Test/ImageDataTest.cpp index d68a895bca..bfe8207237 100644 --- a/src/Magnum/Trade/Test/ImageDataTest.cpp +++ b/src/Magnum/Trade/Test/ImageDataTest.cpp @@ -47,6 +47,15 @@ struct ImageDataTest: TestSuite::Tester { void constructCompressedGeneric(); void constructCompressedImplementationSpecific(); + void constructGenericNotOwned(); + void constructImplementationSpecificNotOwned(); + void constructCompressedGenericNotOwned(); + void constructCompressedImplementationSpecificNotOwned(); + void constructGenericNotOwnedFlagOwned(); + void constructImplementationSpecificNotOwnedFlagOwned(); + void constructCompressedGenericNotOwnedFlagOwned(); + void constructCompressedImplementationSpecificNotOwnedFlagOwned(); + void constructInvalidSize(); void constructCompressedInvalidSize(); @@ -67,6 +76,7 @@ struct ImageDataTest: TestSuite::Tester { void data(); void dataRvalue(); + void mutableAccessNotAllowed(); void dataProperties(); @@ -91,11 +101,30 @@ template<> struct MutabilityTraits { static const char* name() { return "MutableImageView"; } }; +struct { + const char* name; + DataFlags dataFlags; +} NotOwnedData[] { + {"", {}}, + {"mutable", DataFlag::Mutable}, +}; + ImageDataTest::ImageDataTest() { addTests({&ImageDataTest::constructGeneric, &ImageDataTest::constructImplementationSpecific, &ImageDataTest::constructCompressedGeneric, - &ImageDataTest::constructCompressedImplementationSpecific, + &ImageDataTest::constructCompressedImplementationSpecific}); + + addInstancedTests({&ImageDataTest::constructGenericNotOwned, + &ImageDataTest::constructImplementationSpecificNotOwned, + &ImageDataTest::constructCompressedGenericNotOwned, + &ImageDataTest::constructCompressedImplementationSpecificNotOwned}, + Containers::arraySize(NotOwnedData)); + + addTests({&ImageDataTest::constructGenericNotOwnedFlagOwned, + &ImageDataTest::constructImplementationSpecificNotOwnedFlagOwned, + &ImageDataTest::constructCompressedGenericNotOwnedFlagOwned, + &ImageDataTest::constructCompressedImplementationSpecificNotOwnedFlagOwned, &ImageDataTest::constructInvalidSize, &ImageDataTest::constructCompressedInvalidSize, @@ -121,6 +150,7 @@ ImageDataTest::ImageDataTest() { &ImageDataTest::data, &ImageDataTest::dataRvalue, + &ImageDataTest::mutableAccessNotAllowed, &ImageDataTest::dataProperties, @@ -167,14 +197,19 @@ void ImageDataTest::constructGeneric() { int state; ImageData2D a{PixelFormat::RGBA8Unorm, {1, 3}, Containers::Array{data, 4*4}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.storage().alignment(), 4); CORRADE_COMPARE(a.format(), PixelFormat::RGBA8Unorm); CORRADE_COMPARE(a.formatExtra(), 0); CORRADE_COMPARE(a.pixelSize(), 4); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 4*4); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 4*4); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } { auto data = new char[3*2]; @@ -182,14 +217,19 @@ void ImageDataTest::constructGeneric() { ImageData2D a{PixelStorage{}.setAlignment(1), PixelFormat::R16UI, {1, 3}, Containers::Array{data, 3*2}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.storage().alignment(), 1); CORRADE_COMPARE(a.format(), PixelFormat::R16UI); CORRADE_COMPARE(a.formatExtra(), 0); CORRADE_COMPARE(a.pixelSize(), 2); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 3*2); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*2); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } } @@ -202,14 +242,19 @@ void ImageDataTest::constructImplementationSpecific() { ImageData2D a{PixelStorage{}.setAlignment(1), Vk::PixelFormat::R32G32B32F, {1, 3}, Containers::Array{data, 3*12}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.storage().alignment(), 1); CORRADE_COMPARE(a.format(), pixelFormatWrap(Vk::PixelFormat::R32G32B32F)); CORRADE_COMPARE(a.formatExtra(), 0); CORRADE_COMPARE(a.pixelSize(), 12); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 3*12); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*12); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } @@ -220,13 +265,18 @@ void ImageDataTest::constructImplementationSpecific() { ImageData2D a{PixelStorage{}.setAlignment(1), GL::PixelFormat::RGB, GL::PixelType::UnsignedShort, {1, 3}, Containers::Array{data, 3*6}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.format(), pixelFormatWrap(GL::PixelFormat::RGB)); CORRADE_COMPARE(a.formatExtra(), UnsignedInt(GL::PixelType::UnsignedShort)); CORRADE_COMPARE(a.pixelSize(), 6); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.pixels>()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.mutablePixels>()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } @@ -236,14 +286,19 @@ void ImageDataTest::constructImplementationSpecific() { int state; ImageData2D a{PixelStorage{}.setAlignment(1), 666, 1337, 6, {1, 3}, Containers::Array{data, 3*6}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!a.isCompressed()); CORRADE_COMPARE(a.storage().alignment(), 1); CORRADE_COMPARE(a.format(), pixelFormatWrap(GL::PixelFormat::RGB)); CORRADE_COMPARE(a.formatExtra(), UnsignedInt(GL::PixelType::UnsignedShort)); CORRADE_COMPARE(a.pixelSize(), 6); CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.pixels>()[0][0]), data); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.mutablePixels>()[0][0]), data); CORRADE_COMPARE(a.importerState(), &state); } } @@ -255,12 +310,15 @@ void ImageDataTest::constructCompressedGeneric() { ImageData2D a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, Containers::Array{data, 8}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{0}); CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); CORRADE_COMPARE(a.importerState(), &state); } { auto data = new char[8]; @@ -269,12 +327,15 @@ void ImageDataTest::constructCompressedGeneric() { CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, Containers::Array{data, 8}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); CORRADE_COMPARE(a.size(), Vector2i(4, 4)); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); CORRADE_COMPARE(a.importerState(), &state); } } @@ -288,18 +349,270 @@ void ImageDataTest::constructCompressedImplementationSpecific() { GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, Containers::Array{data, 8}, &state}; + CORRADE_COMPARE(a.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(a.isCompressed()); CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(a.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); - CORRADE_COMPARE(a.data(), data); + CORRADE_COMPARE(static_cast(a.data().data()), data); CORRADE_COMPARE(a.data().size(), 8); + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); CORRADE_COMPARE(a.importerState(), &state); } /* Manual properties not implemented yet */ } +void ImageDataTest::constructGenericNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + { + char data[4*4]; + int state; + ImageData2D a{PixelFormat::RGBA8Unorm, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.storage().alignment(), 4); + CORRADE_COMPARE(a.format(), PixelFormat::RGBA8Unorm); + CORRADE_COMPARE(a.formatExtra(), 0); + CORRADE_COMPARE(a.pixelSize(), 4); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 4*4); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 4*4); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } { + char data[3*2]; + int state; + ImageData2D a{PixelStorage{}.setAlignment(1), + PixelFormat::R16UI, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.storage().alignment(), 1); + CORRADE_COMPARE(a.format(), PixelFormat::R16UI); + CORRADE_COMPARE(a.formatExtra(), 0); + CORRADE_COMPARE(a.pixelSize(), 2); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 3*2); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*2); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } +} + +void ImageDataTest::constructImplementationSpecificNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + /* Single format */ + { + char data[3*12]; + int state; + ImageData2D a{PixelStorage{}.setAlignment(1), + Vk::PixelFormat::R32G32B32F, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.storage().alignment(), 1); + CORRADE_COMPARE(a.format(), pixelFormatWrap(Vk::PixelFormat::R32G32B32F)); + CORRADE_COMPARE(a.formatExtra(), 0); + CORRADE_COMPARE(a.pixelSize(), 12); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 3*12); + CORRADE_COMPARE(static_cast(&a.pixels()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*12); + CORRADE_COMPARE(static_cast(&a.mutablePixels()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } + + /* Format + extra */ + { + char data[3*6]; + int state; + ImageData2D a{PixelStorage{}.setAlignment(1), + GL::PixelFormat::RGB, GL::PixelType::UnsignedShort, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.format(), pixelFormatWrap(GL::PixelFormat::RGB)); + CORRADE_COMPARE(a.formatExtra(), UnsignedInt(GL::PixelType::UnsignedShort)); + CORRADE_COMPARE(a.pixelSize(), 6); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.pixels>()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.mutablePixels>()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } + + /* Manual pixel size */ + { + char data[3*6]; + int state; + ImageData2D a{PixelStorage{}.setAlignment(1), 666, 1337, 6, {1, 3}, instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(!a.isCompressed()); + CORRADE_COMPARE(a.storage().alignment(), 1); + CORRADE_COMPARE(a.format(), pixelFormatWrap(GL::PixelFormat::RGB)); + CORRADE_COMPARE(a.formatExtra(), UnsignedInt(GL::PixelType::UnsignedShort)); + CORRADE_COMPARE(a.pixelSize(), 6); + CORRADE_COMPARE(a.size(), (Vector2i{1, 3})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.pixels>()[0][0]), data); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 3*6); + CORRADE_COMPARE(static_cast(&a.mutablePixels>()[0][0]), data); + } + CORRADE_COMPARE(a.importerState(), &state); + } +} + +void ImageDataTest::constructCompressedGenericNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + { + char data[8]; + int state; + ImageData2D a{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, + instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(a.isCompressed()); + CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{0}); + CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); + CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 8); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); + } + CORRADE_COMPARE(a.importerState(), &state); + } { + char data[8]; + int state; + ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, + instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(a.isCompressed()); + CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); + CORRADE_COMPARE(a.compressedFormat(), CompressedPixelFormat::Bc1RGBAUnorm); + CORRADE_COMPARE(a.size(), Vector2i(4, 4)); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 8); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); + } + CORRADE_COMPARE(a.importerState(), &state); + } +} + +void ImageDataTest::constructCompressedImplementationSpecificNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + /* Format with autodetection */ + { + char data[8]; + int state; + ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, + instanceData.dataFlags, data, &state}; + + CORRADE_COMPARE(a.dataFlags(), instanceData.dataFlags); + CORRADE_VERIFY(a.isCompressed()); + CORRADE_COMPARE(a.compressedStorage().compressedBlockSize(), Vector3i{4}); + CORRADE_COMPARE(a.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); + CORRADE_COMPARE(a.size(), (Vector2i{4, 4})); + CORRADE_COMPARE(static_cast(a.data().data()), data); + CORRADE_COMPARE(a.data().size(), 8); + if(instanceData.dataFlags & DataFlag::Mutable) { + CORRADE_COMPARE(static_cast(a.mutableData().data()), data); + CORRADE_COMPARE(a.mutableData().size(), 8); + } + CORRADE_COMPARE(a.importerState(), &state); + } + + /* Manual properties not implemented yet */ +} + +void ImageDataTest::constructGenericNotOwnedFlagOwned() { + char data[4*4]; + + std::ostringstream out; + Error redirectError{&out}; + ImageData2D{PixelFormat::RGBA8Unorm, {1, 3}, DataFlag::Owned, data}; + ImageData2D{PixelStorage{}.setAlignment(1), PixelFormat::R16UI, {1, 3}, DataFlag::Owned, data}; + CORRADE_COMPARE(out.str(), + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n" + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + +void ImageDataTest::constructImplementationSpecificNotOwnedFlagOwned() { + char data[3*12]; + + std::ostringstream out; + Error redirectError{&out}; + ImageData2D{PixelStorage{}.setAlignment(1), Vk::PixelFormat::R32G32B32F, {1, 3}, DataFlag::Owned, data}; + ImageData2D{PixelStorage{}.setAlignment(1), GL::PixelFormat::RGB, GL::PixelType::UnsignedShort, {1, 3}, DataFlag::Owned, data}; + CORRADE_COMPARE(out.str(), + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n" + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + +void ImageDataTest::constructCompressedGenericNotOwnedFlagOwned() { + char data[8]; + + std::ostringstream out; + Error redirectError{&out}; + ImageData2D{CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, DataFlag::Owned, data}; + ImageData2D{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + CompressedPixelFormat::Bc1RGBAUnorm, {4, 4}, DataFlag::Owned, data}; + CORRADE_COMPARE(out.str(), + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n" + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + +void ImageDataTest::constructCompressedImplementationSpecificNotOwnedFlagOwned() { + char data[8]; + + std::ostringstream out; + Error redirectError{&out}; + ImageData2D a{CompressedPixelStorage{}.setCompressedBlockSize(Vector3i{4}), + GL::CompressedPixelFormat::RGBS3tcDxt1, {4, 4}, DataFlag::Owned, data}; + CORRADE_COMPARE(out.str(), + "Trade::ImageData: can't construct a non-owned instance with Trade::DataFlag::Owned\n"); +} + void ImageDataTest::constructInvalidSize() { std::ostringstream out; Error redirectError{&out}; @@ -343,6 +656,7 @@ void ImageDataTest::constructMoveGeneric() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!b.isCompressed()); CORRADE_COMPARE(b.storage().alignment(), 1); CORRADE_COMPARE(b.format(), PixelFormat::RGBA32F); @@ -360,6 +674,7 @@ void ImageDataTest::constructMoveGeneric() { CORRADE_COMPARE(b.data(), data2); CORRADE_COMPARE(b.size(), (Vector2i{2, 6})); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!c.isCompressed()); CORRADE_COMPARE(c.storage().alignment(), 1); CORRADE_COMPARE(c.format(), PixelFormat::RGBA32F); @@ -384,6 +699,7 @@ void ImageDataTest::constructMoveImplementationSpecific() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!b.isCompressed()); CORRADE_COMPARE(b.storage().alignment(), 1); CORRADE_COMPARE(b.format(), pixelFormatWrap(GL::PixelFormat::RGB)); @@ -402,6 +718,7 @@ void ImageDataTest::constructMoveImplementationSpecific() { CORRADE_COMPARE(b.data(), data2); CORRADE_COMPARE(b.size(), Vector2i(2, 6)); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!c.isCompressed()); CORRADE_COMPARE(c.storage().alignment(), 1); CORRADE_COMPARE(c.format(), pixelFormatWrap(GL::PixelFormat::RGB)); @@ -424,6 +741,7 @@ void ImageDataTest::constructMoveCompressedGeneric() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(b.isCompressed()); CORRADE_COMPARE(b.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(b.compressedFormat(), CompressedPixelFormat::Bc3RGBAUnorm); @@ -439,6 +757,7 @@ void ImageDataTest::constructMoveCompressedGeneric() { CORRADE_COMPARE(b.data(), data2); CORRADE_COMPARE(b.size(), (Vector2i{8, 4})); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(c.isCompressed()); CORRADE_COMPARE(c.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(c.compressedFormat(), CompressedPixelFormat::Bc3RGBAUnorm); @@ -459,6 +778,7 @@ void ImageDataTest::constructMoveCompressedImplementationSpecific() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(b.isCompressed()); CORRADE_COMPARE(b.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(b.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); @@ -474,6 +794,7 @@ void ImageDataTest::constructMoveCompressedImplementationSpecific() { CORRADE_COMPARE(b.data(), data2); CORRADE_COMPARE(b.size(), (Vector2i{8, 4})); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(c.isCompressed()); CORRADE_COMPARE(c.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(c.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); @@ -493,6 +814,7 @@ void ImageDataTest::constructMoveAttachState() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(!b.isCompressed()); CORRADE_COMPARE(b.storage().alignment(), 1); CORRADE_COMPARE(b.format(), pixelFormatWrap(GL::PixelFormat::RGB)); @@ -515,6 +837,7 @@ void ImageDataTest::constructMoveCompressedAttachState() { CORRADE_COMPARE(a.data(), nullptr); CORRADE_COMPARE(a.size(), Vector2i{}); + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_VERIFY(b.isCompressed()); CORRADE_COMPARE(b.compressedStorage().compressedBlockSize(), Vector3i{4}); CORRADE_COMPARE(b.compressedFormat(), compressedPixelFormatWrap(GL::CompressedPixelFormat::RGBS3tcDxt1)); @@ -603,6 +926,31 @@ void ImageDataTest::dataRvalue() { CORRADE_COMPARE(released.data(), data); } +void ImageDataTest::mutableAccessNotAllowed() { + const char data[4*4]{}; + ImageData2D a{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, data}; + + std::ostringstream out; + Error redirectError{&out}; + a.mutableData(); + a.mutablePixels(); + /* Can't do just MutableImageView2D(a) because the compiler then treats it + as a function. Can't do MutableImageView2D{a} because that doesn't work + on old Clang. So it's this mess, then. Sigh. */ + auto b = MutableImageView2D(a); + static_cast(b); + auto c = MutableCompressedImageView2D(a); + static_cast(c); + /* a.mutablePixels() calls non-templated mutablePixels(), so assume + there it will blow up correctly as well (can't test because it asserts + inside arrayCast() due to zero stride) */ + CORRADE_COMPARE(out.str(), + "Trade::ImageData::mutableData(): the image is not mutable\n" + "Trade::ImageData::mutablePixels(): the image is not mutable\n" + "Trade::ImageData: the image is not mutable\n" + "Trade::ImageData: the image is not mutable\n"); +} + void ImageDataTest::dataProperties() { ImageData3D image{ PixelStorage{} @@ -646,12 +994,12 @@ void ImageDataTest::pixels1D() { /* Full test is in ImageTest, this is just a sanity check */ { - Containers::StridedArrayView1D pixels = image.pixels(); + Containers::StridedArrayView1D pixels = image.mutablePixels(); CORRADE_COMPARE(pixels.size(), 2); CORRADE_COMPARE(pixels.stride(), 3); CORRADE_COMPARE(pixels.data(), image.data() + 3*3); } { - Containers::StridedArrayView1D pixels = Containers::arrayCast<1, const Color3ub>(cimage.pixels()); + Containers::StridedArrayView1D pixels = cimage.pixels(); CORRADE_COMPARE(pixels.size(), 2); CORRADE_COMPARE(pixels.stride(), 3); CORRADE_COMPARE(pixels.data(), cimage.data() + 3*3); @@ -671,12 +1019,12 @@ void ImageDataTest::pixels2D() { /* Full test is in ImageTest, this is just a sanity check */ { - Containers::StridedArrayView2D pixels = image.pixels(); + Containers::StridedArrayView2D pixels = image.mutablePixels(); CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView2D::Size{4, 2})); CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView2D::Stride{20, 3})); CORRADE_COMPARE(pixels.data(), image.data() + 2*20 + 3*3); } { - Containers::StridedArrayView2D pixels = Containers::arrayCast<2, const Color3ub>(cimage.pixels()); + Containers::StridedArrayView2D pixels = cimage.pixels(); CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView2D::Size{4, 2})); CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView2D::Stride{20, 3})); CORRADE_COMPARE(pixels.data(), cimage.data() + 2*20 + 3*3); @@ -697,7 +1045,7 @@ void ImageDataTest::pixels3D() { /* Full test is in ImageTest, this is just a sanity check */ { - Containers::StridedArrayView3D pixels = image.pixels(); + Containers::StridedArrayView3D pixels = image.mutablePixels(); CORRADE_COMPARE(pixels.size(), (Containers::StridedArrayView3D::Size{3, 4, 2})); CORRADE_COMPARE(pixels.stride(), (Containers::StridedArrayView3D::Stride{140, 20, 3})); CORRADE_COMPARE(pixels.data(), image.data() + 140 + 2*20 + 3*3); From 4011e3006d74551f3ed477b0bb10c111bd01e590 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 13 Nov 2019 21:22:10 +0100 Subject: [PATCH 033/107] Trade: allow non-owning aray deleters passed through Importer APIs. --- src/Magnum/Trade/AbstractImporter.cpp | 17 ++- src/Magnum/Trade/AbstractImporter.h | 8 +- .../Trade/Test/AbstractImporterTest.cpp | 114 ++++++++++++++++++ 3 files changed, 131 insertions(+), 8 deletions(-) diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index d9abe242e5..50882268d9 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -268,7 +268,10 @@ Containers::Optional AbstractImporter::animation(const UnsignedIn CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::animation(): no file opened", {}); CORRADE_ASSERT(id < doAnimationCount(), "Trade::AbstractImporter::animation(): index" << id << "out of range for" << doAnimationCount() << "entries", {}); Containers::Optional animation = doAnimation(id); - CORRADE_ASSERT(!animation || (!animation->_data.deleter() && !animation->_tracks.deleter()), "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter", {}); + CORRADE_ASSERT(!animation || + ((!animation->_data.deleter() || animation->_data.deleter() == Implementation::nonOwnedArrayDeleter) && + (!animation->_tracks.deleter() || animation->_tracks.deleter() == reinterpret_cast(Implementation::nonOwnedArrayDeleter))), + "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter", {}); return animation; } @@ -430,7 +433,11 @@ Containers::Optional AbstractImporter::mesh(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh(): no file opened", {}); CORRADE_ASSERT(id < doMeshCount(), "Trade::AbstractImporter::mesh(): index" << id << "out of range for" << doMeshCount() << "entries", {}); Containers::Optional mesh = doMesh(id); - CORRADE_ASSERT(!mesh || (!mesh->_indexData.deleter() && !mesh->_vertexData.deleter() && !mesh->_attributes.deleter()), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter", {}); + CORRADE_ASSERT(!mesh || ( + (!mesh->_indexData.deleter() || mesh->_indexData.deleter() == Implementation::nonOwnedArrayDeleter) && + (!mesh->_vertexData.deleter() || mesh->_vertexData.deleter() == Implementation::nonOwnedArrayDeleter) && + (!mesh->_attributes.deleter() || mesh->_attributes.deleter() == reinterpret_cast(Implementation::nonOwnedArrayDeleter))), + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter", {}); return mesh; } @@ -633,7 +640,7 @@ Containers::Optional AbstractImporter::image1D(const UnsignedInt id } #endif Containers::Optional image = doImage1D(id, level); - CORRADE_ASSERT(!image || !image->_data.deleter(), "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter", {}); + CORRADE_ASSERT(!image || !image->_data.deleter() || image->_data.deleter() == Implementation::nonOwnedArrayDeleter, "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter", {}); return image; } @@ -689,7 +696,7 @@ Containers::Optional AbstractImporter::image2D(const UnsignedInt id } #endif Containers::Optional image = doImage2D(id, level); - CORRADE_ASSERT(!image || !image->_data.deleter(), "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter", {}); + CORRADE_ASSERT(!image || !image->_data.deleter() || image->_data.deleter() == Implementation::nonOwnedArrayDeleter, "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter", {}); return image; } @@ -745,7 +752,7 @@ Containers::Optional AbstractImporter::image3D(const UnsignedInt id } #endif Containers::Optional image = doImage3D(id, level); - CORRADE_ASSERT(!image || !image->_data.deleter(), "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter", {}); + CORRADE_ASSERT(!image || !image->_data.deleter() || image->_data.deleter() == Implementation::nonOwnedArrayDeleter, "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter", {}); return image; } diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index 024c7ffaa7..212c735753 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -194,9 +194,11 @@ dependency on the importer instance and neither on the dynamic plugin module. In other words, you don't need to keep the importer instance (or the plugin manager instance) around in order to have the `*Data` instances valid. Moreover, all @ref Corrade::Containers::Array instances returned through -@ref ImageData, @ref AnimationData and others are only allowed to have default -deleters --- this is to avoid potential dangling function pointer calls when -destructing such instances after the plugin module has been unloaded. +@ref ImageData, @ref AnimationData and @ref MeshData are only allowed to have +default deleters (or be non-owning instances created from +@ref Corrade::Containers::ArrayView) --- this is to avoid potential dangling +function pointer calls when destructing such instances after the plugin module +has been unloaded. The only exception are various `importerState()` functions @ref Trade-AbstractImporter-usage-state "described above", but in that case the diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 9f44d98d7b..0e5c7ebc81 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -106,6 +106,7 @@ struct AbstractImporterTest: TestSuite::Tester { void animationNotImplemented(); void animationNoFile(); void animationOutOfRange(); + void animationNonOwningDeleters(); void animationCustomDataDeleter(); void animationCustomTrackDeleter(); @@ -168,6 +169,7 @@ struct AbstractImporterTest: TestSuite::Tester { void meshNotImplemented(); void meshNoFile(); void meshOutOfRange(); + void meshNonOwningDeleters(); void meshCustomIndexDataDeleter(); void meshCustomVertexDataDeleter(); void meshCustomAttributesDeleter(); @@ -240,6 +242,7 @@ struct AbstractImporterTest: TestSuite::Tester { void image1DNoFile(); void image1DOutOfRange(); void image1DLevelOutOfRange(); + void image1DNonOwningDeleter(); void image1DCustomDeleter(); void image2D(); @@ -258,6 +261,7 @@ struct AbstractImporterTest: TestSuite::Tester { void image2DNoFile(); void image2DOutOfRange(); void image2DLevelOutOfRange(); + void image2DNonOwningDeleter(); void image2DCustomDeleter(); void image3D(); @@ -276,6 +280,7 @@ struct AbstractImporterTest: TestSuite::Tester { void image3DNoFile(); void image3DOutOfRange(); void image3DLevelOutOfRange(); + void image3DNonOwningDeleter(); void image3DCustomDeleter(); void importerState(); @@ -340,6 +345,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::animationNotImplemented, &AbstractImporterTest::animationNoFile, &AbstractImporterTest::animationOutOfRange, + &AbstractImporterTest::animationNonOwningDeleters, &AbstractImporterTest::animationCustomDataDeleter, &AbstractImporterTest::animationCustomTrackDeleter, @@ -402,6 +408,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::meshNotImplemented, &AbstractImporterTest::meshNoFile, &AbstractImporterTest::meshOutOfRange, + &AbstractImporterTest::meshNonOwningDeleters, &AbstractImporterTest::meshCustomIndexDataDeleter, &AbstractImporterTest::meshCustomVertexDataDeleter, &AbstractImporterTest::meshCustomAttributesDeleter, @@ -474,6 +481,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::image1DNoFile, &AbstractImporterTest::image1DOutOfRange, &AbstractImporterTest::image1DLevelOutOfRange, + &AbstractImporterTest::image1DNonOwningDeleter, &AbstractImporterTest::image1DCustomDeleter, &AbstractImporterTest::image2D, @@ -492,6 +500,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::image2DNoFile, &AbstractImporterTest::image2DOutOfRange, &AbstractImporterTest::image2DLevelOutOfRange, + &AbstractImporterTest::image2DNonOwningDeleter, &AbstractImporterTest::image2DCustomDeleter, &AbstractImporterTest::image3D, @@ -510,6 +519,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::image3DNoFile, &AbstractImporterTest::image3DOutOfRange, &AbstractImporterTest::image3DLevelOutOfRange, + &AbstractImporterTest::image3DNonOwningDeleter, &AbstractImporterTest::image3DCustomDeleter, &AbstractImporterTest::importerState, @@ -1368,6 +1378,28 @@ void AbstractImporterTest::animationOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animation(): index 8 out of range for 8 entries\n"); } +void AbstractImporterTest::animationNonOwningDeleters() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doAnimationCount() const override { return 1; } + Containers::Optional doAnimation(UnsignedInt) override { + return AnimationData{Containers::Array{data, 1, Implementation::nonOwnedArrayDeleter}, + Containers::Array{&track, 1, + reinterpret_cast(Implementation::nonOwnedArrayDeleter)}}; + } + + char data[1]; + AnimationTrackData track; + } importer; + + auto data = importer.animation(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(static_cast(data->data()), importer.data); +} + void AbstractImporterTest::animationCustomDataDeleter() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -2238,6 +2270,31 @@ void AbstractImporterTest::meshOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): index 8 out of range for 8 entries\n"); } +void AbstractImporterTest::meshNonOwningDeleters() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt) override { + return MeshData{MeshPrimitive::Triangles, + Containers::Array{indexData, 1, Implementation::nonOwnedArrayDeleter}, MeshIndexData{MeshIndexType::UnsignedByte, indexData}, + Containers::Array{nullptr, 0, Implementation::nonOwnedArrayDeleter}, + meshAttributeDataNonOwningArray(attributes)}; + } + + char indexData[1]; + MeshAttributeData attributes[1]{ + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr} + }; + } importer; + + auto data = importer.mesh(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(static_cast(data->indexData()), importer.indexData); +} + void AbstractImporterTest::meshCustomIndexDataDeleter() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -3263,6 +3320,25 @@ void AbstractImporterTest::image1DLevelOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image1D(): level 3 out of range for 3 entries\n"); } +void AbstractImporterTest::image1DNonOwningDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage1DCount() const override { return 1; } + Containers::Optional doImage1D(UnsignedInt, UnsignedInt) override { + return ImageData1D{PixelFormat::RGBA8Unorm, {}, Containers::Array{data, 1, Implementation::nonOwnedArrayDeleter}}; + } + + char data[1]; + } importer; + + auto data = importer.image1D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(static_cast(data->data()), importer.data); +} + void AbstractImporterTest::image1DCustomDeleter() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -3530,6 +3606,25 @@ void AbstractImporterTest::image2DLevelOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image2D(): level 3 out of range for 3 entries\n"); } +void AbstractImporterTest::image2DNonOwningDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage2DCount() const override { return 1; } + Containers::Optional doImage2D(UnsignedInt, UnsignedInt) override { + return ImageData2D{PixelFormat::RGBA8Unorm, {}, Containers::Array{data, 1, Implementation::nonOwnedArrayDeleter}}; + } + + char data[1]; + } importer; + + auto data = importer.image2D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(static_cast(data->data()), importer.data); +} + void AbstractImporterTest::image2DCustomDeleter() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -3798,6 +3893,25 @@ void AbstractImporterTest::image3DLevelOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image3D(): level 3 out of range for 3 entries\n"); } +void AbstractImporterTest::image3DNonOwningDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage3DCount() const override { return 1; } + Containers::Optional doImage3D(UnsignedInt, UnsignedInt) override { + return ImageData3D{PixelFormat::RGBA8Unorm, {}, Containers::Array{data, 1, Implementation::nonOwnedArrayDeleter}}; + } + + char data[1]; + } importer; + + auto data = importer.image3D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(static_cast(data->data()), importer.data); +} + void AbstractImporterTest::image3DCustomDeleter() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } From dd5acdd850e20d290817be7af0e66672074b33c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 19 Nov 2019 20:11:49 +0100 Subject: [PATCH 034/107] Trade: allow AnimationTrackData be created from a typed track. So users aren't force to specify everything on their own. It makes the test code a bit less painful. But just a bit. --- doc/changelog.dox | 3 + src/Magnum/Trade/AnimationData.h | 16 ++- src/Magnum/Trade/Test/AnimationDataTest.cpp | 111 ++++++++++++++------ 3 files changed, 96 insertions(+), 34 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index dedff6a26a..ff5043bc1b 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -280,6 +280,9 @@ See also: plugin if you specify `--importer raw:<format>`; and save raw imported data instead of going through a converter plugin if you specify `--converter raw` +- New convenience @ref Trade::AnimationTrackData constructor taking a + templated @ref Animation::TrackView type, autodetecting value and result + @ref Trade::AnimationTrackType out of it @subsection changelog-latest-buildsystem Build system diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index 0935c4813f..02f13ebf3c 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -231,7 +231,7 @@ class AnimationTrackData { /*implicit*/ AnimationTrackData() noexcept: _type{}, _resultType{}, _targetType{}, _target{}, _view{} {} /** - * @brief Constructor + * @brief Type-erased constructor * @param type Value type * @param resultType Result type * @param targetType Track target type @@ -247,6 +247,18 @@ class AnimationTrackData { */ /*implicit*/ AnimationTrackData(AnimationTrackType type, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{type}, _targetType{targetType}, _target{target}, _view{view} {} + /** + * @brief Constructor + * @param targetType Track target type + * @param target Track target + * @param view @ref Animation::TrackView instance + * @m_since_latest + * + * Detects @ref AnimationTrackType from @p view type and delegates to + * @ref AnimationTrackData(AnimationTrackType, AnimationTrackType, AnimationTrackTargetType, UnsignedInt, Animation::TrackViewStorage). + */ + template /*implicit*/ AnimationTrackData(AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackView view) noexcept; + private: friend AnimationData; @@ -611,6 +623,8 @@ namespace Implementation { } #endif +template inline AnimationTrackData::AnimationTrackData(AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackView view) noexcept: AnimationTrackData{Implementation::animationTypeFor(), Implementation::animationTypeFor(), targetType, target, view} {} + template const Animation::TrackView& AnimationData::track(UnsignedInt id) const { const Animation::TrackViewStorage& storage = track(id); CORRADE_ASSERT(Implementation::animationTypeFor() == _tracks[id]._type, "Trade::AnimationData::track(): improper type requested for" << _tracks[id]._type, (static_cast&>(storage))); diff --git a/src/Magnum/Trade/Test/AnimationDataTest.cpp b/src/Magnum/Trade/Test/AnimationDataTest.cpp index c61228eef4..f5072512b0 100644 --- a/src/Magnum/Trade/Test/AnimationDataTest.cpp +++ b/src/Magnum/Trade/Test/AnimationDataTest.cpp @@ -27,6 +27,7 @@ #include #include +#include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/Quaternion.h" #include "Magnum/Trade/AnimationData.h" @@ -35,6 +36,11 @@ namespace Magnum { namespace Trade { namespace Test { namespace { struct AnimationDataTest: TestSuite::Tester { explicit AnimationDataTest(); + void constructTrackData(); + void constructTrackDataResultType(); + void constructTrackDataTemplate(); + void constructTrackDataDefault(); + void construct(); void constructNotOwned(); void constructImplicitDuration(); @@ -46,8 +52,6 @@ struct AnimationDataTest: TestSuite::Tester { void constructCopy(); void constructMove(); - void constructTrackDataDefault(); - void mutableAccessNotAllowed(); void trackCustomResultType(); @@ -70,7 +74,12 @@ struct { }; AnimationDataTest::AnimationDataTest() { - addTests({&AnimationDataTest::construct, + addTests({&AnimationDataTest::constructTrackData, + &AnimationDataTest::constructTrackDataResultType, + &AnimationDataTest::constructTrackDataTemplate, + &AnimationDataTest::constructTrackDataDefault, + + &AnimationDataTest::construct, &AnimationDataTest::constructImplicitDuration, &AnimationDataTest::constructImplicitDurationEmpty}); @@ -84,8 +93,6 @@ AnimationDataTest::AnimationDataTest() { &AnimationDataTest::constructCopy, &AnimationDataTest::constructMove, - &AnimationDataTest::constructTrackDataDefault, - &AnimationDataTest::mutableAccessNotAllowed, &AnimationDataTest::trackCustomResultType, @@ -101,6 +108,61 @@ AnimationDataTest::AnimationDataTest() { using namespace Math::Literals; +void AnimationDataTest::constructTrackData() { + AnimationTrackData trackData{ + AnimationTrackType::Vector3, + AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + nullptr, + Animation::Interpolation::Linear, + animationInterpolatorFor(Animation::Interpolation::Linear)}}; + AnimationData data{nullptr, Containers::Array{Containers::InPlaceInit, {trackData}}}; + CORRADE_COMPARE(data.trackType(0), AnimationTrackType::Vector3); + CORRADE_COMPARE(data.trackResultType(0), AnimationTrackType::Vector3); + CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType::Translation3D); + CORRADE_COMPARE(data.trackTarget(0), 42); + CORRADE_COMPARE(data.track(0).interpolation(), Animation::Interpolation::Linear); +} + +void AnimationDataTest::constructTrackDataResultType() { + AnimationTrackData trackData{ + AnimationTrackType::CubicHermite3D, + AnimationTrackType::Vector3, + AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + nullptr, + Animation::Interpolation::Linear, + animationInterpolatorFor(Animation::Interpolation::Linear)}}; + AnimationData data{nullptr, Containers::Array{Containers::InPlaceInit, {trackData}}}; + CORRADE_COMPARE(data.trackType(0), AnimationTrackType::CubicHermite3D); + CORRADE_COMPARE(data.trackResultType(0), AnimationTrackType::Vector3); + CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType::Translation3D); + CORRADE_COMPARE(data.trackTarget(0), 42); + CORRADE_COMPARE(data.track(0).interpolation(), Animation::Interpolation::Linear); +} + +void AnimationDataTest::constructTrackDataTemplate() { + AnimationTrackData trackData{ + AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + nullptr, + Animation::Interpolation::Linear, + animationInterpolatorFor(Animation::Interpolation::Linear)}}; + AnimationData data{nullptr, Containers::Array{Containers::InPlaceInit, {trackData}}}; + CORRADE_COMPARE(data.trackType(0), AnimationTrackType::CubicHermite3D); + CORRADE_COMPARE(data.trackResultType(0), AnimationTrackType::Vector3); + CORRADE_COMPARE(data.trackTargetType(0), AnimationTrackTargetType::Translation3D); + CORRADE_COMPARE(data.trackTarget(0), 42); + CORRADE_COMPARE(data.track(0).interpolation(), Animation::Interpolation::Linear); +} + +void AnimationDataTest::constructTrackDataDefault() { + AnimationTrackData data; + /* no public accessors here, so nothing to check -- and such a track + shouldn't get added to AnimationData anyway */ + CORRADE_VERIFY(true); +} + void AnimationDataTest::construct() { /* Ain't the prettiest, but trust me: you won't do it like this in the plugins anyway */ @@ -117,15 +179,13 @@ void AnimationDataTest::construct() { const int state = 5; AnimationData data{std::move(buffer), Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Vector3, - AnimationTrackTargetType::Translation3D, 42, + {AnimationTrackTargetType::Translation3D, 42, Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, {view, &view[0].position, view.size(), sizeof(Data)}, Animation::Interpolation::Constant, animationInterpolatorFor(Animation::Interpolation::Constant)}}, - {AnimationTrackType::Quaternion, - AnimationTrackTargetType::Rotation3D, 1337, + {AnimationTrackTargetType::Rotation3D, 1337, Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, {view, &view[0].rotation, view.size(), sizeof(Data)}, @@ -189,14 +249,12 @@ void AnimationDataTest::constructImplicitDuration() { const int state = 5; AnimationData data{std::move(buffer), Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Bool, - AnimationTrackTargetType(129), 0, + {AnimationTrackTargetType(129), 0, Animation::TrackView{ {view, &view[0].time, 2, sizeof(Data)}, {view, &view[0].value, 2, sizeof(Data)}, Animation::Interpolation::Constant}}, - {AnimationTrackType::Bool, - AnimationTrackTargetType(130), 1, + {AnimationTrackTargetType(130), 1, Animation::TrackView{ {view, &view[2].time, 2, sizeof(Data)}, {view, &view[2].value, 2, sizeof(Data)}, @@ -259,8 +317,7 @@ void AnimationDataTest::constructNotOwned() { const int state = 5; AnimationData data{instanceData.dataFlags, keyframes, Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Vector3, - AnimationTrackTargetType::Translation3D, 42, + {AnimationTrackTargetType::Translation3D, 42, Animation::TrackView{ keyframes, Animation::Interpolation::Constant, @@ -308,8 +365,7 @@ void AnimationDataTest::constructImplicitDurationNotOwned() { const int state = 5; AnimationData data{instanceData.dataFlags, keyframes, Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Bool, - AnimationTrackTargetType(129), 0, + {AnimationTrackTargetType(129), 0, Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, }}, &state}; @@ -381,15 +437,13 @@ void AnimationDataTest::constructMove() { const int state = 5; AnimationData a{std::move(buffer), Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Vector3, - AnimationTrackTargetType::Translation3D, 42, + {AnimationTrackTargetType::Translation3D, 42, Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, {view, &view[0].position, view.size(), sizeof(Data)}, Animation::Interpolation::Constant, animationInterpolatorFor(Animation::Interpolation::Constant)}}, - {AnimationTrackType::Quaternion, - AnimationTrackTargetType::Rotation3D, 1337, + {AnimationTrackTargetType::Rotation3D, 1337, Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, {view, &view[0].rotation, view.size(), sizeof(Data)}, @@ -465,11 +519,6 @@ void AnimationDataTest::constructMove() { CORRADE_VERIFY(std::is_nothrow_move_assignable::value); } -void AnimationDataTest::constructTrackDataDefault() { - AnimationTrackData data; - CORRADE_VERIFY(true); /* no public accessors here, so nothing to check */ -} - void AnimationDataTest::mutableAccessNotAllowed() { const std::pair keyframes[] { {1.0f, true}, @@ -477,8 +526,7 @@ void AnimationDataTest::mutableAccessNotAllowed() { }; AnimationData data{{}, keyframes, Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Bool, - AnimationTrackTargetType(129), 0, + {AnimationTrackTargetType(129), 0, Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, }}}; CORRADE_COMPARE(data.dataFlags(), DataFlags{}); @@ -507,9 +555,7 @@ void AnimationDataTest::trackCustomResultType() { view[1] = {5.0f, {30, 60, 100}}; AnimationData data{std::move(buffer), Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Vector3i, - AnimationTrackType::Vector3, - AnimationTrackTargetType::Scaling3D, 0, + {AnimationTrackTargetType::Scaling3D, 0, Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, {view, &view[0].position, view.size(), sizeof(Data)}, @@ -577,8 +623,7 @@ void AnimationDataTest::release() { }; AnimationData data{{}, keyframes, Containers::Array{Containers::InPlaceInit, { - {AnimationTrackType::Bool, - AnimationTrackTargetType(129), 0, + {AnimationTrackTargetType(129), 0, Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, }}}; CORRADE_COMPARE(data.trackCount(), 1); From 6ddcc0b1aef7fe7fea4f5c0fbb6272e0c649ef8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 19 Nov 2019 20:52:27 +0100 Subject: [PATCH 035/107] Trade: make it possible to construct AnimationData from track init list. Easier to write. Need to take extra care with default deleters. --- doc/changelog.dox | 2 + src/Magnum/Trade/AnimationData.cpp | 9 ++++ src/Magnum/Trade/AnimationData.h | 28 +++++++++++ .../Trade/Test/AbstractImporterTest.cpp | 7 ++- src/Magnum/Trade/Test/AnimationDataTest.cpp | 46 +++++++++---------- 5 files changed, 66 insertions(+), 26 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index ff5043bc1b..b6fca800db 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -283,6 +283,8 @@ See also: - New convenience @ref Trade::AnimationTrackData constructor taking a templated @ref Animation::TrackView type, autodetecting value and result @ref Trade::AnimationTrackType out of it +- New convenience @ref Trade::AnimationData constructors taking an + @ref std::initializer_list of @ref Trade::AnimationTrackData @subsection changelog-latest-buildsystem Build system diff --git a/src/Magnum/Trade/AnimationData.cpp b/src/Magnum/Trade/AnimationData.cpp index dca88ba8d6..d315a0bef7 100644 --- a/src/Magnum/Trade/AnimationData.cpp +++ b/src/Magnum/Trade/AnimationData.cpp @@ -29,17 +29,22 @@ #include "Magnum/Math/Vector4.h" #include "Magnum/Math/Quaternion.h" +#include "Magnum/Trade/Implementation/arrayUtilities.h" namespace Magnum { namespace Trade { AnimationData::AnimationData(Containers::Array&& data, Containers::Array&& tracks, const Range1D& duration, const void* importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _duration{duration}, _data{std::move(data)}, _tracks{std::move(tracks)}, _importerState{importerState} {} +AnimationData::AnimationData(Containers::Array&& data, std::initializer_list tracks, const Range1D& duration, const void* importerState): AnimationData{std::move(data), Implementation::initializerListToArrayWithDefaultDeleter(tracks), duration, importerState} {} + AnimationData::AnimationData(const DataFlags dataFlags, const Containers::ArrayView data, Containers::Array&& tracks, const Range1D& duration, const void* importerState) noexcept: AnimationData{Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, std::move(tracks), duration, importerState} { CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), "Trade::AnimationData: can't construct a non-owned instance with" << dataFlags, ); _dataFlags = dataFlags; } +AnimationData::AnimationData(const DataFlags dataFlags, const Containers::ArrayView data, std::initializer_list tracks, const Range1D& duration, const void* importerState): AnimationData{dataFlags, data, Implementation::initializerListToArrayWithDefaultDeleter(tracks), duration, importerState} {} + AnimationData::AnimationData(Containers::Array&& data, Containers::Array&& tracks, const void* importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _data{std::move(data)}, _tracks{std::move(tracks)}, _importerState{importerState} { if(!_tracks.empty()) { /* Reset duration to duration of the first track so it properly support @@ -50,12 +55,16 @@ AnimationData::AnimationData(Containers::Array&& data, Containers::Array&& data, std::initializer_list tracks, const void* importerState): AnimationData{std::move(data), Implementation::initializerListToArrayWithDefaultDeleter(tracks), importerState} {} + AnimationData::AnimationData(const DataFlags dataFlags, const Containers::ArrayView data, Containers::Array&& tracks, const void* importerState) noexcept: AnimationData{Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, std::move(tracks), importerState} { CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), "Trade::AnimationData: can't construct a non-owned instance with" << dataFlags, ); _dataFlags = dataFlags; } +AnimationData::AnimationData(const DataFlags dataFlags, const Containers::ArrayView data, std::initializer_list tracks, const void* importerState): AnimationData{dataFlags, data, Implementation::initializerListToArrayWithDefaultDeleter(tracks), importerState} {} + AnimationData::~AnimationData() = default; AnimationData::AnimationData(AnimationData&&) noexcept = default; diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index 02f13ebf3c..d8bfd56881 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -327,6 +327,13 @@ class MAGNUM_TRADE_EXPORT AnimationData { */ explicit AnimationData(Containers::Array&& data, Containers::Array&& tracks, const void* importerState = nullptr) noexcept; + /** + * @overload + * @m_since_latest + */ + /* Not noexcept because allocation happens inside */ + explicit AnimationData(Containers::Array&& data, std::initializer_list tracks, const void* importerState = nullptr); + /** * @brief Construct a non-owned animation data * @param dataFlags Data flags @@ -344,6 +351,13 @@ class MAGNUM_TRADE_EXPORT AnimationData { */ explicit AnimationData(DataFlags dataFlags, Containers::ArrayView data, Containers::Array&& tracks, const void* importerState = nullptr) noexcept; + /** + * @overload + * @m_since_latest + */ + /* Not noexcept because allocation happens inside */ + explicit AnimationData(DataFlags dataFlags, Containers::ArrayView data, std::initializer_list tracks, const void* importerState = nullptr); + /** * @brief Construct an animation data with explicit duration * @param data Buffer containing all keyframe data for this @@ -362,6 +376,13 @@ class MAGNUM_TRADE_EXPORT AnimationData { */ explicit AnimationData(Containers::Array&& data, Containers::Array&& tracks, const Range1D& duration, const void* importerState = nullptr) noexcept; + /** + * @overload + * @m_since_latest + */ + /* Not noexcept because allocation happens inside */ + explicit AnimationData(Containers::Array&& data, std::initializer_list tracks, const Range1D& duration, const void* importerState = nullptr); + /** * @brief Construct a non-owned animation data with explicit duration * @param dataFlags Data flags @@ -380,6 +401,13 @@ class MAGNUM_TRADE_EXPORT AnimationData { */ explicit AnimationData(DataFlags dataFlags, Containers::ArrayView data, Containers::Array&& tracks, const Range1D& duration, const void* importerState = nullptr) noexcept; + /** + * @overload + * @m_since_latest + */ + /* Not noexcept because allocation happens inside */ + explicit AnimationData(DataFlags dataFlags, Containers::ArrayView data, std::initializer_list tracks, const Range1D& duration, const void* importerState = nullptr); + ~AnimationData(); /** @brief Copying is not allowed */ diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 0e5c7ebc81..f714854ceb 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -1228,7 +1228,12 @@ void AbstractImporterTest::animation() { else return {}; } Containers::Optional doAnimation(UnsignedInt id) override { - if(id == 7) return AnimationData{{}, {}, &state}; + /* Verify that initializer list is converted to an array with + the default deleter and not something disallowed */ + if(id == 7) return AnimationData{nullptr, { + {AnimationTrackType::Vector3, + AnimationTrackTargetType::Scaling3D, 0, {}} + }, &state}; else return AnimationData{{}, {}}; } } importer; diff --git a/src/Magnum/Trade/Test/AnimationDataTest.cpp b/src/Magnum/Trade/Test/AnimationDataTest.cpp index f5072512b0..96ae3c3e55 100644 --- a/src/Magnum/Trade/Test/AnimationDataTest.cpp +++ b/src/Magnum/Trade/Test/AnimationDataTest.cpp @@ -164,8 +164,6 @@ void AnimationDataTest::constructTrackDataDefault() { } void AnimationDataTest::construct() { - /* Ain't the prettiest, but trust me: you won't do it like this in the - plugins anyway */ struct Data { Float time; Vector3 position; @@ -178,7 +176,7 @@ void AnimationDataTest::construct() { view[2] = {7.5f, {1.0f, 0.3f, 2.1f}, Quaternion{}}; const int state = 5; - AnimationData data{std::move(buffer), Containers::Array{Containers::InPlaceInit, { + AnimationData data{std::move(buffer), { {AnimationTrackTargetType::Translation3D, 42, Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, @@ -191,7 +189,7 @@ void AnimationDataTest::construct() { {view, &view[0].rotation, view.size(), sizeof(Data)}, Animation::Interpolation::Linear, animationInterpolatorFor(Animation::Interpolation::Linear)}} - }}, {-1.0f, 7.0f}, &state}; + }, {-1.0f, 7.0f}, &state}; CORRADE_COMPARE(data.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.duration(), (Range1D{-1.0f, 7.0f})); @@ -234,8 +232,6 @@ void AnimationDataTest::construct() { } void AnimationDataTest::constructImplicitDuration() { - /* Ain't the prettiest, but trust me: you won't do it like this in the - plugins anyway */ struct Data { Float time; bool value; @@ -248,7 +244,7 @@ void AnimationDataTest::constructImplicitDuration() { view[3] = {7.0f, false}; const int state = 5; - AnimationData data{std::move(buffer), Containers::Array{Containers::InPlaceInit, { + AnimationData data{std::move(buffer), { {AnimationTrackTargetType(129), 0, Animation::TrackView{ {view, &view[0].time, 2, sizeof(Data)}, @@ -259,7 +255,7 @@ void AnimationDataTest::constructImplicitDuration() { {view, &view[2].time, 2, sizeof(Data)}, {view, &view[2].value, 2, sizeof(Data)}, Animation::Interpolation::Linear}} - }}, &state}; + }, &state}; CORRADE_COMPARE(data.dataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.duration(), (Range1D{1.0f, 7.0f})); @@ -316,13 +312,13 @@ void AnimationDataTest::constructNotOwned() { }; const int state = 5; - AnimationData data{instanceData.dataFlags, keyframes, Containers::Array{Containers::InPlaceInit, { + AnimationData data{instanceData.dataFlags, keyframes, { {AnimationTrackTargetType::Translation3D, 42, Animation::TrackView{ keyframes, Animation::Interpolation::Constant, animationInterpolatorFor(Animation::Interpolation::Constant)}} - }}, {-1.0f, 7.0f}, &state}; + }, {-1.0f, 7.0f}, &state}; CORRADE_COMPARE(data.dataFlags(), instanceData.dataFlags); CORRADE_COMPARE(data.duration(), (Range1D{-1.0f, 7.0f})); @@ -364,10 +360,10 @@ void AnimationDataTest::constructImplicitDurationNotOwned() { }; const int state = 5; - AnimationData data{instanceData.dataFlags, keyframes, Containers::Array{Containers::InPlaceInit, { + AnimationData data{instanceData.dataFlags, keyframes, { {AnimationTrackTargetType(129), 0, Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, - }}, &state}; + }, &state}; CORRADE_COMPARE(data.dataFlags(), instanceData.dataFlags); CORRADE_COMPARE(data.duration(), (Range1D{1.0f, 5.0f})); @@ -436,7 +432,7 @@ void AnimationDataTest::constructMove() { view[2] = {7.5f, {1.0f, 0.3f, 2.1f}, Quaternion{}}; const int state = 5; - AnimationData a{std::move(buffer), Containers::Array{Containers::InPlaceInit, { + AnimationData a{std::move(buffer), { {AnimationTrackTargetType::Translation3D, 42, Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, @@ -449,7 +445,7 @@ void AnimationDataTest::constructMove() { {view, &view[0].rotation, view.size(), sizeof(Data)}, Animation::Interpolation::Linear, animationInterpolatorFor(Animation::Interpolation::Linear)}} - }}, {-1.0f, 7.0f}, &state}; + }, {-1.0f, 7.0f}, &state}; AnimationData b{std::move(a)}; @@ -525,10 +521,10 @@ void AnimationDataTest::mutableAccessNotAllowed() { {5.0f, false} }; - AnimationData data{{}, keyframes, Containers::Array{Containers::InPlaceInit, { + AnimationData data{{}, keyframes, { {AnimationTrackTargetType(129), 0, Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, - }}}; + }}; CORRADE_COMPARE(data.dataFlags(), DataFlags{}); std::ostringstream out; @@ -554,15 +550,15 @@ void AnimationDataTest::trackCustomResultType() { view[0] = {0.0f, {300, 100, 10}}; view[1] = {5.0f, {30, 60, 100}}; - AnimationData data{std::move(buffer), Containers::Array{Containers::InPlaceInit, { + AnimationData data{std::move(buffer), { {AnimationTrackTargetType::Scaling3D, 0, Animation::TrackView{ {view, &view[0].time, view.size(), sizeof(Data)}, {view, &view[0].position, view.size(), sizeof(Data)}, [](const Vector3i& a, const Vector3i& b, Float t) -> Vector3 { return Math::lerp(Vector3{a}*0.01f, Vector3{b}*0.01f, t); - }}}} - }}; + }}} + }}; CORRADE_COMPARE((data.track(0).at(2.5f)), (Vector3{1.65f, 0.8f, 0.55f})); } @@ -590,11 +586,11 @@ void AnimationDataTest::trackWrongType() { std::ostringstream out; Error redirectError{&out}; - AnimationData data{nullptr, Containers::Array{Containers::InPlaceInit, { + AnimationData data{nullptr, { {AnimationTrackType::Vector3i, AnimationTrackType::Vector3, AnimationTrackTargetType::Scaling3D, 0, {}} - }}}; + }}; data.track(0); @@ -605,11 +601,11 @@ void AnimationDataTest::trackWrongResultType() { std::ostringstream out; Error redirectError{&out}; - AnimationData data{nullptr, Containers::Array{Containers::InPlaceInit, { + AnimationData data{nullptr, { {AnimationTrackType::Vector3i, AnimationTrackType::Vector3, AnimationTrackTargetType::Scaling3D, 0, {}} - }}}; + }}; data.track(0); @@ -622,10 +618,10 @@ void AnimationDataTest::release() { {5.0f, false} }; - AnimationData data{{}, keyframes, Containers::Array{Containers::InPlaceInit, { + AnimationData data{{}, keyframes, { {AnimationTrackTargetType(129), 0, Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, - }}}; + }}; CORRADE_COMPARE(data.trackCount(), 1); Containers::Array released = data.release(); From 8cd75087ed6d6b1f7e9fe7b814af7d380b3db2cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 19 Feb 2020 13:52:37 +0100 Subject: [PATCH 036/107] Trade: make MeshIndexData constexpr. First step towards an ability to expose data compiled into an executable through MeshData without having to allocate anything. --- src/Magnum/Trade/MeshData.cpp | 9 +++++---- src/Magnum/Trade/MeshData.h | 19 +++++++++++++++---- src/Magnum/Trade/Test/MeshDataTest.cpp | 22 ++++++++++++++++++++++ 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 79c693610f..ed68f54430 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -32,9 +32,10 @@ namespace Magnum { namespace Trade { -MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayView data) noexcept: type{type}, data{reinterpret_cast&>(data)} { - CORRADE_ASSERT(!data.empty(), - "Trade::MeshIndexData: index array can't be empty, create a non-indexed mesh instead", ); +MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayView data) noexcept: MeshIndexData{type, data, nullptr} { + /* Yes, this calls into a constexpr function defined in the header -- + because I feel that makes more sense than duplicating the full assert + logic */ CORRADE_ASSERT(data.size()%meshIndexTypeSize(type) == 0, "Trade::MeshIndexData: view size" << data.size() << "does not correspond to" << type, ); } @@ -63,7 +64,7 @@ Containers::Array meshAttributeDataNonOwningArray(const Conta return Containers::Array{const_cast(view.data()), view.size(), reinterpret_cast(Trade::Implementation::nonOwnedArrayDeleter)}; } -MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: _indexType{indices.type}, _primitive{primitive}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{indices.data} { +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: _indexType{indices.type}, _primitive{primitive}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{Containers::arrayCast(indices.data)} { /* Save vertex count. It's a strided array view, so the size is not depending on type. */ if(_attributes.empty()) { diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index e5ac1865b7..1df7d111c1 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -177,19 +177,27 @@ class MAGNUM_TRADE_EXPORT MeshIndexData { explicit MeshIndexData(MeshIndexType type, Containers::ArrayView data) noexcept; /** @brief Construct with unsigned byte indices */ - explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedByte, data} {} + constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedByte, data, nullptr} {} /** @brief Construct with unsigned short indices */ - explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedShort, data} {} + constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedShort, data, nullptr} {} /** @brief Construct with unsigned int indices */ - explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedInt, data} {} + constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedInt, data, nullptr} {} private: + /* Contains an assert common for all constexpr constructor, nullptr_t + to disambiguate from the public constructor of the same signature -- + can't delegate into that one, because it checks against + meshIndexTypeSize() that's not constexpr, and since we come from a + template, we don't need that check anyway */ + constexpr explicit MeshIndexData(MeshIndexType type, Containers::ArrayView data, std::nullptr_t); + /* Not prefixed with _ because we use them like public in MeshData */ friend MeshData; MeshIndexType type; - Containers::ArrayView data; + /* Void so the constructors can be constexpr */ + Containers::ArrayView data; }; /** @@ -1016,6 +1024,9 @@ namespace Implementation { } #endif +constexpr MeshIndexData::MeshIndexData(MeshIndexType type, Containers::ArrayView data, std::nullptr_t): + type{type}, data{(CORRADE_CONSTEXPR_ASSERT(!data.empty(), "Trade::MeshIndexData: index array can't be empty, create a non-indexed mesh instead"), data)} {} + template MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), Containers::arrayCast(data)} {} template Containers::ArrayView MeshData::indices() const { diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 7f6a0caac4..bce1be59b5 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -250,6 +250,10 @@ void MeshDataTest::debugAttributeName() { using namespace Math::Literals; +constexpr UnsignedByte IndexBytes[]{25, 132, 3}; +constexpr UnsignedShort IndexShorts[]{2575, 13224, 3}; +constexpr UnsignedInt IndexInts[]{2110122, 132257, 3}; + void MeshDataTest::constructIndex() { { Containers::Array indexData{3*1}; @@ -260,6 +264,12 @@ void MeshDataTest::constructIndex() { CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedByte); CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); CORRADE_COMPARE(data.indexCount(), 3); + + constexpr MeshIndexData cindices{IndexBytes}; + MeshData cdata{MeshPrimitive::Points, {}, IndexBytes, cindices}; + CORRADE_COMPARE(cdata.indexType(), MeshIndexType::UnsignedByte); + CORRADE_COMPARE(static_cast(cdata.indices().data()), IndexBytes); + CORRADE_COMPARE(data.indexCount(), 3); } { Containers::Array indexData{3*2}; auto indexView = Containers::arrayCast(indexData); @@ -269,6 +279,12 @@ void MeshDataTest::constructIndex() { CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); CORRADE_COMPARE(data.indexCount(), 3); + + constexpr MeshIndexData cindices{IndexShorts}; + MeshData cdata{MeshPrimitive::Points, {}, IndexShorts, cindices}; + CORRADE_COMPARE(cdata.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(static_cast(cdata.indices().data()), IndexShorts); + CORRADE_COMPARE(data.indexCount(), 3); } { Containers::Array indexData{3*4}; auto indexView = Containers::arrayCast(indexData); @@ -278,6 +294,12 @@ void MeshDataTest::constructIndex() { CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedInt); CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); CORRADE_COMPARE(data.indexCount(), 3); + + constexpr MeshIndexData cindices{IndexInts}; + MeshData cdata{MeshPrimitive::Points, {}, IndexInts, cindices}; + CORRADE_COMPARE(cdata.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(static_cast(cdata.indices().data()), IndexInts); + CORRADE_COMPARE(data.indexCount(), 3); } } From c74b4c6b90ae0e62230c492948b82c59d4e8cbaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 19 Feb 2020 14:01:15 +0100 Subject: [PATCH 037/107] Trade: make MeshAttributeData constexpr. This makes it possible to have fully allocation-less MeshData, with statically defined indices and attributes. Only the final MeshData construction needs to be done at runtime because Array is not constexpr, but that isn't anything heavy anyway. --- src/Magnum/Trade/MeshData.cpp | 27 ++++++++--------------- src/Magnum/Trade/MeshData.h | 30 ++++++++++++++++++++------ src/Magnum/Trade/Test/MeshDataTest.cpp | 13 +++++++++++ 3 files changed, 46 insertions(+), 24 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index ed68f54430..3841e56a6c 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -40,23 +40,13 @@ MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayVi "Trade::MeshIndexData: view size" << data.size() << "does not correspond to" << type, ); } -MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data) noexcept: name{name}, format{format}, data{data} { +MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, format, data, nullptr} { + /* Yes, this calls into a constexpr function defined in the header -- + because I feel that makes more sense than duplicating the full assert + logic */ /** @todo support zero / negative stride? would be hard to transfer to GL */ CORRADE_ASSERT(data.empty() || std::ptrdiff_t(vertexFormatSize(format)) <= data.stride(), "Trade::MeshAttributeData: view stride" << data.stride() << "is not large enough to contain" << format, ); - CORRADE_ASSERT( - (name == MeshAttribute::Position && - (format == VertexFormat::Vector2 || - format == VertexFormat::Vector3)) || - (name == MeshAttribute::Normal && - (format == VertexFormat::Vector3)) || - (name == MeshAttribute::Color && - (format == VertexFormat::Vector3 || - format == VertexFormat::Vector4)) || - (name == MeshAttribute::TextureCoordinates && - (format == VertexFormat::Vector2)) || - isMeshAttributeCustom(name) /* can be any format */, - "Trade::MeshAttributeData:" << format << "is not a valid format for" << name, ); } Containers::Array meshAttributeDataNonOwningArray(const Containers::ArrayView view) { @@ -88,10 +78,11 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde constructors */ for(std::size_t i = 0; i != _attributes.size(); ++i) { const MeshAttributeData& attribute = _attributes[i]; - CORRADE_ASSERT(attribute.data.size() == _vertexCount, - "Trade::MeshData: attribute" << i << "has" << attribute.data.size() << "vertices but" << _vertexCount << "expected", ); - CORRADE_ASSERT(attribute.data.empty() || (&attribute.data.front() >= _vertexData.begin() && &attribute.data.back() + vertexFormatSize(attribute.format) <= _vertexData.end()), - "Trade::MeshData: attribute" << i << "[" << Debug::nospace << static_cast(&attribute.data.front()) << Debug::nospace << ":" << Debug::nospace << static_cast(&attribute.data.back() + vertexFormatSize(attribute.format)) << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); + const Containers::StridedArrayView1D data = Containers::arrayCast(attribute.data); + CORRADE_ASSERT(data.size() == _vertexCount, + "Trade::MeshData: attribute" << i << "has" << data.size() << "vertices but" << _vertexCount << "expected", ); + CORRADE_ASSERT(data.empty() || (&data.front() >= _vertexData.begin() && &data.back() + vertexFormatSize(attribute.format) <= _vertexData.end()), + "Trade::MeshData: attribute" << i << "[" << Debug::nospace << static_cast(&data.front()) << Debug::nospace << ":" << Debug::nospace << static_cast(&data.back() + vertexFormatSize(attribute.format)) << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); } #endif } diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 1df7d111c1..b69ca987ef 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -216,7 +216,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * initialization of the attribute array for @ref MeshData, expected to * be replaced with concrete values later. */ - explicit MeshAttributeData() noexcept: name{}, format{}, data{} {} + constexpr explicit MeshAttributeData() noexcept: name{}, format{}, data{} {} /** * @brief Type-erased constructor @@ -227,7 +227,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * Expects that @p data stride is large enough to fit @p type and that * @p type corresponds to @p name. */ - explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data) noexcept; + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data) noexcept; /** * @brief Constructor @@ -237,18 +237,20 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * Detects @ref VertexFormat based on @p T and calls * @ref MeshAttributeData(MeshAttribute, VertexFormat, const Containers::StridedArrayView1D&). */ - template explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept; + template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept; /** @overload */ - template explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {} + template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {} private: + constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept; + /* Not prefixed with _ because we use them like public in MeshData */ friend MeshData; MeshAttribute name; /* Here's some room for flags */ VertexFormat format; - Containers::StridedArrayView1D data; + Containers::StridedArrayView1D data; }; /** @relatesalso MeshAttributeData @@ -1027,7 +1029,23 @@ namespace Implementation { constexpr MeshIndexData::MeshIndexData(MeshIndexType type, Containers::ArrayView data, std::nullptr_t): type{type}, data{(CORRADE_CONSTEXPR_ASSERT(!data.empty(), "Trade::MeshIndexData: index array can't be empty, create a non-indexed mesh instead"), data)} {} -template MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), Containers::arrayCast(data)} {} +constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept: + name{name}, format{format}, data{(CORRADE_CONSTEXPR_ASSERT( + (name == MeshAttribute::Position && + (format == VertexFormat::Vector2 || + format == VertexFormat::Vector3)) || + (name == MeshAttribute::Normal && + (format == VertexFormat::Vector3)) || + (name == MeshAttribute::Color && + (format == VertexFormat::Vector3 || + format == VertexFormat::Vector4)) || + (name == MeshAttribute::TextureCoordinates && + (format == VertexFormat::Vector2)) || + isMeshAttributeCustom(name) /* can be any format */, + "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), data)} + {} + +template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), data, nullptr} {} template Containers::ArrayView MeshData::indices() const { CORRADE_ASSERT(isIndexed(), diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index bce1be59b5..3741e020b8 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -330,6 +330,12 @@ void MeshDataTest::constructIndexTypeErasedWrongSize() { CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: view size 6 does not correspond to MeshIndexType::UnsignedInt\n"); } +constexpr Vector2 Positions[] { + {1.2f, 0.2f}, + {2.2f, 1.1f}, + {-0.2f, 7.2f} +}; + void MeshDataTest::constructAttribute() { Containers::Array positionData{3*sizeof(Vector2)}; auto positionView = Containers::arrayCast(positionData); @@ -340,6 +346,13 @@ void MeshDataTest::constructAttribute() { CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); CORRADE_COMPARE(static_cast(data.attribute(0).data()), positionView.data()); + + constexpr MeshAttributeData cpositions{MeshAttribute::Position, Containers::arrayView(Positions)}; + MeshData cdata{MeshPrimitive::Points, {}, Positions, {cpositions}}; + CORRADE_COMPARE(cdata.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(cdata.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(static_cast(cdata.attribute(0).data()), + Positions); } void MeshDataTest::constructAttributeCustom() { From 93e6dc2c544ac1945de47dfb9a5b545b7c919f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 21 Nov 2019 21:51:47 +0100 Subject: [PATCH 038/107] Trade: make AnimationTrackData constructors explicit. There's a ton of parameters and it's just unreadable without. --- doc/changelog.dox | 2 + src/Magnum/Trade/AnimationData.h | 8 +- .../Trade/Test/AbstractImporterTest.cpp | 4 +- src/Magnum/Trade/Test/AnimationDataTest.cpp | 116 +++++++++--------- 4 files changed, 66 insertions(+), 64 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index b6fca800db..0e293d4b0d 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -412,6 +412,8 @@ See also: - The @ref Magnum/Math/FunctionsBatch.h header is no longer included from @ref Magnum/Math/Functions.h for backwards compatibility in order to speed up compile times. +- @ref Trade::AnimationTrackData constructors are now explicit as that + enforces better readability in long initializer expressions - Non-const @ref Trade::ImageData::data() and @ref Trade::ImageData::pixels() were renamed to @ref Trade::ImageData::mutableData() and @ref Trade::ImageData::mutablePixels() to follow the new diff --git a/src/Magnum/Trade/AnimationData.h b/src/Magnum/Trade/AnimationData.h index d8bfd56881..50f7b51ded 100644 --- a/src/Magnum/Trade/AnimationData.h +++ b/src/Magnum/Trade/AnimationData.h @@ -228,7 +228,7 @@ class AnimationTrackData { * initialization of the track array for @ref AnimationData, expected * to be replaced with concrete values later. */ - /*implicit*/ AnimationTrackData() noexcept: _type{}, _resultType{}, _targetType{}, _target{}, _view{} {} + explicit AnimationTrackData() noexcept: _type{}, _resultType{}, _targetType{}, _target{}, _view{} {} /** * @brief Type-erased constructor @@ -238,14 +238,14 @@ class AnimationTrackData { * @param target Track target * @param view Type-erased @ref Animation::TrackView instance */ - /*implicit*/ AnimationTrackData(AnimationTrackType type, AnimationTrackType resultType, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{resultType}, _targetType{targetType}, _target{target}, _view{view} {} + explicit AnimationTrackData(AnimationTrackType type, AnimationTrackType resultType, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{resultType}, _targetType{targetType}, _target{target}, _view{view} {} /** @overload * * Equivalent to the above with @p type used as both value type and * result type. */ - /*implicit*/ AnimationTrackData(AnimationTrackType type, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{type}, _targetType{targetType}, _target{target}, _view{view} {} + explicit AnimationTrackData(AnimationTrackType type, AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackViewStorage view) noexcept: _type{type}, _resultType{type}, _targetType{targetType}, _target{target}, _view{view} {} /** * @brief Constructor @@ -257,7 +257,7 @@ class AnimationTrackData { * Detects @ref AnimationTrackType from @p view type and delegates to * @ref AnimationTrackData(AnimationTrackType, AnimationTrackType, AnimationTrackTargetType, UnsignedInt, Animation::TrackViewStorage). */ - template /*implicit*/ AnimationTrackData(AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackView view) noexcept; + template explicit AnimationTrackData(AnimationTrackTargetType targetType, UnsignedInt target, Animation::TrackView view) noexcept; private: friend AnimationData; diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index f714854ceb..f3b97bade0 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -1231,8 +1231,8 @@ void AbstractImporterTest::animation() { /* Verify that initializer list is converted to an array with the default deleter and not something disallowed */ if(id == 7) return AnimationData{nullptr, { - {AnimationTrackType::Vector3, - AnimationTrackTargetType::Scaling3D, 0, {}} + AnimationTrackData{AnimationTrackType::Vector3, + AnimationTrackTargetType::Scaling3D, 0, {}} }, &state}; else return AnimationData{{}, {}}; } diff --git a/src/Magnum/Trade/Test/AnimationDataTest.cpp b/src/Magnum/Trade/Test/AnimationDataTest.cpp index 96ae3c3e55..6cf570c5ef 100644 --- a/src/Magnum/Trade/Test/AnimationDataTest.cpp +++ b/src/Magnum/Trade/Test/AnimationDataTest.cpp @@ -177,18 +177,18 @@ void AnimationDataTest::construct() { const int state = 5; AnimationData data{std::move(buffer), { - {AnimationTrackTargetType::Translation3D, 42, - Animation::TrackView{ - {view, &view[0].time, view.size(), sizeof(Data)}, - {view, &view[0].position, view.size(), sizeof(Data)}, - Animation::Interpolation::Constant, - animationInterpolatorFor(Animation::Interpolation::Constant)}}, - {AnimationTrackTargetType::Rotation3D, 1337, - Animation::TrackView{ - {view, &view[0].time, view.size(), sizeof(Data)}, - {view, &view[0].rotation, view.size(), sizeof(Data)}, - Animation::Interpolation::Linear, - animationInterpolatorFor(Animation::Interpolation::Linear)}} + AnimationTrackData{AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + {view, &view[0].time, view.size(), sizeof(Data)}, + {view, &view[0].position, view.size(), sizeof(Data)}, + Animation::Interpolation::Constant, + animationInterpolatorFor(Animation::Interpolation::Constant)}}, + AnimationTrackData{AnimationTrackTargetType::Rotation3D, 1337, + Animation::TrackView{ + {view, &view[0].time, view.size(), sizeof(Data)}, + {view, &view[0].rotation, view.size(), sizeof(Data)}, + Animation::Interpolation::Linear, + animationInterpolatorFor(Animation::Interpolation::Linear)}} }, {-1.0f, 7.0f}, &state}; CORRADE_COMPARE(data.dataFlags(), DataFlag::Owned|DataFlag::Mutable); @@ -245,16 +245,16 @@ void AnimationDataTest::constructImplicitDuration() { const int state = 5; AnimationData data{std::move(buffer), { - {AnimationTrackTargetType(129), 0, - Animation::TrackView{ - {view, &view[0].time, 2, sizeof(Data)}, - {view, &view[0].value, 2, sizeof(Data)}, - Animation::Interpolation::Constant}}, - {AnimationTrackTargetType(130), 1, - Animation::TrackView{ - {view, &view[2].time, 2, sizeof(Data)}, - {view, &view[2].value, 2, sizeof(Data)}, - Animation::Interpolation::Linear}} + AnimationTrackData{AnimationTrackTargetType(129), 0, + Animation::TrackView{ + {view, &view[0].time, 2, sizeof(Data)}, + {view, &view[0].value, 2, sizeof(Data)}, + Animation::Interpolation::Constant}}, + AnimationTrackData{AnimationTrackTargetType(130), 1, + Animation::TrackView{ + {view, &view[2].time, 2, sizeof(Data)}, + {view, &view[2].value, 2, sizeof(Data)}, + Animation::Interpolation::Linear}} }, &state}; CORRADE_COMPARE(data.dataFlags(), DataFlag::Owned|DataFlag::Mutable); @@ -313,11 +313,11 @@ void AnimationDataTest::constructNotOwned() { const int state = 5; AnimationData data{instanceData.dataFlags, keyframes, { - {AnimationTrackTargetType::Translation3D, 42, - Animation::TrackView{ - keyframes, - Animation::Interpolation::Constant, - animationInterpolatorFor(Animation::Interpolation::Constant)}} + AnimationTrackData{AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + keyframes, + Animation::Interpolation::Constant, + animationInterpolatorFor(Animation::Interpolation::Constant)}} }, {-1.0f, 7.0f}, &state}; CORRADE_COMPARE(data.dataFlags(), instanceData.dataFlags); @@ -361,8 +361,8 @@ void AnimationDataTest::constructImplicitDurationNotOwned() { const int state = 5; AnimationData data{instanceData.dataFlags, keyframes, { - {AnimationTrackTargetType(129), 0, - Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, + AnimationTrackData{AnimationTrackTargetType(129), 0, + Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, }, &state}; CORRADE_COMPARE(data.dataFlags(), instanceData.dataFlags); @@ -433,18 +433,18 @@ void AnimationDataTest::constructMove() { const int state = 5; AnimationData a{std::move(buffer), { - {AnimationTrackTargetType::Translation3D, 42, - Animation::TrackView{ - {view, &view[0].time, view.size(), sizeof(Data)}, - {view, &view[0].position, view.size(), sizeof(Data)}, - Animation::Interpolation::Constant, - animationInterpolatorFor(Animation::Interpolation::Constant)}}, - {AnimationTrackTargetType::Rotation3D, 1337, - Animation::TrackView{ - {view, &view[0].time, view.size(), sizeof(Data)}, - {view, &view[0].rotation, view.size(), sizeof(Data)}, - Animation::Interpolation::Linear, - animationInterpolatorFor(Animation::Interpolation::Linear)}} + AnimationTrackData{AnimationTrackTargetType::Translation3D, 42, + Animation::TrackView{ + {view, &view[0].time, view.size(), sizeof(Data)}, + {view, &view[0].position, view.size(), sizeof(Data)}, + Animation::Interpolation::Constant, + animationInterpolatorFor(Animation::Interpolation::Constant)}}, + AnimationTrackData{AnimationTrackTargetType::Rotation3D, 1337, + Animation::TrackView{ + {view, &view[0].time, view.size(), sizeof(Data)}, + {view, &view[0].rotation, view.size(), sizeof(Data)}, + Animation::Interpolation::Linear, + animationInterpolatorFor(Animation::Interpolation::Linear)}} }, {-1.0f, 7.0f}, &state}; AnimationData b{std::move(a)}; @@ -522,8 +522,8 @@ void AnimationDataTest::mutableAccessNotAllowed() { }; AnimationData data{{}, keyframes, { - {AnimationTrackTargetType(129), 0, - Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, + AnimationTrackData{AnimationTrackTargetType(129), 0, + Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, }}; CORRADE_COMPARE(data.dataFlags(), DataFlags{}); @@ -551,13 +551,13 @@ void AnimationDataTest::trackCustomResultType() { view[1] = {5.0f, {30, 60, 100}}; AnimationData data{std::move(buffer), { - {AnimationTrackTargetType::Scaling3D, 0, - Animation::TrackView{ - {view, &view[0].time, view.size(), sizeof(Data)}, - {view, &view[0].position, view.size(), sizeof(Data)}, - [](const Vector3i& a, const Vector3i& b, Float t) -> Vector3 { - return Math::lerp(Vector3{a}*0.01f, Vector3{b}*0.01f, t); - }}} + AnimationTrackData{AnimationTrackTargetType::Scaling3D, 0, + Animation::TrackView{ + {view, &view[0].time, view.size(), sizeof(Data)}, + {view, &view[0].position, view.size(), sizeof(Data)}, + [](const Vector3i& a, const Vector3i& b, Float t) -> Vector3 { + return Math::lerp(Vector3{a}*0.01f, Vector3{b}*0.01f, t); + }}} }}; CORRADE_COMPARE((data.track(0).at(2.5f)), (Vector3{1.65f, 0.8f, 0.55f})); @@ -587,9 +587,9 @@ void AnimationDataTest::trackWrongType() { Error redirectError{&out}; AnimationData data{nullptr, { - {AnimationTrackType::Vector3i, - AnimationTrackType::Vector3, - AnimationTrackTargetType::Scaling3D, 0, {}} + AnimationTrackData{AnimationTrackType::Vector3i, + AnimationTrackType::Vector3, + AnimationTrackTargetType::Scaling3D, 0, {}} }}; data.track(0); @@ -602,9 +602,9 @@ void AnimationDataTest::trackWrongResultType() { Error redirectError{&out}; AnimationData data{nullptr, { - {AnimationTrackType::Vector3i, - AnimationTrackType::Vector3, - AnimationTrackTargetType::Scaling3D, 0, {}} + AnimationTrackData{AnimationTrackType::Vector3i, + AnimationTrackType::Vector3, + AnimationTrackTargetType::Scaling3D, 0, {}} }}; data.track(0); @@ -619,8 +619,8 @@ void AnimationDataTest::release() { }; AnimationData data{{}, keyframes, { - {AnimationTrackTargetType(129), 0, - Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, + AnimationTrackData{AnimationTrackTargetType(129), 0, + Animation::TrackView{keyframes, Animation::Interpolation::Constant}}, }}; CORRADE_COMPARE(data.trackCount(), 1); From 036fced7498e7edc096b2d8c119023e811f0f26a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 6 Dec 2019 19:17:47 +0100 Subject: [PATCH 039/107] Trade: whitelist (exported) growable array deleters in imported data. --- src/Magnum/Trade/AbstractImporter.cpp | 13 +- src/Magnum/Trade/AbstractImporter.h | 7 +- src/Magnum/Trade/ArrayAllocator.cpp | 34 ++++++ src/Magnum/Trade/ArrayAllocator.h | 60 +++++++++ src/Magnum/Trade/CMakeLists.txt | 2 + .../Trade/Test/AbstractImporterTest.cpp | 114 ++++++++++++++++++ 6 files changed, 221 insertions(+), 9 deletions(-) create mode 100644 src/Magnum/Trade/ArrayAllocator.cpp create mode 100644 src/Magnum/Trade/ArrayAllocator.h diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index 50882268d9..512794d6b3 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -35,6 +35,7 @@ #include "Magnum/FileCallback.h" #include "Magnum/Trade/AbstractMaterialData.h" #include "Magnum/Trade/AnimationData.h" +#include "Magnum/Trade/ArrayAllocator.h" #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" @@ -269,7 +270,7 @@ Containers::Optional AbstractImporter::animation(const UnsignedIn CORRADE_ASSERT(id < doAnimationCount(), "Trade::AbstractImporter::animation(): index" << id << "out of range for" << doAnimationCount() << "entries", {}); Containers::Optional animation = doAnimation(id); CORRADE_ASSERT(!animation || - ((!animation->_data.deleter() || animation->_data.deleter() == Implementation::nonOwnedArrayDeleter) && + ((!animation->_data.deleter() || animation->_data.deleter() == Implementation::nonOwnedArrayDeleter || animation->_data.deleter() == ArrayAllocator::deleter) && (!animation->_tracks.deleter() || animation->_tracks.deleter() == reinterpret_cast(Implementation::nonOwnedArrayDeleter))), "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter", {}); return animation; @@ -434,8 +435,8 @@ Containers::Optional AbstractImporter::mesh(const UnsignedInt id) { CORRADE_ASSERT(id < doMeshCount(), "Trade::AbstractImporter::mesh(): index" << id << "out of range for" << doMeshCount() << "entries", {}); Containers::Optional mesh = doMesh(id); CORRADE_ASSERT(!mesh || ( - (!mesh->_indexData.deleter() || mesh->_indexData.deleter() == Implementation::nonOwnedArrayDeleter) && - (!mesh->_vertexData.deleter() || mesh->_vertexData.deleter() == Implementation::nonOwnedArrayDeleter) && + (!mesh->_indexData.deleter() || mesh->_indexData.deleter() == Implementation::nonOwnedArrayDeleter || mesh->_indexData.deleter() == ArrayAllocator::deleter) && + (!mesh->_vertexData.deleter() || mesh->_vertexData.deleter() == Implementation::nonOwnedArrayDeleter || mesh->_vertexData.deleter() == ArrayAllocator::deleter) && (!mesh->_attributes.deleter() || mesh->_attributes.deleter() == reinterpret_cast(Implementation::nonOwnedArrayDeleter))), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter", {}); return mesh; @@ -640,7 +641,7 @@ Containers::Optional AbstractImporter::image1D(const UnsignedInt id } #endif Containers::Optional image = doImage1D(id, level); - CORRADE_ASSERT(!image || !image->_data.deleter() || image->_data.deleter() == Implementation::nonOwnedArrayDeleter, "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter", {}); + CORRADE_ASSERT(!image || !image->_data.deleter() || image->_data.deleter() == Implementation::nonOwnedArrayDeleter || image->_data.deleter() == ArrayAllocator::deleter, "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter", {}); return image; } @@ -696,7 +697,7 @@ Containers::Optional AbstractImporter::image2D(const UnsignedInt id } #endif Containers::Optional image = doImage2D(id, level); - CORRADE_ASSERT(!image || !image->_data.deleter() || image->_data.deleter() == Implementation::nonOwnedArrayDeleter, "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter", {}); + CORRADE_ASSERT(!image || !image->_data.deleter() || image->_data.deleter() == Implementation::nonOwnedArrayDeleter || image->_data.deleter() == ArrayAllocator::deleter, "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter", {}); return image; } @@ -752,7 +753,7 @@ Containers::Optional AbstractImporter::image3D(const UnsignedInt id } #endif Containers::Optional image = doImage3D(id, level); - CORRADE_ASSERT(!image || !image->_data.deleter() || image->_data.deleter() == Implementation::nonOwnedArrayDeleter, "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter", {}); + CORRADE_ASSERT(!image || !image->_data.deleter() || image->_data.deleter() == Implementation::nonOwnedArrayDeleter || image->_data.deleter() == ArrayAllocator::deleter, "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter", {}); return image; } diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index 212c735753..bcc3374300 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -255,9 +255,10 @@ checked by the implementation: As @ref Trade-AbstractImporter-data-dependency "mentioned above", @ref Corrade::Containers::Array instances returned from plugin implementations are not allowed to use anything else than the default - deleter, otherwise this could cause dangling function pointer call on array - destruction if the plugin gets unloaded before the array is destroyed. This - is asserted by the base implementation on return. + deleter or the deleter used by @ref Trade::ArrayAllocator, otherwise this + could cause dangling function pointer call on array destruction if the + plugin gets unloaded before the array is destroyed. This is asserted by the + base implementation on return. @par Similarly for interpolator functions passed through @ref Animation::TrackView instances to @ref AnimationData --- to avoid diff --git a/src/Magnum/Trade/ArrayAllocator.cpp b/src/Magnum/Trade/ArrayAllocator.cpp new file mode 100644 index 0000000000..cc574e884d --- /dev/null +++ b/src/Magnum/Trade/ArrayAllocator.cpp @@ -0,0 +1,34 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "ArrayAllocator.h" + +namespace Magnum { namespace Trade { + +void ArrayAllocator::deleter(char* const data, std::size_t) { + deallocate(data); +} + +}} diff --git a/src/Magnum/Trade/ArrayAllocator.h b/src/Magnum/Trade/ArrayAllocator.h new file mode 100644 index 0000000000..29426effc9 --- /dev/null +++ b/src/Magnum/Trade/ArrayAllocator.h @@ -0,0 +1,60 @@ +#ifndef Magnum_Trade_ArrayAllocator_h +#define Magnum_Trade_ArrayAllocator_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Class @ref Magnum::Trade::ArrayAllocator + * @m_since_latest + */ + +#include + +#include "Magnum/Magnum.h" +#include "Magnum/Trade/visibility.h" + +namespace Magnum { namespace Trade { + +/** +@brief Growable array allocator to be used in importer plugins +@m_since_latest + +Compared to @ref Corrade::Containers::ArrayMallocAllocator ensures that the +@ref Array deleter function pointer is defined in the @ref Trade library and +not in the plugin binary itself, avoiding dangling function pointer call when +the data array is destructed after the plugin has been unloaded. Other than +that the behavior is identical. +*/ +template struct ArrayAllocator: Containers::ArrayMallocAllocator {}; + +#ifndef DOXYGEN_GENERATING_OUTPUT +template<> struct ArrayAllocator: Containers::ArrayMallocAllocator { + MAGNUM_TRADE_EXPORT static void deleter(char* data, std::size_t size); +}; +#endif + +}} + +#endif diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index 28afc0091f..1b07fc5e11 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -27,6 +27,7 @@ find_package(Corrade REQUIRED PluginManager) set(MagnumTrade_SRCS AbstractMaterialData.cpp + ArrayAllocator.cpp Data.cpp LightData.cpp MeshData2D.cpp @@ -52,6 +53,7 @@ set(MagnumTrade_HEADERS AbstractImageConverter.h AbstractMaterialData.h AnimationData.h + ArrayAllocator.h CameraData.h Data.h ImageData.h diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index f3b97bade0..9dc4256b62 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -34,6 +34,7 @@ #include "Magnum/FileCallback.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/AnimationData.h" +#include "Magnum/Trade/ArrayAllocator.h" #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" @@ -107,6 +108,7 @@ struct AbstractImporterTest: TestSuite::Tester { void animationNoFile(); void animationOutOfRange(); void animationNonOwningDeleters(); + void animationGrowableDeleters(); void animationCustomDataDeleter(); void animationCustomTrackDeleter(); @@ -170,6 +172,7 @@ struct AbstractImporterTest: TestSuite::Tester { void meshNoFile(); void meshOutOfRange(); void meshNonOwningDeleters(); + void meshGrowableDeleters(); void meshCustomIndexDataDeleter(); void meshCustomVertexDataDeleter(); void meshCustomAttributesDeleter(); @@ -243,6 +246,7 @@ struct AbstractImporterTest: TestSuite::Tester { void image1DOutOfRange(); void image1DLevelOutOfRange(); void image1DNonOwningDeleter(); + void image1DGrowableDeleter(); void image1DCustomDeleter(); void image2D(); @@ -262,6 +266,7 @@ struct AbstractImporterTest: TestSuite::Tester { void image2DOutOfRange(); void image2DLevelOutOfRange(); void image2DNonOwningDeleter(); + void image2DGrowableDeleter(); void image2DCustomDeleter(); void image3D(); @@ -281,6 +286,7 @@ struct AbstractImporterTest: TestSuite::Tester { void image3DOutOfRange(); void image3DLevelOutOfRange(); void image3DNonOwningDeleter(); + void image3DGrowableDeleter(); void image3DCustomDeleter(); void importerState(); @@ -346,6 +352,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::animationNoFile, &AbstractImporterTest::animationOutOfRange, &AbstractImporterTest::animationNonOwningDeleters, + &AbstractImporterTest::animationGrowableDeleters, &AbstractImporterTest::animationCustomDataDeleter, &AbstractImporterTest::animationCustomTrackDeleter, @@ -409,6 +416,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::meshNoFile, &AbstractImporterTest::meshOutOfRange, &AbstractImporterTest::meshNonOwningDeleters, + &AbstractImporterTest::meshGrowableDeleters, &AbstractImporterTest::meshCustomIndexDataDeleter, &AbstractImporterTest::meshCustomVertexDataDeleter, &AbstractImporterTest::meshCustomAttributesDeleter, @@ -482,6 +490,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::image1DOutOfRange, &AbstractImporterTest::image1DLevelOutOfRange, &AbstractImporterTest::image1DNonOwningDeleter, + &AbstractImporterTest::image1DGrowableDeleter, &AbstractImporterTest::image1DCustomDeleter, &AbstractImporterTest::image2D, @@ -501,6 +510,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::image2DOutOfRange, &AbstractImporterTest::image2DLevelOutOfRange, &AbstractImporterTest::image2DNonOwningDeleter, + &AbstractImporterTest::image2DGrowableDeleter, &AbstractImporterTest::image2DCustomDeleter, &AbstractImporterTest::image3D, @@ -520,6 +530,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::image3DOutOfRange, &AbstractImporterTest::image3DLevelOutOfRange, &AbstractImporterTest::image3DNonOwningDeleter, + &AbstractImporterTest::image3DGrowableDeleter, &AbstractImporterTest::image3DCustomDeleter, &AbstractImporterTest::importerState, @@ -1405,6 +1416,25 @@ void AbstractImporterTest::animationNonOwningDeleters() { CORRADE_COMPARE(static_cast(data->data()), importer.data); } +void AbstractImporterTest::animationGrowableDeleters() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doAnimationCount() const override { return 1; } + Containers::Optional doAnimation(UnsignedInt) override { + Containers::Array data; + Containers::arrayAppend(data, '\x37'); + return AnimationData{std::move(data), {AnimationTrackData{}}}; + } + } importer; + + auto data = importer.animation(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->data()[0], '\x37'); +} + void AbstractImporterTest::animationCustomDataDeleter() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -2300,6 +2330,33 @@ void AbstractImporterTest::meshNonOwningDeleters() { CORRADE_COMPARE(static_cast(data->indexData()), importer.indexData); } +void AbstractImporterTest::meshGrowableDeleters() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt) override { + Containers::Array indexData; + Containers::arrayAppend(indexData, '\xab'); + Containers::Array vertexData; + Containers::arrayAppend(vertexData, Vector3{}); + MeshIndexData indices{MeshIndexType::UnsignedByte, indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + return MeshData{MeshPrimitive::Triangles, + std::move(indexData), indices, + Containers::arrayAllocatorCast(std::move(vertexData)), {positions}}; + } + } importer; + + auto data = importer.mesh(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->indexData()[0], '\xab'); + CORRADE_COMPARE(data->vertexData().size(), 12); +} + void AbstractImporterTest::meshCustomIndexDataDeleter() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -3344,6 +3401,25 @@ void AbstractImporterTest::image1DNonOwningDeleter() { CORRADE_COMPARE(static_cast(data->data()), importer.data); } +void AbstractImporterTest::image1DGrowableDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage1DCount() const override { return 1; } + Containers::Optional doImage1D(UnsignedInt, UnsignedInt) override { + Containers::Array data; + Containers::arrayAppend(data, '\xff'); + return ImageData1D{PixelFormat::RGBA8Unorm, {}, std::move(data)}; + } + } importer; + + auto data = importer.image1D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->data()[0], '\xff'); +} + void AbstractImporterTest::image1DCustomDeleter() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -3630,6 +3706,25 @@ void AbstractImporterTest::image2DNonOwningDeleter() { CORRADE_COMPARE(static_cast(data->data()), importer.data); } +void AbstractImporterTest::image2DGrowableDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage2DCount() const override { return 1; } + Containers::Optional doImage2D(UnsignedInt, UnsignedInt) override { + Containers::Array data; + Containers::arrayAppend(data, '\xff'); + return ImageData2D{PixelFormat::RGBA8Unorm, {}, std::move(data)}; + } + } importer; + + auto data = importer.image2D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->data()[0], '\xff'); +} + void AbstractImporterTest::image2DCustomDeleter() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -3917,6 +4012,25 @@ void AbstractImporterTest::image3DNonOwningDeleter() { CORRADE_COMPARE(static_cast(data->data()), importer.data); } +void AbstractImporterTest::image3DGrowableDeleter() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doImage3DCount() const override { return 1; } + Containers::Optional doImage3D(UnsignedInt, UnsignedInt) override { + Containers::Array data; + Containers::arrayAppend(data, '\xff'); + return ImageData3D{PixelFormat::RGBA8Unorm, {}, std::move(data)}; + } + } importer; + + auto data = importer.image3D(0); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->data()[0], '\xff'); +} + void AbstractImporterTest::image3DCustomDeleter() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } From e3ee1e561e6194c93d94fd0a0dcfb1bfb07a0911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 19 Feb 2020 17:58:22 +0100 Subject: [PATCH 040/107] Trade: typeless access to MeshData attributes. Similarly to ImageData::pixels() which return a strided array view of one dimension more. --- src/Magnum/Trade/MeshData.cpp | 48 +++++++++++ src/Magnum/Trade/MeshData.h | 103 +++++++++++++++++++---- src/Magnum/Trade/Test/MeshDataTest.cpp | 109 +++++++++++++++++++++++++ 3 files changed, 244 insertions(+), 16 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 3841e56a6c..f5296c472a 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -49,6 +49,16 @@ MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexForma "Trade::MeshAttributeData: view stride" << data.stride() << "is not large enough to contain" << format, ); } +MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, format, Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, nullptr} { + /* Yes, this calls into a constexpr function defined in the header -- + because I feel that makes more sense than duplicating the full assert + logic */ + CORRADE_ASSERT(data.empty()[0] || vertexFormatSize(format) == data.size()[1], + "Trade::MeshAttributeData: second view dimension size" << data.size()[1] << "doesn't match" << format, ); + CORRADE_ASSERT(data.isContiguous<1>(), + "Trade::MeshAttributeData: second view dimension is not contiguous", ); +} + Containers::Array meshAttributeDataNonOwningArray(const Containers::ArrayView view) { /* Ugly, eh? */ return Containers::Array{const_cast(view.data()), view.size(), reinterpret_cast(Trade::Implementation::nonOwnedArrayDeleter)}; @@ -230,6 +240,44 @@ UnsignedInt MeshData::attributeStride(MeshAttribute name, UnsignedInt id) const return attributeStride(attributeId); } +Containers::StridedArrayView2D MeshData::attribute(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + /* Build a 2D view using information about attribute type size */ + return Containers::arrayCast<2, const char>(_attributes[id].data, + vertexFormatSize(_attributes[id].format)); +} + +Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) { + CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + /* Build a 2D view using information about attribute type size */ + auto out = Containers::arrayCast<2, const char>(_attributes[id].data, + vertexFormatSize(_attributes[id].format)); + /** @todo some arrayConstCast? UGH */ + return Containers::StridedArrayView2D{ + /* The view size is there only for a size assert, we're pretty sure the + view is valid */ + {static_cast(const_cast(out.data())), ~std::size_t{}}, + out.size(), out.stride()}; +} + +Containers::StridedArrayView2D MeshData::attribute(MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attribute(attributeId); +} + +Containers::StridedArrayView2D MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { + CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return mutableAttribute(attributeId); +} + namespace { template void convertIndices(const Containers::ArrayView data, const Containers::ArrayView destination) { diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index b69ca987ef..fbb5be122a 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -229,6 +229,20 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { */ explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data) noexcept; + /** + * @brief Constructor + * @param name Attribute name + * @param format Vertex format + * @param data Attribute data + * + * Expects that the second dimension of @p data is contiguous and its + * size matches @p type; and that @p type corresponds to @p name. + */ + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView2D& data) noexcept; + + /** @overload */ + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, std::nullptr_t) noexcept: MeshAttributeData{name, format, nullptr, nullptr} {} + /** * @brief Constructor * @param name Attribute name @@ -762,6 +776,26 @@ class MAGNUM_TRADE_EXPORT MeshData { /** * @brief Data for given attribute array * + * The @p id is expected to be smaller than @ref attributeCount() const. + * The second dimension represents the actual data type (its size is + * equal to type size) and is guaranteed to be contiguous. Use the + * templated overload below to get the attribute in a concrete type. + * @see @ref Corrade::Containers::StridedArrayView::isContiguous() + */ + Containers::StridedArrayView2D attribute(UnsignedInt id) const; + + /** + * @brief Mutable data for given attribute array + * + * Like @ref attribute(UnsignedInt) const, but returns a mutable view. + * Expects that the mesh is mutable. + * @see @ref vertexDataFlags() + */ + Containers::StridedArrayView2D mutableAttribute(UnsignedInt id); + + /** + * @brief Data for given attribute array in a concrete type + * * The @p id is expected to be smaller than @ref attributeCount() const * and @p T is expected to correspond to * @ref attributeFormat(UnsignedInt) const. You can also use the @@ -776,7 +810,7 @@ class MAGNUM_TRADE_EXPORT MeshData { template Containers::StridedArrayView1D attribute(UnsignedInt id) const; /** - * @brief Mutable data for given attribute array + * @brief Mutable data for given attribute array in a concrete type * * Like @ref attribute(UnsignedInt) const, but returns a mutable view. * Expects that the mesh is mutable. @@ -788,6 +822,29 @@ class MAGNUM_TRADE_EXPORT MeshData { * @brief Data for given named attribute array * * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const. The second dimension + * represents the actual data type (its size is equal to type size) and + * is guaranteed to be contiguous. Use the templated overload below to + * get the attribute in a concrete type. + * @see @ref attribute(UnsignedInt) const, + * @ref mutableAttribute(MeshAttribute, UnsignedInt), + * @ref Corrade::Containers::StridedArrayView::isContiguous() + */ + Containers::StridedArrayView2D attribute(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Mutable data for given named attribute array + * + * Like @ref attribute(MeshAttribute, UnsignedInt) const, but returns a + * mutable view. Expects that the mesh is mutable. + * @see @ref vertexDataFlags() + */ + Containers::StridedArrayView2D mutableAttribute(MeshAttribute name, UnsignedInt id = 0); + + /** + * @brief Data for given named attribute array in a concrete type + * + * The @p id is expected to be smaller than * @ref attributeCount(MeshAttribute) const and @p T is expected to * correspond to @ref attributeFormat(MeshAttribute, UnsignedInt) const. * You can also use the non-templated @ref positions2DAsArray(), @@ -802,7 +859,7 @@ class MAGNUM_TRADE_EXPORT MeshData { template Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0) const; /** - * @brief Mutable data for given named attribute array + * @brief Mutable data for given named attribute array in a concrete type * * Like @ref attribute(MeshAttribute, UnsignedInt) const, but returns a * mutable view. Expects that the mesh is mutable. @@ -1066,35 +1123,49 @@ template Containers::ArrayView MeshData::mutableIndices() { } template Containers::StridedArrayView1D MeshData::attribute(UnsignedInt id) const { - CORRADE_ASSERT(id < _attributes.size(), - "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + Containers::StridedArrayView2D data = attribute(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[id].format, "Trade::MeshData::attribute(): improper type requested for" << _attributes[id].name << "of format" << _attributes[id].format, nullptr); - return Containers::arrayCast(_attributes[id].data); + return Containers::arrayCast<1, const T>(data); } template Containers::StridedArrayView1D MeshData::mutableAttribute(UnsignedInt id) { - CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, - "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); - CORRADE_ASSERT(id < _attributes.size(), - "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + Containers::StridedArrayView2D data = mutableAttribute(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[id].format, "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[id].name << "of format" << _attributes[id].format, nullptr); - return Containers::arrayCast(reinterpret_cast&>(_attributes[id].data)); + return Containers::arrayCast<1, T>(data); } template Containers::StridedArrayView1D MeshData::attribute(MeshAttribute name, UnsignedInt id) const { + Containers::StridedArrayView2D data = attribute(name, id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT const UnsignedInt attributeId = attributeFor(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); - return attribute(attributeId); + #endif + CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[attributeId].format, + "Trade::MeshData::attribute(): improper type requested for" << _attributes[attributeId].name << "of format" << _attributes[attributeId].format, nullptr); + return Containers::arrayCast<1, const T>(data); } template Containers::StridedArrayView1D MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { - CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, - "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); + Containers::StridedArrayView2D data = mutableAttribute(name, id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT const UnsignedInt attributeId = attributeFor(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); - return mutableAttribute(attributeId); + #endif + CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[attributeId].format, + "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[attributeId].name << "of type" << _attributes[attributeId].format, nullptr); + return Containers::arrayCast<1, T>(data); } }} diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 3741e020b8..37f2900d75 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -49,8 +49,12 @@ struct MeshDataTest: TestSuite::Tester { void constructAttribute(); void constructAttributeCustom(); void constructAttributeWrongFormat(); + void constructAttribute2D(); + void constructAttribute2DWrongSize(); + void constructAttribute2DNonContiguous(); void constructAttributeTypeErased(); void constructAttributeTypeErasedWrongStride(); + void constructAttributeNullptr(); void constructAttributeNonOwningArray(); void construct(); @@ -140,8 +144,12 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttribute, &MeshDataTest::constructAttributeCustom, &MeshDataTest::constructAttributeWrongFormat, + &MeshDataTest::constructAttribute2D, + &MeshDataTest::constructAttribute2DWrongSize, + &MeshDataTest::constructAttribute2DNonContiguous, &MeshDataTest::constructAttributeTypeErased, &MeshDataTest::constructAttributeTypeErasedWrongStride, + &MeshDataTest::constructAttributeNullptr, &MeshDataTest::constructAttributeNonOwningArray, &MeshDataTest::construct, @@ -376,6 +384,41 @@ void MeshDataTest::constructAttributeWrongFormat() { CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n"); } +void MeshDataTest::constructAttribute2D() { + Containers::Array positionData{4*sizeof(Vector2)}; + auto positionView = Containers::StridedArrayView2D{positionData, + {4, sizeof(Vector2)}}.every(2); + + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, positionView}; + MeshData data{MeshPrimitive::Points, std::move(positionData), {positions}}; + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(static_cast(data.attribute(0).data()), + positionView.data()); +} + +void MeshDataTest::constructAttribute2DWrongSize() { + Containers::Array positionData{4*sizeof(Vector2)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, + Containers::StridedArrayView2D{positionData, + {4, sizeof(Vector2)}}.every(2)}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension size 8 doesn't match VertexFormat::Vector3\n"); +} + +void MeshDataTest::constructAttribute2DNonContiguous() { + Containers::Array positionData{4*sizeof(Vector2)}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, + Containers::StridedArrayView2D{positionData, + {2, sizeof(Vector2)*2}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension is not contiguous\n"); +} + void MeshDataTest::constructAttributeTypeErased() { Containers::Array positionData{3*sizeof(Vector3)}; auto positionView = Containers::arrayCast(positionData); @@ -397,6 +440,14 @@ void MeshDataTest::constructAttributeTypeErasedWrongStride() { CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: view stride 1 is not large enough to contain VertexFormat::Vector3\n"); } +void MeshDataTest::constructAttributeNullptr() { + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; + MeshData data{MeshPrimitive::LineLoop, nullptr, {positions}}; + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); + CORRADE_VERIFY(!data.attribute(0).data()); +} + void MeshDataTest::constructAttributeNonOwningArray() { const MeshAttributeData data[3]; Containers::Array array = meshAttributeDataNonOwningArray(data); @@ -491,6 +542,30 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attributeStride(1), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(2), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(3), sizeof(Vertex)); + + /* Typeless access by ID with a cast later */ + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.attribute(0))[1]), (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(1))[0]), (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.attribute(2))[2]), Vector3::zAxis()); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(3))[1]), (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Short>( + data.attribute(4))[0]), 15); + CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( + data.mutableAttribute(0))[1]), (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(1))[0]), (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( + data.mutableAttribute(2))[2]), Vector3::zAxis()); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(3))[1]), (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE((Containers::arrayCast<1, Short>( + data.mutableAttribute(4))[0]), 15); + + /* Typed access by ID */ CORRADE_COMPARE(data.attribute(0)[1], (Vector3{0.4f, 0.5f, 0.6f})); CORRADE_COMPARE(data.attribute(1)[0], (Vector2{0.000f, 0.125f})); CORRADE_COMPARE(data.attribute(2)[2], Vector3::zAxis()); @@ -534,6 +609,30 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 0), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 1), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(meshAttributeCustom(13)), sizeof(Vertex)); + + /* Typeless access by name with a cast later */ + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.attribute(MeshAttribute::Position))[1]), (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.attribute(MeshAttribute::Normal))[2]), Vector3::zAxis()); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(MeshAttribute::TextureCoordinates, 0))[0]), (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(MeshAttribute::TextureCoordinates, 1))[1]), (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Short>( + data.attribute(meshAttributeCustom(13)))[1]), -374); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.mutableAttribute(MeshAttribute::Position))[1]), (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( + data.mutableAttribute(MeshAttribute::Normal))[2]), Vector3::zAxis()); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(MeshAttribute::TextureCoordinates, 0))[0]), (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(MeshAttribute::TextureCoordinates, 1))[1]), (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE((Containers::arrayCast<1, Short>( + data.mutableAttribute(meshAttributeCustom(13)))[1]), -374); + + /* Typed access by name */ CORRADE_COMPARE(data.attribute(MeshAttribute::Position)[1], (Vector3{0.4f, 0.5f, 0.6f})); CORRADE_COMPARE(data.attribute(MeshAttribute::Normal)[2], Vector3::zAxis()); CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); @@ -1275,13 +1374,17 @@ void MeshDataTest::mutableAccessNotAllowed() { data.mutableIndexData(); data.mutableVertexData(); data.mutableIndices(); + data.mutableAttribute(0); data.mutableAttribute(0); + data.mutableAttribute(MeshAttribute::Position); data.mutableAttribute(MeshAttribute::Position); CORRADE_COMPARE(out.str(), "Trade::MeshData::mutableIndexData(): index data not mutable\n" "Trade::MeshData::mutableVertexData(): vertex data not mutable\n" "Trade::MeshData::mutableIndices(): index data not mutable\n" "Trade::MeshData::mutableAttribute(): vertex data not mutable\n" + "Trade::MeshData::mutableAttribute(): vertex data not mutable\n" + "Trade::MeshData::mutableAttribute(): vertex data not mutable\n" "Trade::MeshData::mutableAttribute(): vertex data not mutable\n"); } @@ -1327,6 +1430,7 @@ void MeshDataTest::attributeNotFound() { data.attributeFormat(2); data.attributeOffset(2); data.attributeStride(2); + data.attribute(2); data.attribute(2); data.attributeFormat(MeshAttribute::Position); data.attributeFormat(MeshAttribute::Color, 2); @@ -1334,6 +1438,8 @@ void MeshDataTest::attributeNotFound() { data.attributeOffset(MeshAttribute::Color, 2); data.attributeStride(MeshAttribute::Position); data.attributeStride(MeshAttribute::Color, 2); + data.attribute(MeshAttribute::Position); + data.attribute(MeshAttribute::Color, 2); data.attribute(MeshAttribute::Position); data.attribute(MeshAttribute::Color, 2); data.positions2DAsArray(); @@ -1347,6 +1453,7 @@ void MeshDataTest::attributeNotFound() { "Trade::MeshData::attributeOffset(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attributeStride(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attribute(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attributeFormat(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attributeFormat(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" "Trade::MeshData::attributeOffset(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" @@ -1355,6 +1462,8 @@ void MeshDataTest::attributeNotFound() { "Trade::MeshData::attributeStride(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" "Trade::MeshData::positions2DInto(): index 0 out of range for 0 position attributes\n" "Trade::MeshData::positions3DInto(): index 0 out of range for 0 position attributes\n" "Trade::MeshData::normalsInto(): index 0 out of range for 0 normal attributes\n" From aecec186bed30facc9f857b14f70ca2b6c7dbf2e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 8 Jan 2020 19:01:08 +0100 Subject: [PATCH 041/107] Trade: expose getters in MeshIndexData. It made testing and everything harder than strictly necessary. OTOH still keeping MeshData as a friend and accessing members directly since those are heavily interconnected anyway. --- src/Magnum/Trade/MeshData.cpp | 4 +- src/Magnum/Trade/MeshData.h | 23 ++++---- src/Magnum/Trade/Test/MeshDataTest.cpp | 72 ++++++++++---------------- 3 files changed, 44 insertions(+), 55 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index f5296c472a..319a1faa63 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -64,11 +64,11 @@ Containers::Array meshAttributeDataNonOwningArray(const Conta return Containers::Array{const_cast(view.data()), view.size(), reinterpret_cast(Trade::Implementation::nonOwnedArrayDeleter)}; } -MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: _indexType{indices.type}, _primitive{primitive}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{Containers::arrayCast(indices.data)} { +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: _indexType{indices._type}, _primitive{primitive}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{Containers::arrayCast(indices._data)} { /* Save vertex count. It's a strided array view, so the size is not depending on type. */ if(_attributes.empty()) { - CORRADE_ASSERT(indices.type != MeshIndexType{}, + CORRADE_ASSERT(indices._type != MeshIndexType{}, "Trade::MeshData: indices are expected to be valid if there are no attributes and vertex count isn't passed explicitly", ); /** @todo some better value? attributeless indexed with defined vertex count? */ _vertexCount = 0; diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index fbb5be122a..ba83cd7ebb 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -152,18 +152,18 @@ constexpr UnsignedShort meshAttributeCustom(MeshAttribute name) { @brief Mesh index data @m_since_latest -Convenience type for populating @ref MeshData. Has no accessors, as the data -are then accessible through @ref MeshData APIs. +Convenience type for populating @ref MeshData, see its documentation for an +introduction. @see @ref MeshAttributeData */ class MAGNUM_TRADE_EXPORT MeshIndexData { public: /** @brief Construct for a non-indexed mesh */ - explicit MeshIndexData() noexcept: type{} {} + explicit MeshIndexData() noexcept: _type{} {} /** * @brief Construct with a runtime-specified index type - * @param type Mesh index type + * @param type Index type * @param data Index data * * The @p data size is expected to correspond to given @p type (e.g., @@ -185,6 +185,12 @@ class MAGNUM_TRADE_EXPORT MeshIndexData { /** @brief Construct with unsigned int indices */ constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedInt, data, nullptr} {} + /** @brief Index type */ + constexpr MeshIndexType type() const { return _type; } + + /** @brief Type-erased index data */ + constexpr Containers::ArrayView data() const { return _data; } + private: /* Contains an assert common for all constexpr constructor, nullptr_t to disambiguate from the public constructor of the same signature -- @@ -193,11 +199,10 @@ class MAGNUM_TRADE_EXPORT MeshIndexData { template, we don't need that check anyway */ constexpr explicit MeshIndexData(MeshIndexType type, Containers::ArrayView data, std::nullptr_t); - /* Not prefixed with _ because we use them like public in MeshData */ friend MeshData; - MeshIndexType type; + MeshIndexType _type; /* Void so the constructors can be constexpr */ - Containers::ArrayView data; + Containers::ArrayView _data; }; /** @@ -1083,8 +1088,8 @@ namespace Implementation { } #endif -constexpr MeshIndexData::MeshIndexData(MeshIndexType type, Containers::ArrayView data, std::nullptr_t): - type{type}, data{(CORRADE_CONSTEXPR_ASSERT(!data.empty(), "Trade::MeshIndexData: index array can't be empty, create a non-indexed mesh instead"), data)} {} +constexpr MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayView data, std::nullptr_t): + _type{type}, _data{(CORRADE_CONSTEXPR_ASSERT(!data.empty(), "Trade::MeshIndexData: index array can't be empty, create a non-indexed mesh instead"), data)} {} constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept: name{name}, format{format}, data{(CORRADE_CONSTEXPR_ASSERT( diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 37f2900d75..5d20e46f52 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -264,50 +264,38 @@ constexpr UnsignedInt IndexInts[]{2110122, 132257, 3}; void MeshDataTest::constructIndex() { { - Containers::Array indexData{3*1}; - auto indexView = Containers::arrayCast(indexData); - - MeshIndexData indices{indexView}; - MeshData data{MeshPrimitive::Points, std::move(indexData), indices}; - CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedByte); - CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); - CORRADE_COMPARE(data.indexCount(), 3); + const UnsignedByte indexData[]{25, 132, 3}; + MeshIndexData indices{indexData}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedByte); + CORRADE_COMPARE(indices.data().data(), indexData); constexpr MeshIndexData cindices{IndexBytes}; - MeshData cdata{MeshPrimitive::Points, {}, IndexBytes, cindices}; - CORRADE_COMPARE(cdata.indexType(), MeshIndexType::UnsignedByte); - CORRADE_COMPARE(static_cast(cdata.indices().data()), IndexBytes); - CORRADE_COMPARE(data.indexCount(), 3); + constexpr MeshIndexType type = cindices.type(); + constexpr Containers::ArrayView data = cindices.data(); + CORRADE_COMPARE(type, MeshIndexType::UnsignedByte); + CORRADE_COMPARE(data.data(), IndexBytes); } { - Containers::Array indexData{3*2}; - auto indexView = Containers::arrayCast(indexData); - - MeshIndexData indices{indexView}; - MeshData data{MeshPrimitive::Points, std::move(indexData), indices}; - CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); - CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); - CORRADE_COMPARE(data.indexCount(), 3); + const UnsignedShort indexData[]{2575, 13224, 3}; + MeshIndexData indices{indexData}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(indices.data().data(), indexData); constexpr MeshIndexData cindices{IndexShorts}; - MeshData cdata{MeshPrimitive::Points, {}, IndexShorts, cindices}; - CORRADE_COMPARE(cdata.indexType(), MeshIndexType::UnsignedShort); - CORRADE_COMPARE(static_cast(cdata.indices().data()), IndexShorts); - CORRADE_COMPARE(data.indexCount(), 3); + constexpr MeshIndexType type = cindices.type(); + constexpr Containers::ArrayView data = cindices.data(); + CORRADE_COMPARE(type, MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.data(), IndexShorts); } { - Containers::Array indexData{3*4}; - auto indexView = Containers::arrayCast(indexData); - - MeshIndexData indices{indexView}; - MeshData data{MeshPrimitive::Points, std::move(indexData), indices}; - CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedInt); - CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); - CORRADE_COMPARE(data.indexCount(), 3); + const UnsignedInt indexData[]{2110122, 132257, 3}; + MeshIndexData indices{indexData}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(indices.data().data(), indexData); constexpr MeshIndexData cindices{IndexInts}; - MeshData cdata{MeshPrimitive::Points, {}, IndexInts, cindices}; - CORRADE_COMPARE(cdata.indexType(), MeshIndexType::UnsignedInt); - CORRADE_COMPARE(static_cast(cdata.indices().data()), IndexInts); - CORRADE_COMPARE(data.indexCount(), 3); + constexpr MeshIndexType type = cindices.type(); + constexpr Containers::ArrayView data = cindices.data(); + CORRADE_COMPARE(type, MeshIndexType::UnsignedInt); + CORRADE_COMPARE(data.data(), IndexInts); } } @@ -319,18 +307,14 @@ void MeshDataTest::constructIndexZeroCount() { } void MeshDataTest::constructIndexTypeErased() { - Containers::Array indexData{3*2}; - auto indexView = Containers::arrayCast(indexData); - + const char indexData[3*2]{}; MeshIndexData indices{MeshIndexType::UnsignedShort, indexData}; - MeshData data{MeshPrimitive::Points, std::move(indexData), indices}; - CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); - CORRADE_COMPARE(static_cast(data.indices().data()), indexView.data()); - CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedShort); + CORRADE_VERIFY(indices.data().data() == indexData); } void MeshDataTest::constructIndexTypeErasedWrongSize() { - Containers::Array indexData{3*2}; + const char indexData[3*2]{}; std::ostringstream out; Error redirectError{&out}; From 5f35b06d8f6ec08749f8e0337dd82bda70860551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 20 Feb 2020 14:22:19 +0100 Subject: [PATCH 042/107] Trade: expose getters in MeshAttributeData. --- src/Magnum/Trade/MeshData.cpp | 58 +++++++++---------- src/Magnum/Trade/MeshData.h | 40 +++++++------ src/Magnum/Trade/Test/MeshDataTest.cpp | 80 +++++++++++--------------- 3 files changed, 86 insertions(+), 92 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 319a1faa63..1217cb87fb 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -72,7 +72,7 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde "Trade::MeshData: indices are expected to be valid if there are no attributes and vertex count isn't passed explicitly", ); /** @todo some better value? attributeless indexed with defined vertex count? */ _vertexCount = 0; - } else _vertexCount = _attributes[0].data.size(); + } else _vertexCount = _attributes[0]._data.size(); CORRADE_ASSERT(!_indices.empty() || !_indexData, "Trade::MeshData: indexData passed for a non-indexed mesh", ); @@ -88,11 +88,11 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde constructors */ for(std::size_t i = 0; i != _attributes.size(); ++i) { const MeshAttributeData& attribute = _attributes[i]; - const Containers::StridedArrayView1D data = Containers::arrayCast(attribute.data); + const Containers::StridedArrayView1D data = Containers::arrayCast(attribute._data); CORRADE_ASSERT(data.size() == _vertexCount, "Trade::MeshData: attribute" << i << "has" << data.size() << "vertices but" << _vertexCount << "expected", ); - CORRADE_ASSERT(data.empty() || (&data.front() >= _vertexData.begin() && &data.back() + vertexFormatSize(attribute.format) <= _vertexData.end()), - "Trade::MeshData: attribute" << i << "[" << Debug::nospace << static_cast(&data.front()) << Debug::nospace << ":" << Debug::nospace << static_cast(&data.back() + vertexFormatSize(attribute.format)) << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); + CORRADE_ASSERT(data.empty() || (&data.front() >= _vertexData.begin() && &data.back() + vertexFormatSize(attribute._format) <= _vertexData.end()), + "Trade::MeshData: attribute" << i << "[" << Debug::nospace << static_cast(&data.front()) << Debug::nospace << ":" << Debug::nospace << static_cast(&data.back() + vertexFormatSize(attribute._format)) << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); } #endif } @@ -181,37 +181,37 @@ MeshIndexType MeshData::indexType() const { MeshAttribute MeshData::attributeName(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeName(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); - return _attributes[id].name; + return _attributes[id]._name; } VertexFormat MeshData::attributeFormat(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeFormat(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); - return _attributes[id].format; + return _attributes[id]._format; } std::size_t MeshData::attributeOffset(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); - return static_cast(_attributes[id].data.data()) - _vertexData.data(); + return static_cast(_attributes[id]._data.data()) - _vertexData.data(); } UnsignedInt MeshData::attributeStride(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeStride(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); - return _attributes[id].data.stride(); + return _attributes[id]._data.stride(); } UnsignedInt MeshData::attributeCount(const MeshAttribute name) const { UnsignedInt count = 0; for(const MeshAttributeData& attribute: _attributes) - if(attribute.name == name) ++count; + if(attribute._name == name) ++count; return count; } UnsignedInt MeshData::attributeFor(const MeshAttribute name, UnsignedInt id) const { for(std::size_t i = 0; i != _attributes.size(); ++i) { - if(_attributes[i].name != name) continue; + if(_attributes[i]._name != name) continue; if(id-- == 0) return i; } @@ -244,8 +244,8 @@ Containers::StridedArrayView2D MeshData::attribute(UnsignedInt id) c CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); /* Build a 2D view using information about attribute type size */ - return Containers::arrayCast<2, const char>(_attributes[id].data, - vertexFormatSize(_attributes[id].format)); + return Containers::arrayCast<2, const char>(_attributes[id]._data, + vertexFormatSize(_attributes[id]._format)); } Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) { @@ -254,8 +254,8 @@ Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); /* Build a 2D view using information about attribute type size */ - auto out = Containers::arrayCast<2, const char>(_attributes[id].data, - vertexFormatSize(_attributes[id].format)); + auto out = Containers::arrayCast<2, const char>(_attributes[id]._data, + vertexFormatSize(_attributes[id]._format)); /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the @@ -317,9 +317,9 @@ void MeshData::positions2DInto(const Containers::StridedArrayView1D des const MeshAttributeData& attribute = _attributes[attributeId]; /* Copy 2D positions as-is, for 3D positions ignore Z */ - if(attribute.format == VertexFormat::Vector2 || - attribute.format == VertexFormat::Vector3) - Utility::copy(Containers::arrayCast(attribute.data), destination); + if(attribute._format == VertexFormat::Vector2 || + attribute._format == VertexFormat::Vector3) + Utility::copy(Containers::arrayCast(attribute._data), destination); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -337,16 +337,16 @@ void MeshData::positions3DInto(const Containers::StridedArrayView1D des /* For 2D positions copy the XY part to the first two components and then fill the Z with a single value */ - if(attribute.format == VertexFormat::Vector2) { - Utility::copy(Containers::arrayCast(attribute.data), + if(attribute._format == VertexFormat::Vector2) { + Utility::copy(Containers::arrayCast(attribute._data), Containers::arrayCast(destination)); constexpr Float z[1]{0.0f}; Utility::copy( Containers::stridedArrayView(z).broadcasted<0>(_vertexCount), Containers::arrayCast<2, Float>(destination).transposed<0, 1>()[2]); /* Copy 3D positions as-is */ - } else if(attribute.format == VertexFormat::Vector3) { - Utility::copy(Containers::arrayCast(attribute.data), destination); + } else if(attribute._format == VertexFormat::Vector3) { + Utility::copy(Containers::arrayCast(attribute._data), destination); } else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -362,8 +362,8 @@ void MeshData::normalsInto(const Containers::StridedArrayView1D destina CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::normalsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; - if(attribute.format == VertexFormat::Vector3) - Utility::copy(Containers::arrayCast(attribute.data), destination); + if(attribute._format == VertexFormat::Vector3) + Utility::copy(Containers::arrayCast(attribute._data), destination); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -379,8 +379,8 @@ void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D(attribute.data), destination); + if(attribute._format == VertexFormat::Vector2) + Utility::copy(Containers::arrayCast(attribute._data), destination); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -398,16 +398,16 @@ void MeshData::colorsInto(const Containers::StridedArrayView1D destinati /* For three-component colors copy the RGB part to the first three components and then fill the alpha with a single value */ - if(attribute.format == VertexFormat::Vector3) { - Utility::copy(Containers::arrayCast(attribute.data), + if(attribute._format == VertexFormat::Vector3) { + Utility::copy(Containers::arrayCast(attribute._data), Containers::arrayCast(destination)); constexpr Float alpha[1]{1.0f}; Utility::copy( Containers::stridedArrayView(alpha).broadcasted<0>(_vertexCount), Containers::arrayCast<2, Float>(destination).transposed<0, 1>()[3]); /* Copy four-component colors as-is */ - } else if(attribute.format == VertexFormat::Vector4) { - Utility::copy(Containers::arrayCast(attribute.data), + } else if(attribute._format == VertexFormat::Vector4) { + Utility::copy(Containers::arrayCast(attribute._data), Containers::arrayCast(destination)); } else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index ba83cd7ebb..dfa8209233 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -209,8 +209,8 @@ class MAGNUM_TRADE_EXPORT MeshIndexData { @brief Mesh attribute data @m_since_latest -Convenience type for populating @ref MeshData. Has no accessors, as the data -are then accessible through @ref MeshData APIs. +Convenience type for populating @ref MeshData, see its documentation for an +introduction. */ class MAGNUM_TRADE_EXPORT MeshAttributeData { public: @@ -221,7 +221,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * initialization of the attribute array for @ref MeshData, expected to * be replaced with concrete values later. */ - constexpr explicit MeshAttributeData() noexcept: name{}, format{}, data{} {} + constexpr explicit MeshAttributeData() noexcept: _name{}, _format{}, _data{} {} /** * @brief Type-erased constructor @@ -261,15 +261,23 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /** @overload */ template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {} + /** @brief Attribute name */ + constexpr MeshAttribute name() const { return _name; } + + /** @brief Attribute format */ + constexpr VertexFormat format() const { return _format; } + + /** @brief Type-erased attribute data */ + constexpr Containers::StridedArrayView1D data() const { return _data; } + private: constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept; - /* Not prefixed with _ because we use them like public in MeshData */ friend MeshData; - MeshAttribute name; + MeshAttribute _name; /* Here's some room for flags */ - VertexFormat format; - Containers::StridedArrayView1D data; + VertexFormat _format; + Containers::StridedArrayView1D _data; }; /** @relatesalso MeshAttributeData @@ -1092,7 +1100,7 @@ constexpr MeshIndexData::MeshIndexData(const MeshIndexType type, const Container _type{type}, _data{(CORRADE_CONSTEXPR_ASSERT(!data.empty(), "Trade::MeshIndexData: index array can't be empty, create a non-indexed mesh instead"), data)} {} constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept: - name{name}, format{format}, data{(CORRADE_CONSTEXPR_ASSERT( + _name{name}, _format{format}, _data{(CORRADE_CONSTEXPR_ASSERT( (name == MeshAttribute::Position && (format == VertexFormat::Vector2 || format == VertexFormat::Vector3)) || @@ -1132,8 +1140,8 @@ template Containers::StridedArrayView1D MeshData::attribute(Un #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif - CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[id].format, - "Trade::MeshData::attribute(): improper type requested for" << _attributes[id].name << "of format" << _attributes[id].format, nullptr); + CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[id]._format, + "Trade::MeshData::attribute(): improper type requested for" << _attributes[id]._name << "of format" << _attributes[id]._format, nullptr); return Containers::arrayCast<1, const T>(data); } @@ -1142,8 +1150,8 @@ template Containers::StridedArrayView1D MeshData::mutableAttribute(U #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif - CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[id].format, - "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[id].name << "of format" << _attributes[id].format, nullptr); + CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[id]._format, + "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[id]._name << "of format" << _attributes[id]._format, nullptr); return Containers::arrayCast<1, T>(data); } @@ -1155,8 +1163,8 @@ template Containers::StridedArrayView1D MeshData::attribute(Me #ifndef CORRADE_NO_ASSERT const UnsignedInt attributeId = attributeFor(name, id); #endif - CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[attributeId].format, - "Trade::MeshData::attribute(): improper type requested for" << _attributes[attributeId].name << "of format" << _attributes[attributeId].format, nullptr); + CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[attributeId]._format, + "Trade::MeshData::attribute(): improper type requested for" << _attributes[attributeId]._name << "of format" << _attributes[attributeId]._format, nullptr); return Containers::arrayCast<1, const T>(data); } @@ -1168,8 +1176,8 @@ template Containers::StridedArrayView1D MeshData::mutableAttribute(M #ifndef CORRADE_NO_ASSERT const UnsignedInt attributeId = attributeFor(name, id); #endif - CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[attributeId].format, - "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[attributeId].name << "of type" << _attributes[attributeId].format, nullptr); + CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[attributeId]._format, + "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[attributeId]._name << "of type" << _attributes[attributeId]._format, nullptr); return Containers::arrayCast<1, T>(data); } diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 5d20e46f52..dffb068e50 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -329,60 +329,51 @@ constexpr Vector2 Positions[] { }; void MeshDataTest::constructAttribute() { - Containers::Array positionData{3*sizeof(Vector2)}; - auto positionView = Containers::arrayCast(positionData); - - MeshAttributeData positions{MeshAttribute::Position, positionView}; - MeshData data{MeshPrimitive::Points, std::move(positionData), {positions}}; - CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); - CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); - CORRADE_COMPARE(static_cast(data.attribute(0).data()), - positionView.data()); + const Vector2 positionData[3]; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(positionData)}; + CORRADE_COMPARE(positions.name(), MeshAttribute::Position); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); + CORRADE_VERIFY(positions.data().data() == positionData); constexpr MeshAttributeData cpositions{MeshAttribute::Position, Containers::arrayView(Positions)}; - MeshData cdata{MeshPrimitive::Points, {}, Positions, {cpositions}}; - CORRADE_COMPARE(cdata.attributeName(0), MeshAttribute::Position); - CORRADE_COMPARE(cdata.attributeFormat(0), VertexFormat::Vector2); - CORRADE_COMPARE(static_cast(cdata.attribute(0).data()), - Positions); + constexpr MeshAttribute name = cpositions.name(); + constexpr VertexFormat format = cpositions.format(); + constexpr Containers::StridedArrayView1D data = cpositions.data(); + CORRADE_COMPARE(name, MeshAttribute::Position); + CORRADE_COMPARE(format, VertexFormat::Vector2); + CORRADE_COMPARE(data.data(), Positions); } void MeshDataTest::constructAttributeCustom() { - Containers::Array idData{3*sizeof(Short)}; - auto idView = Containers::arrayCast(idData); - - MeshAttributeData ids{meshAttributeCustom(13), idView}; - MeshData data{MeshPrimitive::Points, std::move(idData), {ids}}; - CORRADE_COMPARE(data.attributeName(0), meshAttributeCustom(13)); - CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Short); - CORRADE_COMPARE(static_cast(data.attribute(0).data()), - idView.data()); + const Short idData[3]{}; + MeshAttributeData ids{meshAttributeCustom(13), Containers::arrayView(idData)}; + CORRADE_COMPARE(ids.name(), meshAttributeCustom(13)); + CORRADE_COMPARE(ids.format(), VertexFormat::Short); + CORRADE_VERIFY(ids.data().data() == idData); } void MeshDataTest::constructAttributeWrongFormat() { - Containers::Array positionData{3*sizeof(Vector2)}; + Vector2 positionData[3]; std::ostringstream out; Error redirectError{&out}; - MeshAttributeData{MeshAttribute::Color, Containers::arrayCast(positionData)}; + MeshAttributeData{MeshAttribute::Color, Containers::arrayView(positionData)}; CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n"); } void MeshDataTest::constructAttribute2D() { - Containers::Array positionData{4*sizeof(Vector2)}; + char positionData[4*sizeof(Vector2)]{}; auto positionView = Containers::StridedArrayView2D{positionData, {4, sizeof(Vector2)}}.every(2); MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, positionView}; - MeshData data{MeshPrimitive::Points, std::move(positionData), {positions}}; - CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); - CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); - CORRADE_COMPARE(static_cast(data.attribute(0).data()), - positionView.data()); + CORRADE_COMPARE(positions.name(), MeshAttribute::Position); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); + CORRADE_COMPARE(positions.data().data(), positionView.data()); } void MeshDataTest::constructAttribute2DWrongSize() { - Containers::Array positionData{4*sizeof(Vector2)}; + char positionData[4*sizeof(Vector2)]{}; std::ostringstream out; Error redirectError{&out}; @@ -393,7 +384,7 @@ void MeshDataTest::constructAttribute2DWrongSize() { } void MeshDataTest::constructAttribute2DNonContiguous() { - Containers::Array positionData{4*sizeof(Vector2)}; + char positionData[4*sizeof(Vector2)]{}; std::ostringstream out; Error redirectError{&out}; @@ -404,19 +395,15 @@ void MeshDataTest::constructAttribute2DNonContiguous() { } void MeshDataTest::constructAttributeTypeErased() { - Containers::Array positionData{3*sizeof(Vector3)}; - auto positionView = Containers::arrayCast(positionData); - - MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(Containers::stridedArrayView(positionView))}; - MeshData data{MeshPrimitive::Points, std::move(positionData), {positions}}; - CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); - CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector3); - CORRADE_COMPARE(static_cast(data.attribute(0).data()), - positionView.data()); + const Vector3 positionData[3]{}; + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(Containers::stridedArrayView(positionData))}; + CORRADE_COMPARE(positions.name(), MeshAttribute::Position); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector3); + CORRADE_VERIFY(positions.data().data() == positionData); } void MeshDataTest::constructAttributeTypeErasedWrongStride() { - Containers::Array positionData{3*sizeof(Vector3)}; + char positionData[3*sizeof(Vector3)]{}; std::ostringstream out; Error redirectError{&out}; @@ -426,10 +413,9 @@ void MeshDataTest::constructAttributeTypeErasedWrongStride() { void MeshDataTest::constructAttributeNullptr() { MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; - MeshData data{MeshPrimitive::LineLoop, nullptr, {positions}}; - CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); - CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector2); - CORRADE_VERIFY(!data.attribute(0).data()); + CORRADE_COMPARE(positions.name(), MeshAttribute::Position); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); + CORRADE_VERIFY(!positions.data().data()); } void MeshDataTest::constructAttributeNonOwningArray() { From 476497952f07bbe25896c0bbd4b03bef76b75911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 12 Jan 2020 19:46:41 +0100 Subject: [PATCH 043/107] Trade: make it possible to construct a "padding" MeshAttributeData. Will be used in MeshTools algorithms. Also harden the MeshData constructor to reject such instances. --- src/Magnum/Trade/MeshData.cpp | 3 +++ src/Magnum/Trade/MeshData.h | 10 ++++++++++ src/Magnum/Trade/Test/MeshDataTest.cpp | 26 ++++++++++++++++++++++++++ 3 files changed, 39 insertions(+) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 1217cb87fb..4d5524630d 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -88,6 +88,9 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde constructors */ for(std::size_t i = 0; i != _attributes.size(); ++i) { const MeshAttributeData& attribute = _attributes[i]; + CORRADE_ASSERT(attribute._format != VertexFormat{}, + "Trade::MeshData: attribute" << i << "doesn't specify anything", ); + const Containers::StridedArrayView1D data = Containers::arrayCast(attribute._data); CORRADE_ASSERT(data.size() == _vertexCount, "Trade::MeshData: attribute" << i << "has" << data.size() << "vertices but" << _vertexCount << "expected", ); diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index dfa8209233..ec854f501a 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -261,6 +261,16 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /** @overload */ template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {} + /** + * @brief Construct a pad value + * + * Usable in various @ref MeshTools algorithms to insert padding + * between interleaved attributes. Negative values can be used to alias + * multiple different attributes onto each other. Not meant to be + * passed to @ref MeshData. + */ + constexpr explicit MeshAttributeData(Int padding): _name{}, _format{}, _data{nullptr, 0, padding} {} + /** @brief Attribute name */ constexpr MeshAttribute name() const { return _name; } diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index dffb068e50..cf192eedf1 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -55,6 +55,7 @@ struct MeshDataTest: TestSuite::Tester { void constructAttributeTypeErased(); void constructAttributeTypeErasedWrongStride(); void constructAttributeNullptr(); + void constructAttributePadding(); void constructAttributeNonOwningArray(); void construct(); @@ -83,6 +84,7 @@ struct MeshDataTest: TestSuite::Tester { void constructVerticesNotOwnedFlagOwned(); void constructIndexlessNotOwnedFlagOwned(); void constructAttributelessNotOwnedFlagOwned(); + void constructInvalidAttributeData(); void constructCopy(); void constructMove(); @@ -150,6 +152,7 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttributeTypeErased, &MeshDataTest::constructAttributeTypeErasedWrongStride, &MeshDataTest::constructAttributeNullptr, + &MeshDataTest::constructAttributePadding, &MeshDataTest::constructAttributeNonOwningArray, &MeshDataTest::construct, @@ -180,6 +183,7 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructVerticesNotOwnedFlagOwned, &MeshDataTest::constructIndexlessNotOwnedFlagOwned, &MeshDataTest::constructAttributelessNotOwnedFlagOwned, + &MeshDataTest::constructInvalidAttributeData, &MeshDataTest::constructCopy, &MeshDataTest::constructMove, @@ -418,6 +422,15 @@ void MeshDataTest::constructAttributeNullptr() { CORRADE_VERIFY(!positions.data().data()); } +void MeshDataTest::constructAttributePadding() { + MeshAttributeData padding{-35}; + CORRADE_COMPARE(padding.name(), MeshAttribute{}); + CORRADE_COMPARE(padding.format(), VertexFormat{}); + CORRADE_COMPARE(padding.data().size(), 0); + CORRADE_COMPARE(padding.data().stride(), -35); + CORRADE_VERIFY(!padding.data()); +} + void MeshDataTest::constructAttributeNonOwningArray() { const MeshAttributeData data[3]; Containers::Array array = meshAttributeDataNonOwningArray(data); @@ -1080,6 +1093,19 @@ void MeshDataTest::constructAttributelessNotOwnedFlagOwned() { "Trade::MeshData: can't construct with non-owned index data but Trade::DataFlag::Owned\n"); } +void MeshDataTest::constructInvalidAttributeData() { + MeshAttributeData a; + MeshAttributeData b{3}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Triangles, nullptr, {a}}; + MeshData{MeshPrimitive::Triangles, nullptr, {b}}; + CORRADE_COMPARE(out.str(), + "Trade::MeshData: attribute 0 doesn't specify anything\n" + "Trade::MeshData: attribute 0 doesn't specify anything\n"); +} + void MeshDataTest::constructCopy() { CORRADE_VERIFY(!(std::is_constructible{})); CORRADE_VERIFY(!(std::is_assignable{})); From d0542267ac81827729d2a137a0d4f63c5799355e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 12 Jan 2020 19:48:17 +0100 Subject: [PATCH 044/107] Trade: direct access to MeshAttributeData array in MeshData. Again useful for MeshTools algos. --- src/Magnum/Trade/MeshData.h | 12 ++++++++++++ src/Magnum/Trade/Test/MeshDataTest.cpp | 5 +++++ 2 files changed, 17 insertions(+) diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index ec854f501a..e44a027aeb 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -611,6 +611,18 @@ class MAGNUM_TRADE_EXPORT MeshData { /** @brief Taking a view to a r-value instance is not allowed */ Containers::ArrayView mutableIndexData() && = delete; + /** + * @brief Raw attribute metadata + * + * Useful mainly for passing particular attributes to @ref MeshTools + * algorithms, everything is otherwise exposed directly through various + * `attribute*()` getters. Returns @cpp nullptr @ce if the mesh has no + * attributes. + * @see @ref attributeCount(), @ref attributeName(), + * @ref attributeFormat(), @ref attribute() + */ + Containers::ArrayView attributeData() const { return _attributes; } + /** * @brief Raw vertex data * diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index cf192eedf1..be430b8aab 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -489,6 +489,7 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(!data.attributeData().empty()); CORRADE_COMPARE(static_cast(data.indexData()), indexView.data()); CORRADE_COMPARE(static_cast(data.vertexData()), vertexView.data()); CORRADE_COMPARE(static_cast(data.mutableIndexData()), indexView.data()); @@ -684,6 +685,7 @@ void MeshDataTest::constructAttributeless() { CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_VERIFY(!data.attributeData()); CORRADE_COMPARE(data.vertexData(), nullptr); CORRADE_COMPARE(data.importerState(), &importerState); @@ -879,6 +881,7 @@ void MeshDataTest::constructAttributelessNotOwned() { CORRADE_COMPARE(data.indexDataFlags(), instanceData.dataFlags); CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_VERIFY(!data.attributeData()); CORRADE_COMPARE(data.vertexData(), nullptr); if(instanceData.dataFlags & DataFlag::Mutable) CORRADE_COMPARE(data.mutableVertexData(), nullptr); @@ -908,6 +911,7 @@ void MeshDataTest::constructIndexlessAttributeless() { CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_VERIFY(!data.attributeData()); CORRADE_COMPARE(data.indexData(), nullptr); CORRADE_COMPARE(data.vertexData(), nullptr); CORRADE_COMPARE(data.importerState(), &importerState); @@ -921,6 +925,7 @@ void MeshDataTest::constructIndexlessAttributelessZeroVertices() { int importerState; MeshData data{MeshPrimitive::TriangleStrip, 0, &importerState}; CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_VERIFY(!data.attributeData()); CORRADE_COMPARE(data.indexData(), nullptr); CORRADE_COMPARE(data.vertexData(), nullptr); CORRADE_COMPARE(data.importerState(), &importerState); From 506740f9c83d840b98c73953638b8a27e5efa0b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 19 Feb 2020 18:50:18 +0100 Subject: [PATCH 045/107] Trade: typeless access to MeshData indices. Like with attributes, it returns a 2D strided view with the second dimension having the same size as the index type. --- src/Magnum/Trade/MeshData.cpp | 34 ++++++++++++++ src/Magnum/Trade/MeshData.h | 51 ++++++++++++++++----- src/Magnum/Trade/Test/MeshDataTest.cpp | 62 ++++++++++++++++++++++++++ 3 files changed, 137 insertions(+), 10 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 4d5524630d..37b48e11e3 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -40,6 +40,16 @@ MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayVi "Trade::MeshIndexData: view size" << data.size() << "does not correspond to" << type, ); } +MeshIndexData::MeshIndexData(const Containers::StridedArrayView2D& data) noexcept { + if(data.size()[1] == 4) _type = MeshIndexType::UnsignedInt; + else if(data.size()[1] == 2) _type = MeshIndexType::UnsignedShort; + else if(data.size()[1] == 1) _type = MeshIndexType::UnsignedByte; + else CORRADE_ASSERT(false, "Trade::MeshIndexData: expected index type size 1, 2 or 4 but got" << data.size()[1], ); + + CORRADE_ASSERT(data.isContiguous(), "Trade::MeshIndexData: view is not contiguous", ); + _data = data.asContiguous(); +} + MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, format, data, nullptr} { /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert @@ -181,6 +191,30 @@ MeshIndexType MeshData::indexType() const { return _indexType; } +Containers::StridedArrayView2D MeshData::indices() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indices(): the mesh is not indexed", {}); + const std::size_t indexTypeSize = meshIndexTypeSize(_indexType); + /* Build a 2D view using information about attribute type size */ + return {_indices, {_indices.size()/indexTypeSize, indexTypeSize}}; +} + +Containers::StridedArrayView2D MeshData::mutableIndices() { + CORRADE_ASSERT(_indexDataFlags & DataFlag::Mutable, + "Trade::MeshData::mutableIndices(): index data not mutable", {}); + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::mutableIndices(): the mesh is not indexed", {}); + const std::size_t indexTypeSize = meshIndexTypeSize(_indexType); + /* Build a 2D view using information about attribute type size */ + Containers::StridedArrayView2D out{_indices, {_indices.size()/indexTypeSize, indexTypeSize}}; + /** @todo some arrayConstCast? UGH */ + return Containers::StridedArrayView2D{ + /* The view size is there only for a size assert, we're pretty sure the + view is valid */ + {static_cast(const_cast(out.data())), ~std::size_t{}}, + out.size(), out.stride()}; +} + MeshAttribute MeshData::attributeName(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeName(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index e44a027aeb..e7ab2cafc3 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -159,7 +159,7 @@ introduction. class MAGNUM_TRADE_EXPORT MeshIndexData { public: /** @brief Construct for a non-indexed mesh */ - explicit MeshIndexData() noexcept: _type{} {} + explicit MeshIndexData(std::nullptr_t = nullptr) noexcept: _type{} {} /** * @brief Construct with a runtime-specified index type @@ -185,6 +185,15 @@ class MAGNUM_TRADE_EXPORT MeshIndexData { /** @brief Construct with unsigned int indices */ constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedInt, data, nullptr} {} + /** + * @brief Constructor + * + * Expects that @p data is contiguous and size of the second dimension + * is either 1, 2 or 4, corresponding to one of the @ref MeshIndexType + * values. + */ + explicit MeshIndexData(const Containers::StridedArrayView2D& data) noexcept; + /** @brief Index type */ constexpr MeshIndexType type() const { return _type; } @@ -675,6 +684,26 @@ class MAGNUM_TRADE_EXPORT MeshData { /** * @brief Mesh indices * + * The view is guaranteed to be contiguous and its second dimension + * represents the actual data type (its size is equal to type size). + * Use the templated overload below to get the indices in a concrete + * type. + * @see @ref Corrade::Containers::StridedArrayView::isContiguous() + */ + Containers::StridedArrayView2D indices() const; + + /** + * @brief Mutable mesh indices + * + * Like @ref indices() const, but returns a mutable view. Expects that + * the mesh is mutable. + * @see @ref indexDataFlags() + */ + Containers::StridedArrayView2D mutableIndices(); + + /** + * @brief Mesh indices in a concrete type + * * Expects that the mesh is indexed and that @p T corresponds to * @ref indexType(). You can also use the non-templated * @ref indicesAsArray() accessor to get indices converted to 32-bit, @@ -685,7 +714,7 @@ class MAGNUM_TRADE_EXPORT MeshData { template Containers::ArrayView indices() const; /** - * @brief Mutable mesh indices + * @brief Mutable mesh indices in a concrete type * * Like @ref indices() const, but returns a mutable view. Expects that * the mesh is mutable. @@ -1140,21 +1169,23 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), data, nullptr} {} template Containers::ArrayView MeshData::indices() const { - CORRADE_ASSERT(isIndexed(), - "Trade::MeshData::indices(): the mesh is not indexed", {}); + Containers::StridedArrayView2D data = indices(); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif CORRADE_ASSERT(Implementation::meshIndexTypeFor() == _indexType, "Trade::MeshData::indices(): improper type requested for" << _indexType, nullptr); - return Containers::arrayCast(_indices); + return Containers::arrayCast<1, const T>(data).asContiguous(); } template Containers::ArrayView MeshData::mutableIndices() { - CORRADE_ASSERT(_indexDataFlags & DataFlag::Mutable, - "Trade::MeshData::mutableIndices(): index data not mutable", {}); - CORRADE_ASSERT(isIndexed(), - "Trade::MeshData::mutableIndices(): the mesh is not indexed", {}); + Containers::StridedArrayView2D data = mutableIndices(); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif CORRADE_ASSERT(Implementation::meshIndexTypeFor() == _indexType, "Trade::MeshData::mutableIndices(): improper type requested for" << _indexType, nullptr); - return Containers::arrayCast(reinterpret_cast&>(_indices)); + return Containers::arrayCast<1, T>(data).asContiguous(); } template Containers::StridedArrayView1D MeshData::attribute(UnsignedInt id) const { diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index be430b8aab..1b1ffd0975 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -45,6 +45,10 @@ struct MeshDataTest: TestSuite::Tester { void constructIndexZeroCount(); void constructIndexTypeErased(); void constructIndexTypeErasedWrongSize(); + void constructIndex2D(); + void constructIndex2DWrongSize(); + void constructIndex2DNonContiguous(); + void constructIndexNullptr(); void constructAttribute(); void constructAttributeCustom(); @@ -142,6 +146,10 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructIndexZeroCount, &MeshDataTest::constructIndexTypeErased, &MeshDataTest::constructIndexTypeErasedWrongSize, + &MeshDataTest::constructIndex2D, + &MeshDataTest::constructIndex2DWrongSize, + &MeshDataTest::constructIndex2DNonContiguous, + &MeshDataTest::constructIndexNullptr, &MeshDataTest::constructAttribute, &MeshDataTest::constructAttributeCustom, @@ -326,6 +334,49 @@ void MeshDataTest::constructIndexTypeErasedWrongSize() { CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: view size 6 does not correspond to MeshIndexType::UnsignedInt\n"); } +void MeshDataTest::constructIndex2D() { + { + const UnsignedByte indexData[]{25, 132, 3}; + MeshIndexData indices{Containers::arrayCast<2, const char>(Containers::stridedArrayView(indexData))}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedByte); + CORRADE_COMPARE(indices.data().data(), indexData); + } { + const UnsignedShort indexData[]{2575, 13224, 3}; + MeshIndexData indices{Containers::arrayCast<2, const char>(Containers::stridedArrayView(indexData))}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(indices.data().data(), indexData); + } { + const UnsignedInt indexData[]{2110122, 132257, 3}; + MeshIndexData indices{Containers::arrayCast<2, const char>(Containers::stridedArrayView(indexData))}; + CORRADE_COMPARE(indices.type(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(indices.data().data(), indexData); + } +} + +void MeshDataTest::constructIndex2DWrongSize() { + const char data[3*3]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshIndexData{Containers::StridedArrayView2D{data, {3, 3}}}; + CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: expected index type size 1, 2 or 4 but got 3\n"); +} + +void MeshDataTest::constructIndex2DNonContiguous() { + const char data[3*4]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshIndexData{Containers::StridedArrayView2D{data, {3, 2}, {4, 2}}}; + CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: view is not contiguous\n"); +} + +void MeshDataTest::constructIndexNullptr() { + /* Just verify it's not ambiguous */ + MeshIndexData data{nullptr}; + CORRADE_VERIFY(!data.data()); +} + constexpr Vector2 Positions[] { {1.2f, 0.2f}, {2.2f, 1.1f}, @@ -500,6 +551,13 @@ void MeshDataTest::construct() { CORRADE_VERIFY(data.isIndexed()); CORRADE_COMPARE(data.indexCount(), 6); CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + + /* Typeless index access with a cast later */ + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(data.indices())[1]), 1); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(data.indices())[3]), 0); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(data.indices())[4]), 2); + + /* Typed index access */ CORRADE_COMPARE(data.indices()[0], 0); CORRADE_COMPARE(data.indices()[2], 2); CORRADE_COMPARE(data.indices()[5], 1); @@ -1374,6 +1432,7 @@ void MeshDataTest::mutableAccessNotAllowed() { Error redirectError{&out}; data.mutableIndexData(); data.mutableVertexData(); + data.mutableIndices(); data.mutableIndices(); data.mutableAttribute(0); data.mutableAttribute(0); @@ -1383,6 +1442,7 @@ void MeshDataTest::mutableAccessNotAllowed() { "Trade::MeshData::mutableIndexData(): index data not mutable\n" "Trade::MeshData::mutableVertexData(): vertex data not mutable\n" "Trade::MeshData::mutableIndices(): index data not mutable\n" + "Trade::MeshData::mutableIndices(): index data not mutable\n" "Trade::MeshData::mutableAttribute(): vertex data not mutable\n" "Trade::MeshData::mutableAttribute(): vertex data not mutable\n" "Trade::MeshData::mutableAttribute(): vertex data not mutable\n" @@ -1396,6 +1456,7 @@ void MeshDataTest::indicesNotIndexed() { Error redirectError{&out}; data.indexCount(); data.indexType(); + data.indices(); data.indices(); data.indicesAsArray(); UnsignedInt a[1]; @@ -1404,6 +1465,7 @@ void MeshDataTest::indicesNotIndexed() { "Trade::MeshData::indexCount(): the mesh is not indexed\n" "Trade::MeshData::indexType(): the mesh is not indexed\n" "Trade::MeshData::indices(): the mesh is not indexed\n" + "Trade::MeshData::indices(): the mesh is not indexed\n" "Trade::MeshData::indicesAsArray(): the mesh is not indexed\n" "Trade::MeshData::indicesInto(): the mesh is not indexed\n"); } From 87d16bc627960ff541482d653b78f50e6b76effd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 20 Feb 2020 14:34:59 +0100 Subject: [PATCH 046/107] Trade: add a convenience MeshData::indexOffset() getter. --- src/Magnum/Trade/MeshData.cpp | 6 ++++++ src/Magnum/Trade/MeshData.h | 12 ++++++++++++ src/Magnum/Trade/Test/MeshDataTest.cpp | 11 +++++++---- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 37b48e11e3..c08ae6b2cb 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -191,6 +191,12 @@ MeshIndexType MeshData::indexType() const { return _indexType; } +std::size_t MeshData::indexOffset() const { + CORRADE_ASSERT(isIndexed(), + "Trade::MeshData::indexOffset(): the mesh is not indexed", {}); + return _indices.data() - _indexData.data(); +} + Containers::StridedArrayView2D MeshData::indices() const { CORRADE_ASSERT(isIndexed(), "Trade::MeshData::indices(): the mesh is not indexed", {}); diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index e7ab2cafc3..4ef07f9bb5 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -681,6 +681,17 @@ class MAGNUM_TRADE_EXPORT MeshData { */ MeshIndexType indexType() const; + /** + * @brief Index offset + * + * Byte offset of the first index from the beginning of the + * @ref indexData(), or a byte difference between pointers returned + * from @ref indexData() and @ref indices(). Expects that the mesh is + * indexed. + * @see @ref attributeOffset() + */ + std::size_t indexOffset() const; + /** * @brief Mesh indices * @@ -775,6 +786,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref attributeCount() const. You can also use * @ref attributeOffset(MeshAttribute, UnsignedInt) const to * directly get an offset of given named attribute. + * @see @ref indexOffset() */ std::size_t attributeOffset(UnsignedInt id) const; diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 1b1ffd0975..8d3b04fead 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -497,8 +497,8 @@ void MeshDataTest::construct() { Short id; }; - Containers::Array indexData{6*sizeof(UnsignedShort)}; - auto indexView = Containers::arrayCast(indexData); + Containers::Array indexData{8*sizeof(UnsignedShort)}; + auto indexView = Containers::arrayCast(indexData).slice(1, 7); indexView[0] = 0; indexView[1] = 1; indexView[2] = 2; @@ -541,9 +541,9 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::Triangles); CORRADE_VERIFY(!data.attributeData().empty()); - CORRADE_COMPARE(static_cast(data.indexData()), indexView.data()); + CORRADE_COMPARE(static_cast(data.indexData() + 2), indexView.data()); CORRADE_COMPARE(static_cast(data.vertexData()), vertexView.data()); - CORRADE_COMPARE(static_cast(data.mutableIndexData()), indexView.data()); + CORRADE_COMPARE(static_cast(data.mutableIndexData() + 2), indexView.data()); CORRADE_COMPARE(static_cast(data.mutableVertexData()), vertexView.data()); CORRADE_COMPARE(data.importerState(), &importerState); @@ -551,6 +551,7 @@ void MeshDataTest::construct() { CORRADE_VERIFY(data.isIndexed()); CORRADE_COMPARE(data.indexCount(), 6); CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indexOffset(), 2); /* Typeless index access with a cast later */ CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(data.indices())[1]), 1); @@ -1456,6 +1457,7 @@ void MeshDataTest::indicesNotIndexed() { Error redirectError{&out}; data.indexCount(); data.indexType(); + data.indexOffset(); data.indices(); data.indices(); data.indicesAsArray(); @@ -1464,6 +1466,7 @@ void MeshDataTest::indicesNotIndexed() { CORRADE_COMPARE(out.str(), "Trade::MeshData::indexCount(): the mesh is not indexed\n" "Trade::MeshData::indexType(): the mesh is not indexed\n" + "Trade::MeshData::indexOffset(): the mesh is not indexed\n" "Trade::MeshData::indices(): the mesh is not indexed\n" "Trade::MeshData::indices(): the mesh is not indexed\n" "Trade::MeshData::indicesAsArray(): the mesh is not indexed\n" From 270e93e134718b827c6dc5225a1eaeba7f5e84b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 19 Feb 2020 19:00:49 +0100 Subject: [PATCH 047/107] Trade: relax index/attribute/vertex count restrictions in MeshData. Allow to make: - an indexed mesh with zero indices - a mesh with non-zero attribute count but zero vertices - a mesh with non-zero vertex count but zero attributes All of these are valid use cases as explained in the tests, and will also make the release*() behavior defined better. --- src/Magnum/Trade/MeshData.cpp | 10 +-- src/Magnum/Trade/MeshData.h | 28 +++----- src/Magnum/Trade/Test/MeshDataTest.cpp | 99 +++++++++++++++++--------- 3 files changed, 80 insertions(+), 57 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index c08ae6b2cb..75e8162938 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -32,7 +32,7 @@ namespace Magnum { namespace Trade { -MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayView data) noexcept: MeshIndexData{type, data, nullptr} { +MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayView data) noexcept: _type{type}, _data{data} { /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ @@ -84,14 +84,10 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde _vertexCount = 0; } else _vertexCount = _attributes[0]._data.size(); - CORRADE_ASSERT(!_indices.empty() || !_indexData, + CORRADE_ASSERT(!_indices.empty() || _indexData.empty(), "Trade::MeshData: indexData passed for a non-indexed mesh", ); - CORRADE_ASSERT(_indices.empty() || (_indices.begin() >= _indexData.begin() && _indices.end() <= _indexData.end()), + CORRADE_ASSERT(!_indices || (_indices.begin() >= _indexData.begin() && _indices.end() <= _indexData.end()), "Trade::MeshData: indices [" << Debug::nospace << static_cast(_indices.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_indices.end()) << Debug::nospace << "] are not contained in passed indexData array [" << Debug::nospace << static_cast(_indexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_indexData.end()) << Debug::nospace << "]", ); - CORRADE_ASSERT(!_attributes.empty() || !_vertexData, - "Trade::MeshData: vertexData passed for an attribute-less mesh", ); - CORRADE_ASSERT(_vertexCount || !_vertexData, - "Trade::MeshData: vertexData passed for a mesh with zero vertices", ); #ifndef CORRADE_NO_ASSERT /* Not checking what's already checked in MeshIndexData / MeshAttributeData diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 4ef07f9bb5..cb3070760a 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -173,17 +173,21 @@ class MAGNUM_TRADE_EXPORT MeshIndexData { * @ref MeshIndexData(Containers::ArrayView) or * @ref MeshIndexData(Containers::ArrayView) * constructors, which infer the index type automatically. + * + * If @p data is empty, the mesh will be treated as indexed but with + * zero indices. To create a non-indexed mesh, use the + * @ref MeshIndexData(std::nullptr_t) constructor. */ explicit MeshIndexData(MeshIndexType type, Containers::ArrayView data) noexcept; /** @brief Construct with unsigned byte indices */ - constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedByte, data, nullptr} {} + constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: _type{MeshIndexType::UnsignedByte}, _data{data} {} /** @brief Construct with unsigned short indices */ - constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedShort, data, nullptr} {} + constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: _type{MeshIndexType::UnsignedShort}, _data{data} {} /** @brief Construct with unsigned int indices */ - constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: MeshIndexData{MeshIndexType::UnsignedInt, data, nullptr} {} + constexpr explicit MeshIndexData(Containers::ArrayView data) noexcept: _type{MeshIndexType::UnsignedInt}, _data{data} {} /** * @brief Constructor @@ -201,13 +205,6 @@ class MAGNUM_TRADE_EXPORT MeshIndexData { constexpr Containers::ArrayView data() const { return _data; } private: - /* Contains an assert common for all constexpr constructor, nullptr_t - to disambiguate from the public constructor of the same signature -- - can't delegate into that one, because it checks against - meshIndexTypeSize() that's not constexpr, and since we come from a - template, we don't need that check anyway */ - constexpr explicit MeshIndexData(MeshIndexType type, Containers::ArrayView data, std::nullptr_t); - friend MeshData; MeshIndexType _type; /* Void so the constructors can be constexpr */ @@ -371,8 +368,8 @@ class MAGNUM_TRADE_EXPORT MeshData { * The @p indices are expected to point to a sub-range of @p indexData. * The @p attributes are expected to reference (sparse) sub-ranges of * @p vertexData. If the mesh has no attributes, the @p indices are - * expected to be valid and non-empty. If you want to create an - * index-less attribute-less mesh, use + * expected to be valid (but can be empty). If you want to create an + * attribute-less non-indexed mesh, use * @ref MeshData(MeshPrimitive, UnsignedInt, const void*) to specify * desired vertex count. * @@ -515,8 +512,8 @@ class MAGNUM_TRADE_EXPORT MeshData { * * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) * with default-constructed @p vertexData and @p attributes arguments. - * The @p indices are expected to be valid and non-empty. If you want - * to create an index-less attribute-less mesh, use + * The @p indices are expected to be valid (but can be empty). If you + * want to create an attribute-less non-indexed mesh, use * @ref MeshData(MeshPrimitive, UnsignedInt, const void*) to specify * desired vertex count. * @@ -1159,9 +1156,6 @@ namespace Implementation { } #endif -constexpr MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayView data, std::nullptr_t): - _type{type}, _data{(CORRADE_CONSTEXPR_ASSERT(!data.empty(), "Trade::MeshIndexData: index array can't be empty, create a non-indexed mesh instead"), data)} {} - constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept: _name{name}, _format{format}, _data{(CORRADE_CONSTEXPR_ASSERT( (name == MeshAttribute::Position && diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 8d3b04fead..45e51067d7 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -42,7 +42,6 @@ struct MeshDataTest: TestSuite::Tester { void debugAttributeName(); void constructIndex(); - void constructIndexZeroCount(); void constructIndexTypeErased(); void constructIndexTypeErasedWrongSize(); void constructIndex2D(); @@ -63,6 +62,9 @@ struct MeshDataTest: TestSuite::Tester { void constructAttributeNonOwningArray(); void construct(); + void constructZeroIndices(); + void constructZeroAttributes(); + void constructZeroVertices(); void constructIndexless(); void constructIndexlessZeroVertices(); void constructAttributeless(); @@ -76,8 +78,6 @@ struct MeshDataTest: TestSuite::Tester { void constructAttributelessNotOwned(); void constructIndexDataButNotIndexed(); - void constructVertexDataButNoAttributes(); - void constructVertexDataButNoVertices(); void constructAttributelessInvalidIndices(); void constructIndicesNotContained(); void constructAttributeNotContained(); @@ -143,7 +143,6 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::debugAttributeName, &MeshDataTest::constructIndex, - &MeshDataTest::constructIndexZeroCount, &MeshDataTest::constructIndexTypeErased, &MeshDataTest::constructIndexTypeErasedWrongSize, &MeshDataTest::constructIndex2D, @@ -164,6 +163,9 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttributeNonOwningArray, &MeshDataTest::construct, + &MeshDataTest::constructZeroIndices, + &MeshDataTest::constructZeroAttributes, + &MeshDataTest::constructZeroVertices, &MeshDataTest::constructIndexless, &MeshDataTest::constructIndexlessZeroVertices, &MeshDataTest::constructAttributeless, @@ -179,8 +181,6 @@ MeshDataTest::MeshDataTest() { Containers::arraySize(SingleNotOwnedData)); addTests({&MeshDataTest::constructIndexDataButNotIndexed, - &MeshDataTest::constructVertexDataButNoAttributes, - &MeshDataTest::constructVertexDataButNoVertices, &MeshDataTest::constructAttributelessInvalidIndices, &MeshDataTest::constructIndicesNotContained, &MeshDataTest::constructAttributeNotContained, @@ -311,13 +311,6 @@ void MeshDataTest::constructIndex() { } } -void MeshDataTest::constructIndexZeroCount() { - std::ostringstream out; - Error redirectError{&out}; - MeshIndexData{MeshIndexType::UnsignedInt, nullptr}; - CORRADE_COMPARE(out.str(), "Trade::MeshIndexData: index array can't be empty, create a non-indexed mesh instead\n"); -} - void MeshDataTest::constructIndexTypeErased() { const char indexData[3*2]{}; MeshIndexData indices{MeshIndexType::UnsignedShort, indexData}; @@ -688,6 +681,66 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2], 22); } +void MeshDataTest::constructZeroIndices() { + /* This is a valid use case because this could be an empty slice of a + well-defined indexed mesh. Explicitly use a non-null zero-sized array + to check the importer is checking size and not pointer. */ + Containers::Array vertexData{3*sizeof(Vector3)}; + auto vertices = Containers::arrayCast(vertexData); + char i; + Containers::Array indexData{&i, 0, [](char*, std::size_t){}}; + auto indices = Containers::arrayCast(indexData); + MeshAttributeData positions{MeshAttribute::Position, vertices}; + MeshData data{MeshPrimitive::Triangles, + std::move(indexData), MeshIndexData{indices}, + std::move(vertexData), {positions}}; + + CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE(data.indexCount(), 0); + CORRADE_COMPARE(data.vertexCount(), 3); +} + +void MeshDataTest::constructZeroAttributes() { + /* This is a valid use case because e.g. the index/vertex data can be + shared by multiple meshes and this particular one is just a plain + index array */ + Containers::Array indexData{3*sizeof(UnsignedInt)}; + Containers::Array vertexData{3}; + auto indexView = Containers::arrayCast(indexData); + MeshData data{MeshPrimitive::Triangles, + std::move(indexData), MeshIndexData{indexView}, + std::move(vertexData), {}}; + + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.attributeCount(), 0); + CORRADE_VERIFY(!data.attributeData()); + CORRADE_COMPARE(data.vertexData().size(), 3); + CORRADE_COMPARE(data.vertexCount(), 0); +} + +void MeshDataTest::constructZeroVertices() { + /* This is a valid use case because this could be an empty slice of a + well-defined indexed mesh */ + Containers::Array indexData{3*sizeof(UnsignedInt)}; + auto indexView = Containers::arrayCast(indexData); + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, nullptr}; + MeshData data{MeshPrimitive::Triangles, + std::move(indexData), MeshIndexData{indexView}, + nullptr, {positions}}; + + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(data.attributeCount(), 1); + CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector3); + CORRADE_COMPARE(data.attribute(0).size(), 0); + CORRADE_VERIFY(!data.vertexData()); + CORRADE_COMPARE(data.vertexCount(), 0); +} + void MeshDataTest::constructIndexless() { Containers::Array vertexData{3*sizeof(Vector2)}; auto vertexView = Containers::arrayCast(vertexData); @@ -1004,26 +1057,6 @@ void MeshDataTest::constructIndexDataButNotIndexed() { CORRADE_COMPARE(out.str(), "Trade::MeshData: indexData passed for a non-indexed mesh\n"); } -void MeshDataTest::constructVertexDataButNoAttributes() { - Containers::Array indexData{6}; - Containers::Array vertexData{6}; - - std::ostringstream out; - Error redirectError{&out}; - MeshData{MeshPrimitive::Points, std::move(indexData), MeshIndexData{Containers::arrayCast(indexData)}, std::move(vertexData), {}}; - CORRADE_COMPARE(out.str(), "Trade::MeshData: vertexData passed for an attribute-less mesh\n"); -} - -void MeshDataTest::constructVertexDataButNoVertices() { - Containers::Array vertexData{6}; - - std::ostringstream out; - Error redirectError{&out}; - MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; - MeshData{MeshPrimitive::LineLoop, std::move(vertexData), {positions}}; - CORRADE_COMPARE(out.str(), "Trade::MeshData: vertexData passed for a mesh with zero vertices\n"); -} - void MeshDataTest::constructAttributelessInvalidIndices() { std::ostringstream out; Error redirectError{&out}; From a3c5c0052db0cd9dedb1c8be1b8800b8032c43a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 19 Feb 2020 19:02:53 +0100 Subject: [PATCH 048/107] Trade: make MeshData::release*() less brutal, add releaseAttributeData(). It only resets count of released thing to zero, not going all nuclear. Otherwise it wouldn't be possible to release attribute data and then vertex data as releasing one would wipe the other. --- src/Magnum/Trade/MeshData.cpp | 29 +++++++++---- src/Magnum/Trade/MeshData.h | 40 +++++++++++++---- src/Magnum/Trade/Test/MeshDataTest.cpp | 59 ++++++++++++++++++++++---- 3 files changed, 103 insertions(+), 25 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 75e8162938..9c149db99a 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -282,9 +282,11 @@ UnsignedInt MeshData::attributeStride(MeshAttribute name, UnsignedInt id) const Containers::StridedArrayView2D MeshData::attribute(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); - /* Build a 2D view using information about attribute type size */ + /* Build a 2D view using information about attribute type size, return only + a prefix of the actual vertex count (which is zero in case vertex data + is released) */ return Containers::arrayCast<2, const char>(_attributes[id]._data, - vertexFormatSize(_attributes[id]._format)); + vertexFormatSize(_attributes[id]._format)).prefix(_vertexCount); } Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) { @@ -292,9 +294,11 @@ Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); - /* Build a 2D view using information about attribute type size */ + /* Build a 2D view using information about attribute type size, return only + a prefix of the actual vertex count (which is zero in case vertex data + is released) */ auto out = Containers::arrayCast<2, const char>(_attributes[id]._data, - vertexFormatSize(_attributes[id]._format)); + vertexFormatSize(_attributes[id]._format)).prefix(_vertexCount); /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the @@ -458,14 +462,21 @@ Containers::Array MeshData::colorsAsArray(const UnsignedInt id) const { } Containers::Array MeshData::releaseIndexData() { - _indexType = MeshIndexType{}; /* so isIndexed() returns false */ - _indices = nullptr; - return std::move(_indexData); + _indices = {_indices.data(), 0}; + Containers::Array out = std::move(_indexData); + _indexData = Containers::Array{out.data(), 0, Implementation::nonOwnedArrayDeleter}; + return out; +} + +Containers::Array MeshData::releaseAttributeData() { + return std::move(_attributes); } Containers::Array MeshData::releaseVertexData() { - _attributes = nullptr; - return std::move(_vertexData); + _vertexCount = 0; + Containers::Array out = std::move(_vertexData); + _vertexData = Containers::Array{out.data(), 0, Implementation::nonOwnedArrayDeleter}; + return out; } Debug& operator<<(Debug& debug, const MeshAttribute value) { diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index cb3070760a..bba3ce6abd 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -1073,21 +1073,45 @@ class MAGNUM_TRADE_EXPORT MeshData { * * Releases the ownership of the index data array and resets internal * index-related state to default. The mesh then behaves like - * non-indexed. Note that the returned array has a custom no-op deleter - * when the data are not owned by the mesh, and while the returned - * array type is mutable, the actual memory might be not. + * it has zero indices (but it can still have a non-zero vertex count), + * however @ref indexData() still return a zero-sized non-null array so + * index offset calculation continues to work as expected. + * + * Note that the returned array has a custom no-op deleter when the + * data are not owned by the mesh, and while the returned array type is + * mutable, the actual memory might be not. * @see @ref indexData(), @ref indexDataFlags() */ Containers::Array releaseIndexData(); + /** + * @brief Release attribute data storage + * + * Releases the ownership of the attribute data array and resets + * internal attribute-related state to default. The mesh then behaves + * like if it has no attributes (but it can still have a non-zero + * vertex count). Note that the returned array has a custom no-op + * deleter when the data are not owned by the mesh, and while the + * returned array type is mutable, the actual memory might be not --- + * use this function only if you are sure about the origin of the + * array. + * @see @ref attributeData() + */ + Containers::Array releaseAttributeData(); + /** * @brief Release vertex data storage * - * Releases the ownership of the index data array and resets internal - * attribute-related state to default. The mesh then behaves like if - * it has no attributes. Note that the returned array has a custom - * no-op deleter when the data are not owned by the mesh, and while the - * returned array type is mutable, the actual memory might be not. + * Releases the ownership of the vertex data array and resets internal + * attribute-related state to default. The mesh then behaves like it + * has zero vertices (but it can still have a non-zero amount of + * attributes), however @ref vertexData() will still return a zero- + * sized non-null array so attribute offset calculation continues to + * work as expected. + * + * Note that the returned array has a custom no-op deleter when the + * data are not owned by the mesh, and while the returned array type is + * mutable, the actual memory might be not. * @see @ref vertexData(), @ref vertexDataFlags() */ Containers::Array releaseVertexData(); diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 45e51067d7..72f267860d 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -115,6 +115,7 @@ struct MeshDataTest: TestSuite::Tester { void attributeWrongType(); void releaseIndexData(); + void releaseAttributeData(); void releaseVertexData(); }; @@ -223,6 +224,7 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::attributeWrongType, &MeshDataTest::releaseIndexData, + &MeshDataTest::releaseAttributeData, &MeshDataTest::releaseVertexData}); } @@ -1581,19 +1583,26 @@ void MeshDataTest::attributeWrongType() { } void MeshDataTest::releaseIndexData() { - Containers::Array indexData{6}; - auto indexView = Containers::arrayCast(indexData); + Containers::Array indexData{23}; + auto indexView = Containers::arrayCast(indexData.slice(6, 12)); MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), MeshIndexData{indexView}}; CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 3); + CORRADE_COMPARE(data.indexOffset(), 6); Containers::Array released = data.releaseIndexData(); - CORRADE_COMPARE(static_cast(released.data()), indexView.data()); - CORRADE_COMPARE(data.indexData(), nullptr); - CORRADE_VERIFY(!data.isIndexed()); + CORRADE_COMPARE(static_cast(released.data() + 6), indexView.data()); + /* This is not null as we still need the value for calculating offsets */ + CORRADE_COMPARE(static_cast(data.indexData()), released.data()); + CORRADE_COMPARE(data.indexData().size(), 0); + CORRADE_VERIFY(data.isIndexed()); + CORRADE_COMPARE(data.indexCount(), 0); + CORRADE_COMPARE(data.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE(data.indexOffset(), 6); } -void MeshDataTest::releaseVertexData() { +void MeshDataTest::releaseAttributeData() { Containers::Array vertexData{16}; auto vertexView = Containers::arrayCast(vertexData); @@ -1601,9 +1610,43 @@ void MeshDataTest::releaseVertexData() { MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions, positions}}; CORRADE_COMPARE(data.attributeCount(), 2); - Containers::Array released = data.releaseVertexData(); - CORRADE_COMPARE(data.vertexData(), nullptr); + Containers::Array released = data.releaseAttributeData(); + CORRADE_COMPARE(released.size(), 2); + CORRADE_COMPARE(static_cast(released[0].data().data()), vertexView.data()); + CORRADE_COMPARE(released[0].data().size(), 2); + /* Unlike the other two, this is null as we don't need the value for + calculating anything */ + CORRADE_COMPARE(static_cast(data.attributeData()), nullptr); CORRADE_COMPARE(data.attributeCount(), 0); + CORRADE_COMPARE(static_cast(data.vertexData()), vertexView); + CORRADE_COMPARE(data.vertexCount(), 2); +} + +void MeshDataTest::releaseVertexData() { + Containers::Array vertexData{80}; + auto vertexView = Containers::arrayCast(vertexData.slice(48, 72)); + + MeshAttributeData positions{MeshAttribute::Position, vertexView}; + MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions, positions}}; + CORRADE_COMPARE(data.attributeCount(), 2); + CORRADE_COMPARE(data.vertexCount(), 3); + CORRADE_COMPARE(data.attributeOffset(0), 48); + + Containers::Array released = data.releaseVertexData(); + CORRADE_VERIFY(data.attributeData()); + CORRADE_COMPARE(data.attributeCount(), 2); + CORRADE_COMPARE(static_cast(static_cast(data.attribute(0).data())), vertexView.data()); + CORRADE_COMPARE(static_cast(static_cast(data.mutableAttribute(0).data())), vertexView.data()); + /* Returned views should be patched to have zero size (but not the direct + access, there it stays as it's an internal API really) */ + CORRADE_COMPARE(data.attribute(0).size()[0], 0); + CORRADE_COMPARE(data.mutableAttribute(0).size()[0], 0); + CORRADE_COMPARE(data.attributeData()[0].data().size(), 3); + CORRADE_COMPARE(static_cast(released.data() + 48), vertexView.data()); + /* This is not null as we still need the value for calculating offsets */ + CORRADE_COMPARE(static_cast(data.vertexData()), released.data()); + CORRADE_COMPARE(data.vertexCount(), 0); + CORRADE_COMPARE(data.attributeOffset(0), 48); } }}}} From b016258fdb44e8cdbfe91ac02f144cdb3d4e468d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 17 Jan 2020 12:46:41 +0100 Subject: [PATCH 049/107] Trade: added MeshData::attributeId(). Useful for quick localization of named attribs when dealing with attributeData() (for example in MeshTools). --- src/Magnum/Trade/MeshData.cpp | 6 ++++++ src/Magnum/Trade/MeshData.h | 9 +++++++++ src/Magnum/Trade/Test/MeshDataTest.cpp | 9 +++++++++ 3 files changed, 24 insertions(+) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 9c149db99a..f1e458bb9b 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -261,6 +261,12 @@ UnsignedInt MeshData::attributeFor(const MeshAttribute name, UnsignedInt id) con #endif } +UnsignedInt MeshData::attributeId(const MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeId(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeId; +} + VertexFormat MeshData::attributeFormat(MeshAttribute name, UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(name, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeFormat(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index bba3ce6abd..c710d4aa36 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -817,6 +817,14 @@ class MAGNUM_TRADE_EXPORT MeshData { */ UnsignedInt attributeCount(MeshAttribute name) const; + /** + * @brief Absolute ID of a named attribute + * + * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const. + */ + UnsignedInt attributeId(MeshAttribute name, UnsignedInt id = 0) const; + /** * @brief Format of a named attribute * @@ -1129,6 +1137,7 @@ class MAGNUM_TRADE_EXPORT MeshData { implementations. */ friend AbstractImporter; + /* Internal helper that doesn't assert, unlike attributeId() */ UnsignedInt attributeFor(MeshAttribute name, UnsignedInt id) const; UnsignedInt _vertexCount; diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 72f267860d..8d42f8bb67 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -628,6 +628,11 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attributeCount(meshAttributeCustom(13)), 1); CORRADE_COMPARE(data.attributeCount(MeshAttribute::Color), 0); CORRADE_COMPARE(data.attributeCount(meshAttributeCustom(23)), 0); + CORRADE_COMPARE(data.attributeId(MeshAttribute::Position), 0); + CORRADE_COMPARE(data.attributeId(MeshAttribute::Normal), 2); + CORRADE_COMPARE(data.attributeId(MeshAttribute::TextureCoordinates), 1); + CORRADE_COMPARE(data.attributeId(MeshAttribute::TextureCoordinates, 1), 3); + CORRADE_COMPARE(data.attributeId(meshAttributeCustom(13)), 4); CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position), VertexFormat::Vector3); CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Normal), @@ -1533,6 +1538,8 @@ void MeshDataTest::attributeNotFound() { data.attributeStride(2); data.attribute(2); data.attribute(2); + data.attributeId(MeshAttribute::Position); + data.attributeId(MeshAttribute::Color, 2); data.attributeFormat(MeshAttribute::Position); data.attributeFormat(MeshAttribute::Color, 2); data.attributeOffset(MeshAttribute::Position); @@ -1555,6 +1562,8 @@ void MeshDataTest::attributeNotFound() { "Trade::MeshData::attributeStride(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeId(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attributeId(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" "Trade::MeshData::attributeFormat(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attributeFormat(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" "Trade::MeshData::attributeOffset(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" From ccd3d24185434d61ed34c44815641beb9b48e836 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 19 Nov 2019 17:33:20 +0100 Subject: [PATCH 050/107] Trade: add (deprecated) implicit conversion of MeshData to MeshDataXD. --- src/Magnum/Trade/CMakeLists.txt | 12 +- src/Magnum/Trade/MeshData2D.cpp | 31 +++++ src/Magnum/Trade/MeshData2D.h | 13 ++- src/Magnum/Trade/MeshData3D.cpp | 36 ++++++ src/Magnum/Trade/MeshData3D.h | 13 ++- src/Magnum/Trade/Test/MeshData2DTest.cpp | 124 ++++++++++++++------ src/Magnum/Trade/Test/MeshData3DTest.cpp | 141 ++++++++++++++++------- 7 files changed, 287 insertions(+), 83 deletions(-) diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index 1b07fc5e11..cf4b2a93d7 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -30,8 +30,6 @@ set(MagnumTrade_SRCS ArrayAllocator.cpp Data.cpp LightData.cpp - MeshData2D.cpp - MeshData3D.cpp MeshObjectData2D.cpp MeshObjectData3D.cpp SceneData.cpp @@ -44,6 +42,16 @@ set(MagnumTrade_GracefulAssert_SRCS CameraData.cpp ImageData.cpp MeshData.cpp + + # These have to be here instead of in MagnumTrade_SRCS because they include + # MeshData.h and call (and thus instantiate) various functions with inline + # asserts. We need the linker to pick the variant with graceful asserts for + # tests, and if there would be two different copies, it may happen it picks + # the non-graceful-assert variant, causing the tests to blow up. Happens + # only on the MSVC linker, but let's be safe and do this everywhere. + MeshData2D.cpp + MeshData3D.cpp + ObjectData2D.cpp ObjectData3D.cpp PhongMaterialData.cpp) diff --git a/src/Magnum/Trade/MeshData2D.cpp b/src/Magnum/Trade/MeshData2D.cpp index 253887aeda..ecebb77460 100644 --- a/src/Magnum/Trade/MeshData2D.cpp +++ b/src/Magnum/Trade/MeshData2D.cpp @@ -25,6 +25,8 @@ #include "MeshData2D.h" +#include + #include "Magnum/Math/Color.h" namespace Magnum { namespace Trade { @@ -33,6 +35,35 @@ MeshData2D::MeshData2D(const MeshPrimitive primitive, std::vector i CORRADE_ASSERT(!_positions.empty(), "Trade::MeshData2D: no position array specified", ); } +#ifdef MAGNUM_BUILD_DEPRECATED +MeshData2D::MeshData2D(const MeshData& other): _primitive{other.primitive()}, _importerState{other.importerState()} { + /* Copy indices, if any */ + if(other.isIndexed()) { + _indices.resize(other.indexCount()); + other.indicesInto(_indices); + } + + /* Copy attributes */ + _positions.resize(other.attributeCount(MeshAttribute::Position)); + for(UnsignedInt i = 0; i != _positions.size(); ++i) { + _positions[i].resize(other.vertexCount()); + other.positions2DInto(_positions[i], i); + } + _textureCoords2D.resize(other.attributeCount(MeshAttribute::TextureCoordinates)); + for(UnsignedInt i = 0; i != _textureCoords2D.size(); ++i) { + _textureCoords2D[i].resize(other.vertexCount()); + other.textureCoordinates2DInto(_textureCoords2D[i], i); + } + _colors.resize(other.attributeCount(MeshAttribute::Color)); + for(UnsignedInt i = 0; i != _colors.size(); ++i) { + _colors[i].resize(other.vertexCount()); + other.colorsInto(_colors[i], i); + } + + CORRADE_ASSERT(!_positions.empty(), "Trade::MeshData3D: no position array specified in MeshData", ); +} +#endif + MeshData2D::MeshData2D(MeshData2D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept diff --git a/src/Magnum/Trade/MeshData2D.h b/src/Magnum/Trade/MeshData2D.h index 31f2c6d001..52170fa31f 100644 --- a/src/Magnum/Trade/MeshData2D.h +++ b/src/Magnum/Trade/MeshData2D.h @@ -31,8 +31,7 @@ #include -#include "Magnum/Magnum.h" -#include "Magnum/Trade/visibility.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Trade { @@ -67,6 +66,16 @@ class MAGNUM_TRADE_EXPORT MeshData2D { */ explicit MeshData2D(MeshPrimitive primitive, std::vector indices, std::vector> positions, std::vector> textureCoords2D, std::vector> colors, const void* importerState = nullptr); + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Construct from @ref MeshData + * @m_deprecated_since_latest Use @ref MeshData directly instead. + */ + /* No data moving can take place because std::vector is damn shitty + regarding memory ownership transfer, so it can well be a copy. */ + CORRADE_DEPRECATED("use MeshData directly instead") /*implicit*/ MeshData2D(const MeshData& other); + #endif + /** @brief Copying is not allowed */ MeshData2D(const MeshData2D&) = delete; diff --git a/src/Magnum/Trade/MeshData3D.cpp b/src/Magnum/Trade/MeshData3D.cpp index 4556256df7..edb21cd2ab 100644 --- a/src/Magnum/Trade/MeshData3D.cpp +++ b/src/Magnum/Trade/MeshData3D.cpp @@ -25,6 +25,8 @@ #include "MeshData3D.h" +#include + #include "Magnum/Math/Color.h" namespace Magnum { namespace Trade { @@ -33,6 +35,40 @@ MeshData3D::MeshData3D(const MeshPrimitive primitive, std::vector i CORRADE_ASSERT(!_positions.empty(), "Trade::MeshData3D: no position array specified", ); } +#ifdef MAGNUM_BUILD_DEPRECATED +MeshData3D::MeshData3D(const MeshData& other): _primitive{other.primitive()}, _importerState{other.importerState()} { + /* Copy indices, if any */ + if(other.isIndexed()) { + _indices.resize(other.indexCount()); + other.indicesInto(_indices); + } + + /* Copy attributes */ + _positions.resize(other.attributeCount(MeshAttribute::Position)); + for(UnsignedInt i = 0; i != _positions.size(); ++i) { + _positions[i].resize(other.vertexCount()); + other.positions3DInto(_positions[i], i); + } + _normals.resize(other.attributeCount(MeshAttribute::Normal)); + for(UnsignedInt i = 0; i != _normals.size(); ++i) { + _normals[i].resize(other.vertexCount()); + other.normalsInto(_normals[i], i); + } + _textureCoords2D.resize(other.attributeCount(MeshAttribute::TextureCoordinates)); + for(UnsignedInt i = 0; i != _textureCoords2D.size(); ++i) { + _textureCoords2D[i].resize(other.vertexCount()); + other.textureCoordinates2DInto(_textureCoords2D[i], i); + } + _colors.resize(other.attributeCount(MeshAttribute::Color)); + for(UnsignedInt i = 0; i != _colors.size(); ++i) { + _colors[i].resize(other.vertexCount()); + other.colorsInto(_colors[i], i); + } + + CORRADE_ASSERT(!_positions.empty(), "Trade::MeshData3D: no position array specified in MeshData", ); +} +#endif + MeshData3D::MeshData3D(MeshData3D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept diff --git a/src/Magnum/Trade/MeshData3D.h b/src/Magnum/Trade/MeshData3D.h index 0dea375798..c17416783b 100644 --- a/src/Magnum/Trade/MeshData3D.h +++ b/src/Magnum/Trade/MeshData3D.h @@ -31,8 +31,7 @@ #include -#include "Magnum/Magnum.h" -#include "Magnum/Trade/visibility.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Trade { @@ -68,6 +67,16 @@ class MAGNUM_TRADE_EXPORT MeshData3D { */ explicit MeshData3D(MeshPrimitive primitive, std::vector indices, std::vector> positions, std::vector> normals, std::vector> textureCoords2D, std::vector> colors, const void* importerState = nullptr); + #ifdef MAGNUM_BUILD_DEPRECATED + /** + * @brief Construct from @ref MeshData + * @m_deprecated_since_latest Use @ref MeshData directly instead. + */ + /* No data moving can take place because std::vector is damn shitty + regarding memory ownership transfer, so it can well be a copy. */ + CORRADE_DEPRECATED("use MeshData directly instead") /*implicit*/ MeshData3D(const MeshData& other); + #endif + /** @brief Copying is not allowed */ MeshData3D(const MeshData3D&) = delete; diff --git a/src/Magnum/Trade/Test/MeshData2DTest.cpp b/src/Magnum/Trade/Test/MeshData2DTest.cpp index 315e8db643..34a38b0c8d 100644 --- a/src/Magnum/Trade/Test/MeshData2DTest.cpp +++ b/src/Magnum/Trade/Test/MeshData2DTest.cpp @@ -42,59 +42,111 @@ struct MeshData2DTest: TestSuite::Tester { void constructMove(); }; +using namespace Math::Literals; + +const UnsignedByte Indices[]{12, 1, 0}; +const struct Vertex { + Vector2 position1, position2; + Vector2 textureCoords1, textureCoords2, textureCoords3; + Color4 color; +} Vertices[] { + {{0.5f, 1.0f}, {1.4f, 0.2f}, + {0.0f, 0.0f}, {0.1f, 0.2f}, {0.0f, 0.0f}, + 0xff98ab_rgbf}, + {{-1.0f, 0.3f}, {1.1f, 0.13f}, + {0.3f, 0.7f}, {0.7f, 1.0f}, {1.0f, 1.0f}, + 0xff3366_rgbf} +}; +const int State = 3; + +CORRADE_IGNORE_DEPRECATED_PUSH +struct { + const char* name; + const MeshData2D data, dataNonIndexed; +} ConstructData[] { + {"", + MeshData2D{MeshPrimitive::Lines, {12, 1, 0}, + {{{0.5f, 1.0f}, {-1.0f, 0.3f}}, + {{1.4f, 0.2f}, {1.1f, 0.13f}}}, + {{{0.0f, 0.0f}, {0.3f, 0.7f}}, + {{0.1f, 0.2f}, {0.7f, 1.0f}}, + {{0.0f, 0.0f}, {1.0f, 1.0f}}}, + {{0xff98ab_rgbf, 0xff3366_rgbf}}, + &State}, + MeshData2D{MeshPrimitive::Lines, {}, + {{{0.5f, 1.0f}, {-1.0f, 0.3f}}}, + {{{0.0f, 0.0f}, {0.3f, 0.7f}}}, + {{0xff98ab_rgbf, 0xff3366_rgbf}}, + &State}}, + {"from MeshData", + MeshData{MeshPrimitive::Lines, {}, Indices, MeshIndexData{Indices}, {}, Vertices, { + MeshAttributeData{MeshAttribute::Position, + Containers::StridedArrayView1D{Vertices, &Vertices[0].position1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Position, + Containers::StridedArrayView1D{Vertices, &Vertices[0].position2, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords2, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords3, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Color, + Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, + }, &State}, + MeshData{MeshPrimitive::Lines, {}, Vertices, { + MeshAttributeData{MeshAttribute::Position, + Containers::StridedArrayView1D{Vertices, &Vertices[0].position1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Color, + Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, + }, &State} + } +}; +CORRADE_IGNORE_DEPRECATED_POP + MeshData2DTest::MeshData2DTest() { - addTests({&MeshData2DTest::construct, - &MeshData2DTest::constructNonIndexed, - &MeshData2DTest::constructNoTexCoords, + addInstancedTests({&MeshData2DTest::construct, + &MeshData2DTest::constructNonIndexed}, + Containers::arraySize(ConstructData)); + + addTests({&MeshData2DTest::constructNoTexCoords, &MeshData2DTest::constructNoColors, &MeshData2DTest::constructCopy, &MeshData2DTest::constructMove}); } -using namespace Math::Literals; - void MeshData2DTest::construct() { - const int a{}; - const MeshData2D data{MeshPrimitive::Lines, {12, 1, 0}, - {{{0.5f, 1.0f}, {-1.0f, 0.3f}}, - {{1.4f, 0.2f}, {1.1f, 0.13f}}}, - {{{0.0f, 0.0f}, {0.3f, 0.7f}}, - {{0.1f, 0.2f}, {0.7f, 1.0f}}, - {{0.0f, 0.0f}, {1.0f, 1.0f}}}, - {{0xff98ab_rgbf, 0xff3366_rgbf}}, - &a}; + auto&& data = ConstructData[testCaseInstanceId()]; + setTestCaseDescription(data.name); - CORRADE_COMPARE(data.primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(data.data.primitive(), MeshPrimitive::Lines); - CORRADE_VERIFY(data.isIndexed()); - CORRADE_COMPARE(data.indices(), (std::vector{12, 1, 0})); + CORRADE_VERIFY(data.data.isIndexed()); + CORRADE_COMPARE(data.data.indices(), (std::vector{12, 1, 0})); - CORRADE_COMPARE(data.positionArrayCount(), 2); - CORRADE_COMPARE(data.positions(0), (std::vector{{0.5f, 1.0f}, {-1.0f, 0.3f}})); - CORRADE_COMPARE(data.positions(1), (std::vector{{1.4f, 0.2f}, {1.1f, 0.13f}})); + CORRADE_COMPARE(data.data.positionArrayCount(), 2); + CORRADE_COMPARE(data.data.positions(0), (std::vector{{0.5f, 1.0f}, {-1.0f, 0.3f}})); + CORRADE_COMPARE(data.data.positions(1), (std::vector{{1.4f, 0.2f}, {1.1f, 0.13f}})); - CORRADE_VERIFY(data.hasTextureCoords2D()); - CORRADE_COMPARE(data.textureCoords2DArrayCount(), 3); - CORRADE_COMPARE(data.textureCoords2D(0), (std::vector{{0.0f, 0.0f}, {0.3f, 0.7f}})); - CORRADE_COMPARE(data.textureCoords2D(1), (std::vector{{0.1f, 0.2f}, {0.7f, 1.0f}})); - CORRADE_COMPARE(data.textureCoords2D(2), (std::vector{{0.0f, 0.0f}, {1.0f, 1.0f}})); + CORRADE_VERIFY(data.data.hasTextureCoords2D()); + CORRADE_COMPARE(data.data.textureCoords2DArrayCount(), 3); + CORRADE_COMPARE(data.data.textureCoords2D(0), (std::vector{{0.0f, 0.0f}, {0.3f, 0.7f}})); + CORRADE_COMPARE(data.data.textureCoords2D(1), (std::vector{{0.1f, 0.2f}, {0.7f, 1.0f}})); + CORRADE_COMPARE(data.data.textureCoords2D(2), (std::vector{{0.0f, 0.0f}, {1.0f, 1.0f}})); - CORRADE_VERIFY(data.hasColors()); - CORRADE_COMPARE(data.colorArrayCount(), 1); - CORRADE_COMPARE(data.colors(0), (std::vector{0xff98ab_rgbf, 0xff3366_rgbf})); + CORRADE_VERIFY(data.data.hasColors()); + CORRADE_COMPARE(data.data.colorArrayCount(), 1); + CORRADE_COMPARE(data.data.colors(0), (std::vector{0xff98ab_rgbf, 0xff3366_rgbf})); - CORRADE_COMPARE(data.importerState(), &a); + CORRADE_COMPARE(data.data.importerState(), &State); } void MeshData2DTest::constructNonIndexed() { - const int a{}; - const MeshData2D data{MeshPrimitive::Lines, {}, - {{{0.5f, 1.0f}, {-1.0f, 0.3f}}}, - {{{0.0f, 0.0f}, {0.3f, 0.7f}}}, - {{0xff98ab_rgbf, 0xff3366_rgbf}}, - &a}; + auto&& data = ConstructData[testCaseInstanceId()]; + setTestCaseDescription(data.name); - CORRADE_VERIFY(!data.isIndexed()); + CORRADE_VERIFY(!data.dataNonIndexed.isIndexed()); } void MeshData2DTest::constructNoTexCoords() { diff --git a/src/Magnum/Trade/Test/MeshData3DTest.cpp b/src/Magnum/Trade/Test/MeshData3DTest.cpp index 937e3ccd17..1244120ad1 100644 --- a/src/Magnum/Trade/Test/MeshData3DTest.cpp +++ b/src/Magnum/Trade/Test/MeshData3DTest.cpp @@ -43,66 +43,125 @@ struct MeshData3DTest: TestSuite::Tester { void constructMove(); }; +using namespace Math::Literals; + +const UnsignedByte Indices[]{12, 1, 0}; +const struct Vertex { + Vector3 position1, position2; + Vector3 normal; + Vector2 textureCoords1, textureCoords2, textureCoords3; + Color4 color; +} Vertices[] { + {{0.5f, 1.0f, 0.1f}, {1.4f, 0.2f, 0.5f}, + {0.0f, 1.0f, 0.0f}, + {0.0f, 0.0f}, {0.1f, 0.2f}, {0.0f, 0.0f}, + 0xff98ab_rgbf}, + {{-1.0f, 0.3f, -1.0f}, {1.1f, 0.13f, -0.3f}, + {-1.0f, 0.0f, 0.0f}, + {0.3f, 0.7f}, {0.7f, 1.0f}, {1.0f, 1.0f}, + 0xff3366_rgbf} +}; +const int State = 3; + +CORRADE_IGNORE_DEPRECATED_PUSH +struct { + const char* name; + const MeshData3D data, dataNonIndexed; +} ConstructData[] { + {"", + MeshData3D{MeshPrimitive::Lines, {12, 1, 0}, + {{{0.5f, 1.0f, 0.1f}, {-1.0f, 0.3f, -1.0f}}, + {{1.4f, 0.2f, 0.5f}, {1.1f, 0.13f, -0.3f}}}, + {{{0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}}, + {{{0.0f, 0.0f}, {0.3f, 0.7f}}, + {{0.1f, 0.2f}, {0.7f, 1.0f}}, + {{0.0f, 0.0f}, {1.0f, 1.0f}}}, + {{0xff98ab_rgbf, 0xff3366_rgbf}}, + &State}, + MeshData3D{MeshPrimitive::Lines, {}, + {{{0.5f, 1.0f, 0.1f}, {-1.0f, 0.3f, -1.0f}}}, + {{{0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}}, + {{{0.0f, 0.0f}, {0.3f, 0.7f}}}, + {{0xff98ab_rgbf, 0xff3366_rgbf}}, + &State}}, + {"from MeshData", + MeshData{MeshPrimitive::Lines, {}, Indices, MeshIndexData{Indices}, {}, Vertices, { + MeshAttributeData{MeshAttribute::Position, + Containers::StridedArrayView1D{Vertices, &Vertices[0].position1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Position, + Containers::StridedArrayView1D{Vertices, &Vertices[0].position2, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Normal, + Containers::StridedArrayView1D{Vertices, &Vertices[0].normal, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords2, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords3, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Color, + Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, + }, &State}, + MeshData{MeshPrimitive::Lines, {}, Vertices, { + MeshAttributeData{MeshAttribute::Position, + Containers::StridedArrayView1D{Vertices, &Vertices[0].position1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Normal, + Containers::StridedArrayView1D{Vertices, &Vertices[0].normal, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords1, 2, sizeof(Vertex)}}, + MeshAttributeData{MeshAttribute::Color, + Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, + }, &State} + } +}; +CORRADE_IGNORE_DEPRECATED_POP + MeshData3DTest::MeshData3DTest() { - addTests({&MeshData3DTest::construct, - &MeshData3DTest::constructNonIndexed, - &MeshData3DTest::constructNoNormals, + addInstancedTests({&MeshData3DTest::construct, + &MeshData3DTest::constructNonIndexed}, + Containers::arraySize(ConstructData)); + + addTests({&MeshData3DTest::constructNoNormals, &MeshData3DTest::constructNoTexCoords, &MeshData3DTest::constructNoColors, &MeshData3DTest::constructCopy, &MeshData3DTest::constructMove}); } -using namespace Math::Literals; - void MeshData3DTest::construct() { - const int a{}; - const MeshData3D data{MeshPrimitive::Lines, {12, 1, 0}, - {{{0.5f, 1.0f, 0.1f}, {-1.0f, 0.3f, -1.0f}}, - {{1.4f, 0.2f, 0.5f}, {1.1f, 0.13f, -0.3f}}}, - {{{0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}}, - {{{0.0f, 0.0f}, {0.3f, 0.7f}}, - {{0.1f, 0.2f}, {0.7f, 1.0f}}, - {{0.0f, 0.0f}, {1.0f, 1.0f}}}, - {{0xff98ab_rgbf, 0xff3366_rgbf}}, - &a}; + auto&& data = ConstructData[testCaseInstanceId()]; + setTestCaseDescription(data.name); - CORRADE_COMPARE(data.primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(data.data.primitive(), MeshPrimitive::Lines); - CORRADE_VERIFY(data.isIndexed()); - CORRADE_COMPARE(data.indices(), (std::vector{12, 1, 0})); + CORRADE_VERIFY(data.data.isIndexed()); + CORRADE_COMPARE(data.data.indices(), (std::vector{12, 1, 0})); - CORRADE_COMPARE(data.positionArrayCount(), 2); - CORRADE_COMPARE(data.positions(0), (std::vector{{0.5f, 1.0f, 0.1f}, {-1.0f, 0.3f, -1.0f}})); - CORRADE_COMPARE(data.positions(1), (std::vector{{1.4f, 0.2f, 0.5f}, {1.1f, 0.13f, -0.3f}})); + CORRADE_COMPARE(data.data.positionArrayCount(), 2); + CORRADE_COMPARE(data.data.positions(0), (std::vector{{0.5f, 1.0f, 0.1f}, {-1.0f, 0.3f, -1.0f}})); + CORRADE_COMPARE(data.data.positions(1), (std::vector{{1.4f, 0.2f, 0.5f}, {1.1f, 0.13f, -0.3f}})); - CORRADE_VERIFY(data.hasNormals()); - CORRADE_COMPARE(data.normalArrayCount(), 1); - CORRADE_COMPARE(data.normals(0), (std::vector{{0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}})); + CORRADE_VERIFY(data.data.hasNormals()); + CORRADE_COMPARE(data.data.normalArrayCount(), 1); + CORRADE_COMPARE(data.data.normals(0), (std::vector{{0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}})); - CORRADE_VERIFY(data.hasTextureCoords2D()); - CORRADE_COMPARE(data.textureCoords2DArrayCount(), 3); - CORRADE_COMPARE(data.textureCoords2D(0), (std::vector{{0.0f, 0.0f}, {0.3f, 0.7f}})); - CORRADE_COMPARE(data.textureCoords2D(1), (std::vector{{0.1f, 0.2f}, {0.7f, 1.0f}})); - CORRADE_COMPARE(data.textureCoords2D(2), (std::vector{{0.0f, 0.0f}, {1.0f, 1.0f}})); + CORRADE_VERIFY(data.data.hasTextureCoords2D()); + CORRADE_COMPARE(data.data.textureCoords2DArrayCount(), 3); + CORRADE_COMPARE(data.data.textureCoords2D(0), (std::vector{{0.0f, 0.0f}, {0.3f, 0.7f}})); + CORRADE_COMPARE(data.data.textureCoords2D(1), (std::vector{{0.1f, 0.2f}, {0.7f, 1.0f}})); + CORRADE_COMPARE(data.data.textureCoords2D(2), (std::vector{{0.0f, 0.0f}, {1.0f, 1.0f}})); - CORRADE_VERIFY(data.hasColors()); - CORRADE_COMPARE(data.colorArrayCount(), 1); - CORRADE_COMPARE(data.colors(0), (std::vector{0xff98ab_rgbf, 0xff3366_rgbf})); + CORRADE_VERIFY(data.data.hasColors()); + CORRADE_COMPARE(data.data.colorArrayCount(), 1); + CORRADE_COMPARE(data.data.colors(0), (std::vector{0xff98ab_rgbf, 0xff3366_rgbf})); - CORRADE_COMPARE(data.importerState(), &a); + CORRADE_COMPARE(data.data.importerState(), &State); } void MeshData3DTest::constructNonIndexed() { - const int a{}; - const MeshData3D data{MeshPrimitive::Lines, {}, - {{{0.5f, 1.0f, 0.1f}, {-1.0f, 0.3f, -1.0f}}}, - {{{0.0f, 1.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}}}, - {{{0.0f, 0.0f}, {0.3f, 0.7f}}}, - {{0xff98ab_rgbf, 0xff3366_rgbf}}, - &a}; + auto&& data = ConstructData[testCaseInstanceId()]; + setTestCaseDescription(data.name); - CORRADE_VERIFY(!data.isIndexed()); + CORRADE_VERIFY(!data.dataNonIndexed.isIndexed()); } void MeshData3DTest::constructNoNormals() { From 3784dea7c94dec9ba32f0b20851dadd980ba33d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 15 Jan 2020 21:45:39 +0100 Subject: [PATCH 051/107] MeshTools: implement isInterleaved() for Trade::MeshData. Will be used to distinguish if the data need to be repacked or not in various tools. --- doc/changelog.dox | 2 + src/Magnum/MeshTools/CMakeLists.txt | 1 + src/Magnum/MeshTools/Interleave.cpp | 52 ++++++++ src/Magnum/MeshTools/Interleave.h | 17 ++- src/Magnum/MeshTools/Test/CMakeLists.txt | 2 +- src/Magnum/MeshTools/Test/InterleaveTest.cpp | 128 ++++++++++++++++++- 6 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 src/Magnum/MeshTools/Interleave.cpp diff --git a/doc/changelog.dox b/doc/changelog.dox index 0e293d4b0d..80c3892ecf 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -109,6 +109,8 @@ See also: @subsubsection changelog-latest-new-meshtools MeshTools library +- New @ref MeshTools::isInterleaved() utility for checking if + @ref Trade::MeshData is interleaved - Added @ref MeshTools::subdivideInPlace() for allocation-less mesh subdivision - New @ref MeshTools::removeDuplicatesInPlace() variant that works on diff --git a/src/Magnum/MeshTools/CMakeLists.txt b/src/Magnum/MeshTools/CMakeLists.txt index a1a7c4d475..51d3b4727f 100644 --- a/src/Magnum/MeshTools/CMakeLists.txt +++ b/src/Magnum/MeshTools/CMakeLists.txt @@ -25,6 +25,7 @@ # Files shared between main library and unit test library set(MagnumMeshTools_SRCS + Interleave.cpp Tipsify.cpp) # Files compiled with different flags for main library and unit test library diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp new file mode 100644 index 0000000000..0a6d4d150b --- /dev/null +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -0,0 +1,52 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Interleave.h" + +#include "Magnum/Math/Functions.h" +#include "Magnum/Trade/MeshData.h" + +namespace Magnum { namespace MeshTools { + +bool isInterleaved(const Trade::MeshData& data) { + /* There is nothing, so yes it is (because there is nothing we could do + to make it interleaved anyway) */ + if(!data.attributeCount()) return true; + + const UnsignedInt stride = data.attributeStride(0); + std::size_t minOffset = data.attributeOffset(0); + std::size_t maxOffset = minOffset; + for(UnsignedInt i = 1; i != data.attributeCount(); ++i) { + if(data.attributeStride(i) != stride) return false; + + const std::size_t offset = data.attributeOffset(i); + minOffset = Math::min(minOffset, offset); + maxOffset = Math::max(maxOffset, offset + vertexFormatSize(data.attributeFormat(i))); + } + + return maxOffset - minOffset <= stride; +} + +}} diff --git a/src/Magnum/MeshTools/Interleave.h b/src/Magnum/MeshTools/Interleave.h index c95b19afda..623c4345b6 100644 --- a/src/Magnum/MeshTools/Interleave.h +++ b/src/Magnum/MeshTools/Interleave.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::interleave(), @ref Magnum::MeshTools::interleaveInto() + * @brief Function @ref Magnum::MeshTools::interleave(), @ref Magnum::MeshTools::interleaveInto(), @ref Magnum::MeshTools::isInterleaved() */ #include @@ -35,6 +35,8 @@ #include #include "Magnum/Magnum.h" +#include "Magnum/MeshTools/visibility.h" +#include "Magnum/Trade/Trade.h" namespace Magnum { namespace MeshTools { @@ -184,6 +186,19 @@ template void interleaveInto(Containers::ArrayView bu Implementation::writeInterleaved(stride, buffer.begin(), first, next...); } +/** +@brief If the mesh data is interleaved +@m_since_latest + +Returns @cpp true @ce if all attributes have the same stride and the difference +between minimal and maximal offset is not larger than the stride, @cpp false @ce +otherwise. In particular, returns @cpp true @ce also if the mesh has just one +or no attributes. +@see @ref Trade::MeshData::attributeStride(), + @ref Trade::MeshData::attributeOffset() +*/ +MAGNUM_MESHTOOLS_EXPORT bool isInterleaved(const Trade::MeshData& data); + }} #endif diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index 8c49708bdd..aa15700bc2 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -28,7 +28,7 @@ corrade_add_test(MeshToolsCompressIndicesTest CompressIndicesTest.cpp LIBRARIES corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsFlipNormalsTest FlipNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsGenerateNormalsTest GenerateNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib MagnumPrimitives) -corrade_add_test(MeshToolsInterleaveTest InterleaveTest.cpp LIBRARIES Magnum) +corrade_add_test(MeshToolsInterleaveTest InterleaveTest.cpp LIBRARIES MagnumMeshTools) corrade_add_test(MeshToolsRemoveDuplicatesTest RemoveDuplicatesTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsSubdivideTest SubdivideTest.cpp LIBRARIES Magnum) corrade_add_test(MeshToolsTipsifyTest TipsifyTest.cpp LIBRARIES MagnumMeshTools) diff --git a/src/Magnum/MeshTools/Test/InterleaveTest.cpp b/src/Magnum/MeshTools/Test/InterleaveTest.cpp index 89de1aee88..8cb7267d61 100644 --- a/src/Magnum/MeshTools/Test/InterleaveTest.cpp +++ b/src/Magnum/MeshTools/Test/InterleaveTest.cpp @@ -30,7 +30,9 @@ #include #include +#include "Magnum/Math/Vector3.h" #include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { @@ -45,6 +47,14 @@ struct InterleaveTest: Corrade::TestSuite::Tester { void writeGaps(); void interleaveInto(); + + void isInterleaved(); + void isInterleavedEmpty(); + void isInterleavedSingleAttribute(); + void isInterleavedGaps(); + void isInterleavedAliased(); + void isInterleavedUnordered(); + void isInterleavedAttributeAcrossStride(); }; InterleaveTest::InterleaveTest() { @@ -55,7 +65,15 @@ InterleaveTest::InterleaveTest() { &InterleaveTest::write, &InterleaveTest::writeGaps, - &InterleaveTest::interleaveInto}); + &InterleaveTest::interleaveInto, + + &InterleaveTest::isInterleaved, + &InterleaveTest::isInterleavedEmpty, + &InterleaveTest::isInterleavedSingleAttribute, + &InterleaveTest::isInterleavedGaps, + &InterleaveTest::isInterleavedAliased, + &InterleaveTest::isInterleavedUnordered, + &InterleaveTest::isInterleavedAttributeAcrossStride}); } void InterleaveTest::attributeCount() { @@ -159,6 +177,114 @@ void InterleaveTest::interleaveInto() { } } +void InterleaveTest::isInterleaved() { + /* Interleaved; testing also initial offset */ + { + Containers::Array vertexData{100 + 3*20}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + 100), 3, 20}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + 100 + 8), 3, 20}}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + } + + /* One after another */ + { + Containers::Array vertexData{100 + 3*20}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.suffix(100).prefix(3*8))}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::arrayCast(vertexData.suffix(100).suffix(3*8))}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(!MeshTools::isInterleaved(data)); + } +} + +void InterleaveTest::isInterleavedEmpty() { + Trade::MeshData data{MeshPrimitive::Triangles, 5}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); +} + +void InterleaveTest::isInterleavedSingleAttribute() { + Containers::Array vertexData{3*8}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.prefix(3*8))}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {positions}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); +} + +void InterleaveTest::isInterleavedGaps() { + Containers::Array vertexData{3*40}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + 5), 3, 40}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + 24), 3, 40}}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); +} + +void InterleaveTest::isInterleavedAliased() { + /* Normals share first two components with positions */ + Containers::Array vertexData{3*12}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data()), 3, 12}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data()), 3, 12}}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); +} + +void InterleaveTest::isInterleavedUnordered() { + Containers::Array vertexData{3*12}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data()), 3, 12}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data()), 3, 12}}; + + /* Normals specified first even though they're ordered after positions */ + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {normals, positions}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); +} + +void InterleaveTest::isInterleavedAttributeAcrossStride() { + /* Data slightly larger */ + Containers::Array vertexData{5 + 3*30 + 3}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + 5), 3, 30}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, + /* 23 + 12 is 35, which still fits into the stride after + subtracting the initial offset; 24 not */ + reinterpret_cast(vertexData.data() + 23), 3, 30}}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), + {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + + vertexData = data.releaseVertexData(); + Trade::MeshAttributeData normals2{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, + reinterpret_cast(vertexData.data() + 24), 3, 30}}; + Trade::MeshData data2{MeshPrimitive::Triangles, + std::move(vertexData), {positions, normals2}}; + CORRADE_VERIFY(!MeshTools::isInterleaved(data2)); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::InterleaveTest) From 1e85279872d4c79532e61d08d0aa819c690e3f19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 16 Jan 2020 20:20:31 +0100 Subject: [PATCH 052/107] MeshTools: implement interleavedLayout(). This was originally meant to be an interleave() that operates on MeshData, but later I realized I need the same logic in duplicate(), so turned it into a private function. Now I am pretty sure I'll be using this function in *many* importer plugins :D --- doc/changelog.dox | 2 + doc/snippets/MagnumMeshTools.cpp | 35 +++ src/Magnum/MeshTools/CMakeLists.txt | 2 +- src/Magnum/MeshTools/Interleave.cpp | 85 ++++++ src/Magnum/MeshTools/Interleave.h | 37 ++- src/Magnum/MeshTools/Test/CMakeLists.txt | 2 +- src/Magnum/MeshTools/Test/InterleaveTest.cpp | 283 ++++++++++++++++++- 7 files changed, 442 insertions(+), 4 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 80c3892ecf..77eaf76034 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -111,6 +111,8 @@ See also: - New @ref MeshTools::isInterleaved() utility for checking if @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::subdivideInPlace() for allocation-less mesh subdivision - New @ref MeshTools::removeDuplicatesInPlace() variant that works on diff --git a/doc/snippets/MagnumMeshTools.cpp b/doc/snippets/MagnumMeshTools.cpp index e3e64c468d..824fcf821f 100644 --- a/doc/snippets/MagnumMeshTools.cpp +++ b/doc/snippets/MagnumMeshTools.cpp @@ -32,6 +32,7 @@ #include "Magnum/MeshTools/Interleave.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Transform.h" +#include "Magnum/Trade/MeshData.h" using namespace Magnum; using namespace Magnum::Math::Literals; @@ -96,6 +97,40 @@ auto data = MeshTools::interleave(positions, weights, 2, vertexColors, 1); /* [interleave2] */ } +{ +Trade::MeshData data{MeshPrimitive::Lines, 0}; +UnsignedInt vertexCount{}; +Containers::Array indexData; +/* [interleavedLayout-extra] */ +Containers::ArrayView attributes = + data.attributeData(); + +/* Take just positions and normals and add a four-byte padding in between */ +Trade::MeshData layout = MeshTools::interleavedLayout( + Trade::MeshData{MeshPrimitive::Triangles, 0}, vertexCount, { + attributes[data.attributeId(Trade::MeshAttribute::Position)], + Trade::MeshAttributeData{4}, + attributes[data.attributeId(Trade::MeshAttribute::Normal)] + }); +/* [interleavedLayout-extra] */ +} + +{ +Trade::MeshData data{MeshPrimitive::Lines, 0}; +Containers::ArrayView extraAttributes; +UnsignedInt vertexCount{}; +Containers::Array indexData; +/* [interleavedLayout-indices] */ +Trade::MeshData layout = + MeshTools::interleavedLayout(data, vertexCount, extraAttributes); + +Trade::MeshIndexData indices; +Trade::MeshData indexed{data.primitive(), + std::move(indexData), indices, + layout.releaseVertexData(), layout.releaseAttributeData()}; +/* [interleavedLayout-indices] */ +} + { /* [removeDuplicates] */ Containers::ArrayView data; diff --git a/src/Magnum/MeshTools/CMakeLists.txt b/src/Magnum/MeshTools/CMakeLists.txt index 51d3b4727f..be538c5e9f 100644 --- a/src/Magnum/MeshTools/CMakeLists.txt +++ b/src/Magnum/MeshTools/CMakeLists.txt @@ -25,7 +25,6 @@ # Files shared between main library and unit test library set(MagnumMeshTools_SRCS - Interleave.cpp Tipsify.cpp) # Files compiled with different flags for main library and unit test library @@ -35,6 +34,7 @@ set(MagnumMeshTools_GracefulAssert_SRCS Duplicate.cpp FlipNormals.cpp GenerateNormals.cpp + Interleave.cpp RemoveDuplicates.cpp) set(MagnumMeshTools_HEADERS diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp index 0a6d4d150b..75db8a67fc 100644 --- a/src/Magnum/MeshTools/Interleave.cpp +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -49,4 +49,89 @@ bool isInterleaved(const Trade::MeshData& data) { return maxOffset - minOffset <= stride; } +Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt vertexCount, const Containers::ArrayView extra) { + /* If there are no attributes, bail -- return an empty mesh with desired + vertex count but nothing else */ + if(!data.attributeCount() && extra.empty()) + return Trade::MeshData{data.primitive(), vertexCount}; + + const bool interleaved = isInterleaved(data); + + /* If the mesh is already interleaved, use the original stride to + preserve all padding, but remove the initial offset. Otherwise calculate + a tightly-packed stride. */ + std::size_t stride; + std::size_t minOffset; + if(interleaved && data.attributeCount()) { + stride = data.attributeStride(0); + minOffset = ~std::size_t{}; + for(UnsignedInt i = 0, max = data.attributeCount(); i != max; ++i) + minOffset = Math::min(minOffset, data.attributeOffset(i)); + } else { + stride = 0; + minOffset = 0; + for(UnsignedInt i = 0, max = data.attributeCount(); i != max; ++i) + stride += vertexFormatSize(data.attributeFormat(i)); + } + + /* Add the extra attributes and explicit padding */ + std::size_t extraAttributeCount = 0; + for(std::size_t i = 0; i != extra.size(); ++i) { + if(extra[i].format() == VertexFormat{}) { + CORRADE_ASSERT(extra[i].data().stride() > 0 || stride >= std::size_t(-extra[i].data().stride()), + "MeshTools::interleavedLayout(): negative padding" << extra[i].data().stride() << "in extra attribute" << i << "too large for stride" << stride, (Trade::MeshData{MeshPrimitive::Points, 0})); + stride += extra[i].data().stride(); + } else { + stride += vertexFormatSize(extra[i].format()); + ++extraAttributeCount; + } + } + + /* Allocate new data and attribute array */ + Containers::Array vertexData{Containers::NoInit, stride*vertexCount}; + Containers::Array attributeData{data.attributeCount() + extraAttributeCount}; + + /* Copy existing attribute layout. If the original is already interleaved, + preserve relative attribute offsets, otherwise pack tightly. */ + std::size_t offset = 0; + for(UnsignedInt i = 0; i != data.attributeCount(); ++i) { + if(interleaved) offset = data.attributeOffset(i) - minOffset; + + attributeData[i] = Trade::MeshAttributeData{ + data.attributeName(i), data.attributeFormat(i), + Containers::StridedArrayView1D{vertexData, vertexData + offset, + vertexCount, std::ptrdiff_t(stride)}}; + + if(!interleaved) offset += vertexFormatSize(data.attributeFormat(i)); + } + + /* In case the original is already interleaved, set the offset for extra + attribs to the original stride to preserve also potential padding at the + end. */ + if(interleaved && data.attributeCount()) + offset = data.attributeStride(0); + + /* Mix in the extra attributes */ + UnsignedInt attributeIndex = data.attributeCount(); + for(UnsignedInt i = 0; i != extra.size(); ++i) { + /* Padding, only adjust the offset for next attribute */ + if(extra[i].format() == VertexFormat{}) { + offset += extra[i].data().stride(); + continue; + } + + attributeData[attributeIndex++] = Trade::MeshAttributeData{ + extra[i].name(), extra[i].format(), Containers::StridedArrayView1D{vertexData, vertexData + offset, + vertexCount, std::ptrdiff_t(stride)}}; + + offset += vertexFormatSize(extra[i].format()); + } + + return Trade::MeshData{data.primitive(), std::move(vertexData), std::move(attributeData)}; +} + +Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt vertexCount, const std::initializer_list extra) { + return interleavedLayout(data, vertexCount, Containers::arrayView(extra)); +} + }} diff --git a/src/Magnum/MeshTools/Interleave.h b/src/Magnum/MeshTools/Interleave.h index 623c4345b6..81dd6ee71b 100644 --- a/src/Magnum/MeshTools/Interleave.h +++ b/src/Magnum/MeshTools/Interleave.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::interleave(), @ref Magnum::MeshTools::interleaveInto(), @ref Magnum::MeshTools::isInterleaved() + * @brief Function @ref Magnum::MeshTools::interleave(), @ref Magnum::MeshTools::interleaveInto(), @ref Magnum::MeshTools::isInterleaved(), @ref Magnum::MeshTools::interleavedLayout() */ #include @@ -199,6 +199,41 @@ or no attributes. */ MAGNUM_MESHTOOLS_EXPORT bool isInterleaved(const Trade::MeshData& data); +/** +@brief Create an interleaved mesh layout +@m_since_latest + +Returns a @ref Trade::MeshData instance with its vertex data allocated for +@p vertexCount vertices containing attributes from both @p data and @p extra +interleaved together. No data is actually copied, only an interleaved layout is +created. If @p data is already interleaved, keeps the attributes in the same +layout, potentially extending them with @p extra. The @p extra attributes, if +any, are interleaved together with existing attributes. Returned instance +vertex data flags have both @ref Trade::DataFlag::Mutable and @ref Trade::DataFlag::Owned, so mutable attribute access is guaranteed. + +For greater control you can also pass an empty @ref Trade::MeshData instance +and fill @p extra with attributes cherry-picked from +@ref Trade::MeshData::attributeData() of an existing instance. By default the +attributes are tightly packed, you can add arbitrary padding using instances +constructed via @ref Trade::MeshAttributeData::MeshAttributeData(Int). +Example: + +@snippet MagnumMeshTools.cpp interleavedLayout-extra + +This function doesn't preserve index data information in any way, making the +output non-indexed. If you want to preserve index data, create a new indexed +instance with attribute and vertex data transferred from the returned instance: + +@snippet MagnumMeshTools.cpp interleavedLayout-indices +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(const Trade::MeshData& data, UnsignedInt vertexCount, Containers::ArrayView extra = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(const Trade::MeshData& data, UnsignedInt vertexCount, std::initializer_list extra); + }} #endif diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index aa15700bc2..899dc8826d 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -28,7 +28,7 @@ corrade_add_test(MeshToolsCompressIndicesTest CompressIndicesTest.cpp LIBRARIES corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsFlipNormalsTest FlipNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsGenerateNormalsTest GenerateNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib MagnumPrimitives) -corrade_add_test(MeshToolsInterleaveTest InterleaveTest.cpp LIBRARIES MagnumMeshTools) +corrade_add_test(MeshToolsInterleaveTest InterleaveTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsRemoveDuplicatesTest RemoveDuplicatesTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsSubdivideTest SubdivideTest.cpp LIBRARIES Magnum) corrade_add_test(MeshToolsTipsifyTest TipsifyTest.cpp LIBRARIES MagnumMeshTools) diff --git a/src/Magnum/MeshTools/Test/InterleaveTest.cpp b/src/Magnum/MeshTools/Test/InterleaveTest.cpp index 8cb7267d61..f962a6c47f 100644 --- a/src/Magnum/MeshTools/Test/InterleaveTest.cpp +++ b/src/Magnum/MeshTools/Test/InterleaveTest.cpp @@ -55,6 +55,16 @@ struct InterleaveTest: Corrade::TestSuite::Tester { void isInterleavedAliased(); void isInterleavedUnordered(); void isInterleavedAttributeAcrossStride(); + + void interleavedLayout(); + void interleavedLayoutExtra(); + void interleavedLayoutExtraAliased(); + void interleavedLayoutExtraTooNegativePadding(); + void interleavedLayoutExtraOnly(); + void interleavedLayoutAlreadyInterleaved(); + void interleavedLayoutAlreadyInterleavedAliased(); + void interleavedLayoutAlreadyInterleavedExtra(); + void interleavedLayoutNothing(); }; InterleaveTest::InterleaveTest() { @@ -73,7 +83,17 @@ InterleaveTest::InterleaveTest() { &InterleaveTest::isInterleavedGaps, &InterleaveTest::isInterleavedAliased, &InterleaveTest::isInterleavedUnordered, - &InterleaveTest::isInterleavedAttributeAcrossStride}); + &InterleaveTest::isInterleavedAttributeAcrossStride, + + &InterleaveTest::interleavedLayout, + &InterleaveTest::interleavedLayoutExtra, + &InterleaveTest::interleavedLayoutExtraAliased, + &InterleaveTest::interleavedLayoutExtraTooNegativePadding, + &InterleaveTest::interleavedLayoutExtraOnly, + &InterleaveTest::interleavedLayoutAlreadyInterleaved, + &InterleaveTest::interleavedLayoutAlreadyInterleavedAliased, + &InterleaveTest::interleavedLayoutAlreadyInterleavedExtra, + &InterleaveTest::interleavedLayoutNothing}); } void InterleaveTest::attributeCount() { @@ -285,6 +305,267 @@ void InterleaveTest::isInterleavedAttributeAcrossStride() { CORRADE_VERIFY(!MeshTools::isInterleaved(data2)); } +void InterleaveTest::interleavedLayout() { + Containers::Array indexData{6}; + Containers::Array vertexData{3*20}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.prefix(3*8))}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::arrayCast(vertexData.suffix(3*8))}; + + Trade::MeshIndexData indices{Containers::arrayCast(indexData)}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + std::move(indexData), indices, + std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(!MeshTools::isInterleaved(data)); + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 10); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!layout.isIndexed()); /* Indices are not preserved */ + CORRADE_COMPARE(layout.attributeCount(), 2); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeStride(0), 20); + CORRADE_COMPARE(layout.attributeStride(1), 20); + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 8); + CORRADE_COMPARE(layout.vertexCount(), 10); + /* Needs to be like this so we can modify the data */ + CORRADE_COMPARE(layout.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned); + CORRADE_VERIFY(layout.vertexData()); + CORRADE_COMPARE(layout.vertexData().size(), 10*20); +} + +void InterleaveTest::interleavedLayoutExtra() { + Containers::Array vertexData{3*20}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.prefix(3*8))}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::arrayCast(vertexData.suffix(3*8))}; + + Trade::MeshData data{MeshPrimitive::Triangles, + std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(!MeshTools::isInterleaved(data)); + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 7, { + Trade::MeshAttributeData{1}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(15), + VertexFormat::UnsignedShort, nullptr}, + Trade::MeshAttributeData{1}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + VertexFormat::Vector3, nullptr}, + Trade::MeshAttributeData{4} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.attributeCount(), 4); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeName(2), Trade::meshAttributeCustom(15)); + CORRADE_COMPARE(layout.attributeName(3), Trade::MeshAttribute::Color); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeFormat(2), VertexFormat::UnsignedShort); + CORRADE_COMPARE(layout.attributeFormat(3), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeStride(0), 40); + CORRADE_COMPARE(layout.attributeStride(1), 40); + CORRADE_COMPARE(layout.attributeStride(2), 40); + CORRADE_COMPARE(layout.attributeStride(3), 40); + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 8); + CORRADE_COMPARE(layout.attributeOffset(2), 21); + CORRADE_COMPARE(layout.attributeOffset(3), 24); + CORRADE_COMPARE(layout.vertexCount(), 7); + CORRADE_COMPARE(layout.vertexData().size(), 7*40); +} + +void InterleaveTest::interleavedLayoutExtraAliased() { + Containers::Array vertexData{3*12}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data()), 3, 12}}; + Trade::MeshData data{MeshPrimitive::Triangles, + std::move(vertexData), {positions}}; + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 100, { + /* Normals at the same place as positions */ + Trade::MeshAttributeData{-12}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3, positions.data()} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.attributeCount(), 2); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeStride(0), 12); + CORRADE_COMPARE(layout.attributeStride(1), 12); + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 0); /* aliases */ + CORRADE_COMPARE(layout.vertexCount(), 100); + CORRADE_COMPARE(layout.vertexData().size(), 100*12); +} + +void InterleaveTest::interleavedLayoutExtraTooNegativePadding() { + Containers::Array vertexData{3*12}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data()), 3, 12}}; + Trade::MeshData data{MeshPrimitive::Triangles, + std::move(vertexData), {positions}}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::interleavedLayout(data, 100, { + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3, positions.data()}, + Trade::MeshAttributeData{-25} + }); + CORRADE_COMPARE(out.str(), "MeshTools::interleavedLayout(): negative padding -25 in extra attribute 1 too large for stride 24\n"); +} + +void InterleaveTest::interleavedLayoutExtraOnly() { + Trade::MeshData data{MeshPrimitive::Triangles, 0}; + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 10, { + Trade::MeshAttributeData{4}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, nullptr}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3, nullptr} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.attributeCount(), 2); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeStride(0), 24); + CORRADE_COMPARE(layout.attributeStride(1), 24); + CORRADE_COMPARE(layout.attributeOffset(0), 4); + CORRADE_COMPARE(layout.attributeOffset(1), 12); + CORRADE_COMPARE(layout.vertexCount(), 10); + CORRADE_COMPARE(layout.vertexData().size(), 10*24); +} + +void InterleaveTest::interleavedLayoutAlreadyInterleaved() { + Containers::Array indexData{6}; + /* Test also removing the initial offset */ + Containers::Array vertexData{100 + 3*24}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data() + 100), 3, 24}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data() + 100 + 10), 3, 24}}; + + Trade::MeshIndexData indices{Containers::arrayCast(indexData)}; + Trade::MeshData data{MeshPrimitive::Triangles, + std::move(indexData), indices, + std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 10); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_VERIFY(!layout.isIndexed()); /* Indices are not preserved */ + CORRADE_COMPARE(layout.attributeCount(), 2); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + /* Original stride should be preserved */ + CORRADE_COMPARE(layout.attributeStride(0), 24); + CORRADE_COMPARE(layout.attributeStride(1), 24); + /* Relative offsets should be preserved, but the initial one removed */ + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 10); + CORRADE_COMPARE(layout.vertexCount(), 10); + CORRADE_COMPARE(layout.vertexData().size(), 10*24); +} + +void InterleaveTest::interleavedLayoutAlreadyInterleavedAliased() { + Containers::Array indexData{6}; + Containers::Array vertexData{3*12}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data()), 3, 12}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data()), 3, 12}}; + + Trade::MeshIndexData indices{Containers::arrayCast(indexData)}; + Trade::MeshData data{MeshPrimitive::Triangles, + std::move(indexData), indices, + std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 10); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_VERIFY(!layout.isIndexed()); /* Indices are not preserved */ + CORRADE_COMPARE(layout.attributeCount(), 2); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeStride(0), 12); + CORRADE_COMPARE(layout.attributeStride(1), 12); + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 0); /* aliases */ + CORRADE_COMPARE(layout.vertexCount(), 10); + CORRADE_COMPARE(layout.vertexData().size(), 10*12); +} + +void InterleaveTest::interleavedLayoutAlreadyInterleavedExtra() { + Containers::Array vertexData{100 + 3*24}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data() + 100), 3, 24}}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::StridedArrayView1D{vertexData, reinterpret_cast(vertexData.data() + 100 + 10), 3, 24}}; + + Trade::MeshData data{MeshPrimitive::Triangles, + std::move(vertexData), {positions, normals}}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + + Trade::MeshData layout = MeshTools::interleavedLayout(data, 10, { + Trade::MeshAttributeData{1}, + Trade::MeshAttributeData{Trade::meshAttributeCustom(15), + VertexFormat::UnsignedShort, nullptr}, + Trade::MeshAttributeData{1}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + VertexFormat::Vector3, nullptr}, + Trade::MeshAttributeData{4} + }); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.attributeCount(), 4); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeName(2), Trade::meshAttributeCustom(15)); + CORRADE_COMPARE(layout.attributeName(3), Trade::MeshAttribute::Color); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeFormat(2), VertexFormat::UnsignedShort); + CORRADE_COMPARE(layout.attributeFormat(3), VertexFormat::Vector3); + /* Original stride should be preserved, with stride from extra attribs + added */ + CORRADE_COMPARE(layout.attributeStride(0), 24 + 20); + CORRADE_COMPARE(layout.attributeStride(1), 24 + 20); + CORRADE_COMPARE(layout.attributeStride(2), 24 + 20); + CORRADE_COMPARE(layout.attributeStride(3), 24 + 20); + /* Relative offsets should be preserved, but the initial one removed */ + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 10); + CORRADE_COMPARE(layout.attributeOffset(2), 25); + CORRADE_COMPARE(layout.attributeOffset(3), 28); + CORRADE_COMPARE(layout.vertexCount(), 10); + CORRADE_COMPARE(layout.vertexData().size(), 10*44); +} + +void InterleaveTest::interleavedLayoutNothing() { + Trade::MeshData layout = MeshTools::interleavedLayout(Trade::MeshData{MeshPrimitive::Points, 25}, 10); + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.attributeCount(), 0); + CORRADE_COMPARE(layout.vertexCount(), 10); + CORRADE_VERIFY(!layout.vertexData()); + CORRADE_COMPARE(layout.vertexData().size(), 0); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::InterleaveTest) From 6c2fb3facb6190135a7bf41bf755b2f265caa03e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 17 Jan 2020 18:11:07 +0100 Subject: [PATCH 053/107] MeshTools: implement interleave() taking a MeshData. --- doc/changelog.dox | 2 + src/Magnum/MeshTools/Interleave.cpp | 103 ++++++++ src/Magnum/MeshTools/Interleave.h | 52 +++- src/Magnum/MeshTools/Test/InterleaveTest.cpp | 238 ++++++++++++++++++- 4 files changed, 393 insertions(+), 2 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 77eaf76034..44acd36f83 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 75db8a67fc..525c5cabeb 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 81dd6ee71b..1f30c40bd7 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 f962a6c47f..4be0d805dc 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) From e8692af4a6165db60cdca37f3e8ba853fb2f0d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 17 Jan 2020 20:13:44 +0100 Subject: [PATCH 054/107] MeshTools: implement duplicate() taking a MeshData. --- doc/changelog.dox | 3 +- src/Magnum/MeshTools/Duplicate.cpp | 41 +++++ src/Magnum/MeshTools/Duplicate.h | 26 ++++ src/Magnum/MeshTools/Test/DuplicateTest.cpp | 156 +++++++++++++++++++- 4 files changed, 223 insertions(+), 3 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 44acd36f83..c1150095eb 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 a640755bed..4d6b2cb455 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 8b58cd13d0..a1bf32b8a8 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 a715b42390..d936125674 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) From 16c3480d7fe29010ea929302eb8982529652feb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 21 Nov 2019 23:20:55 +0100 Subject: [PATCH 055/107] MeshTools: implement compile() taking a MeshData. Also add new variants that allow for external buffers. --- doc/changelog.dox | 2 + doc/snippets/MagnumMeshTools-gl.cpp | 33 ++ src/Magnum/MeshTools/CMakeLists.txt | 4 +- src/Magnum/MeshTools/Compile.cpp | 148 ++++++- src/Magnum/MeshTools/Compile.h | 91 +++++ src/Magnum/MeshTools/Test/CMakeLists.txt | 2 +- src/Magnum/MeshTools/Test/CompileGLTest.cpp | 414 +++++++++++++------- 7 files changed, 557 insertions(+), 137 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index c1150095eb..1f291d811f 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -109,6 +109,8 @@ See also: @subsubsection changelog-latest-new-meshtools MeshTools library +- Added @ref MeshTools::compile(const Trade::MeshData&) operating on the new + @ref Trade::MeshData API - New @ref MeshTools::isInterleaved() utility for checking if @ref Trade::MeshData is interleaved - Added @ref MeshTools::interleavedLayout() for convenient creation of an diff --git a/doc/snippets/MagnumMeshTools-gl.cpp b/doc/snippets/MagnumMeshTools-gl.cpp index 39ad83a023..bd93ffadae 100644 --- a/doc/snippets/MagnumMeshTools-gl.cpp +++ b/doc/snippets/MagnumMeshTools-gl.cpp @@ -29,13 +29,46 @@ #include "Magnum/GL/Buffer.h" #include "Magnum/GL/Mesh.h" #include "Magnum/Math/Vector3.h" +#include "Magnum/MeshTools/Compile.h" #include "Magnum/MeshTools/CompressIndices.h" #include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" using namespace Magnum; int main() { +{ +Trade::MeshData meshData{MeshPrimitive::Lines, 5}; +/* [compile-external] */ +GL::Buffer indices, vertices; +indices.setData(meshData.indexData()); +vertices.setData(meshData.vertexData()); + +GL::Mesh mesh = MeshTools::compile(meshData, indices, vertices); +/* [compile-external] */ +} + +{ +Trade::MeshData meshData{MeshPrimitive::Lines, 5}; +Trade::MeshAttribute myCustomAttribute{}; +/* [compile-external-attributes] */ +GL::Buffer indices, vertices; +indices.setData(meshData.indexData()); +vertices.setData(meshData.vertexData()); + +/* Let compile() handle the usual attributes and configure custom ones after */ +GL::Mesh mesh = MeshTools::compile(meshData, std::move(indices), vertices); +mesh.addVertexBuffer(std::move(vertices), + meshData.attributeOffset(myCustomAttribute), + meshData.attributeStride(myCustomAttribute), + GL::DynamicAttribute{ + GL::DynamicAttribute::Kind::Generic, 7, + GL::DynamicAttribute::Components::One, + GL::DynamicAttribute::DataType::Float}); +/* [compile-external-attributes] */ +} + { /* [compressIndices] */ Containers::Array indices; diff --git a/src/Magnum/MeshTools/CMakeLists.txt b/src/Magnum/MeshTools/CMakeLists.txt index be538c5e9f..efbf610b1d 100644 --- a/src/Magnum/MeshTools/CMakeLists.txt +++ b/src/Magnum/MeshTools/CMakeLists.txt @@ -60,9 +60,11 @@ endif() if(TARGET_GL) list(APPEND MagnumMeshTools_SRCS - Compile.cpp FullScreenTriangle.cpp) + list(APPEND MagnumMeshTools_GracefulAssert_SRCS + Compile.cpp) + list(APPEND MagnumMeshTools_HEADERS Compile.h FullScreenTriangle.h) diff --git a/src/Magnum/MeshTools/Compile.cpp b/src/Magnum/MeshTools/Compile.cpp index 1cb807b96d..703c2f9c87 100644 --- a/src/Magnum/MeshTools/Compile.cpp +++ b/src/Magnum/MeshTools/Compile.cpp @@ -25,8 +25,9 @@ #include "Compile.h" +#include #include -#include /** @todo remove once MeshData is sane */ +#include /** @todo remove once MeshDataXD is gone */ #include "Magnum/GL/Buffer.h" #include "Magnum/GL/Mesh.h" @@ -36,6 +37,7 @@ #include "Magnum/MeshTools/GenerateNormals.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" @@ -45,6 +47,150 @@ namespace Magnum { namespace MeshTools { +GL::Mesh compile(const Trade::MeshData& meshData, CompileFlags flags) { + /* If we want to generate normals, prepare a new mesh data and recurse, + with the flags unset */ + if(meshData.primitive() == MeshPrimitive::Triangles && (flags & (CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals))) { + CORRADE_ASSERT(meshData.attributeCount(Trade::MeshAttribute::Position), + "MeshTools::compile(): the mesh has no positions, can't generate normals", GL::Mesh{}); + /* Right now this could fire only if we have 2D positions, which is + unlikely; in the future it might fire once packed formats are added */ + CORRADE_ASSERT(meshData.attributeFormat(Trade::MeshAttribute::Position) == VertexFormat::Vector3, + "MeshTools::compile(): can't generate normals for" << meshData.attributeFormat(Trade::MeshAttribute::Position) << "positions", GL::Mesh{}); + + /* If the data already have a normal array, reuse its location, + otherwise mix in an extra one */ + Trade::MeshAttributeData normalAttribute; + Containers::ArrayView extra; + if(!meshData.hasAttribute(Trade::MeshAttribute::Normal)) { + normalAttribute = Trade::MeshAttributeData{ + Trade::MeshAttribute::Normal, VertexFormat::Vector3, + nullptr}; + extra = {&normalAttribute, 1}; + /* If we reuse a normal location, expect correct type. Again this won't + fire now, but might in the future once packed formats are added */ + } else CORRADE_ASSERT(meshData.attributeFormat(Trade::MeshAttribute::Normal) == VertexFormat::Vector3, + "MeshTools::compile(): can't generate normals into" << meshData.attributeFormat(Trade::MeshAttribute::Normal), GL::Mesh{}); + + /* If we want flat normals, we need to first duplicate everything using + the index buffer. Otherwise just interleave the potential extra + normal attribute in. */ + Trade::MeshData generated{MeshPrimitive::Points, 0}; + if(flags & CompileFlag::GenerateFlatNormals && meshData.isIndexed()) + generated = duplicate(meshData, extra); + else + generated = interleave(meshData, extra); + + /* Generate the normals. If we don't have the index buffer, we can only + generate flat ones. */ + if(flags & CompileFlag::GenerateFlatNormals || !meshData.isIndexed()) + generateFlatNormalsInto( + generated.attribute(Trade::MeshAttribute::Position), + generated.mutableAttribute(Trade::MeshAttribute::Normal)); + else + generateSmoothNormalsInto(generated.indices(), + generated.attribute(Trade::MeshAttribute::Position), + generated.mutableAttribute(Trade::MeshAttribute::Normal)); + + return compile(generated, flags & ~(CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals)); + } + + flags &= ~(CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals); + CORRADE_INTERNAL_ASSERT(!flags); + return compile(meshData); +} + +GL::Mesh compile(const Trade::MeshData& meshData) { + GL::Buffer indices{NoCreate}; + if(meshData.isIndexed()) { + indices = GL::Buffer{GL::Buffer::TargetHint::ElementArray}; + indices.setData(meshData.indexData()); + } + + GL::Buffer vertices{GL::Buffer::TargetHint::Array}; + vertices.setData(meshData.vertexData()); + + return compile(meshData, std::move(indices), std::move(vertices)); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer& vertices) { + return compile(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array)); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer&& vertices) { + return compile(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), std::move(vertices)); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer& vertices) { + return compile(meshData, std::move(indices), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array)); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer&& vertices) { + CORRADE_ASSERT((!meshData.isIndexed() || indices.id()) && vertices.id(), + "MeshTools::compile(): invalid external buffer(s)", GL::Mesh{}); + + /* Basics */ + GL::Mesh mesh; + mesh.setPrimitive(meshData.primitive()); + + /* Vertex data */ + GL::Buffer verticesRef = GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array); + for(UnsignedInt i = 0; i != meshData.attributeCount(); ++i) { + Containers::Optional attribute; + switch(meshData.attributeName(i)) { + case Trade::MeshAttribute::Position: + if(meshData.attributeFormat(i) == VertexFormat::Vector2) + attribute.emplace(Shaders::Generic2D::Position{}); + else if(meshData.attributeFormat(i) == VertexFormat::Vector3) + attribute.emplace(Shaders::Generic3D::Position{}); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + break; + case Trade::MeshAttribute::Normal: + CORRADE_INTERNAL_ASSERT(meshData.attributeFormat(i) == VertexFormat::Vector3); + attribute.emplace(Shaders::Generic3D::Normal{}); + break; + case Trade::MeshAttribute::TextureCoordinates: + CORRADE_INTERNAL_ASSERT(meshData.attributeFormat(i) == VertexFormat::Vector2); + /** @todo have Generic2D derived from Generic that has all + attribute definitions common for 2D and 3D */ + attribute.emplace(Shaders::Generic2D::TextureCoordinates{}); + break; + case Trade::MeshAttribute::Color: + /** @todo have Generic2D derived from Generic that has all + attribute definitions common for 2D and 3D */ + if(meshData.attributeFormat(i) == VertexFormat::Vector3) + attribute.emplace(Shaders::Generic2D::Color3{}); + else if(meshData.attributeFormat(i) == VertexFormat::Vector4) + attribute.emplace(Shaders::Generic2D::Color4{}); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + break; + + /* So it doesn't yell that we didn't handle a known attribute */ + case Trade::MeshAttribute::Custom: break; /* LCOV_EXCL_LINE */ + } + + if(!attribute) { + Warning{} << "MeshTools::compile(): ignoring unknown attribute" << meshData.attributeName(i); + continue; + } + + /* For the first attribute move the buffer in, for all others use the + reference */ + if(vertices.id()) mesh.addVertexBuffer(std::move(vertices), + meshData.attributeOffset(i), meshData.attributeStride(i), + *attribute); + else mesh.addVertexBuffer(verticesRef, meshData.attributeOffset(i), + meshData.attributeStride(i), *attribute); + } + + if(meshData.isIndexed()) { + mesh.setIndexBuffer(std::move(indices), 0, meshData.indexType()) + .setCount(meshData.indexCount()); + } else mesh.setCount(meshData.vertexCount()); + + return mesh; +} + GL::Mesh compile(const Trade::MeshData2D& meshData) { GL::Mesh mesh; mesh.setPrimitive(meshData.primitive()); diff --git a/src/Magnum/MeshTools/Compile.h b/src/Magnum/MeshTools/Compile.h index cd975994f0..50d5e9c34e 100644 --- a/src/Magnum/MeshTools/Compile.h +++ b/src/Magnum/MeshTools/Compile.h @@ -85,6 +85,97 @@ typedef Containers::EnumSet CompileFlags; CORRADE_ENUMSET_OPERATORS(CompileFlags) +/** +@brief Compile mesh data +@m_since_latest + +Configures a mesh for a @ref Shaders::Generic shader with a vertex buffer and +possibly also an index buffer, if the mesh is indexed. + +- If the mesh contains positions, these are bound to the + @ref Shaders::Generic2D::Position attribute if they are 2D or to + @ref Shaders::Generic3D::Position if they are 3D. +- If the mesh contains normals or if @ref CompileFlag::GenerateFlatNormals / + @ref CompileFlag::GenerateSmoothNormals is set, these are bound to + @ref Shaders::Generic3D::Normal. +- If the mesh contains texture coordinates, these are bound to + @ref Shaders::Generic::TextureCoordinates. +- If the mesh contains colors, these are bound to + @ref Shaders::Generic::Color3 / @ref Shaders::Generic::Color4 based on + their type. + +If normal generation is not requested, @ref Trade::MeshData::indexData() and +@ref Trade::MeshData::vertexData() are uploaded as-is without any further +modifications, keeping the original layout and vertex formats. If +@ref CompileFlag::GenerateSmoothNormals is requested, vertex data is +interleaved together with the generated normals; if +@ref CompileFlag::GenerateFlatNormals is requested, the mesh is first +deindexed and then the vertex data is interleaved together with the generated +normals. + +The generated mesh owns the index and vertex buffers and there's no possibility +to access them afterwards. For alternative solutions see the +@ref compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&) overloads. + +@note This function is available only if Magnum is compiled with + @ref MAGNUM_TARGET_GL enabled (done by default). See @ref building-features + for more information. + +@see @ref shaders-generic +*/ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, CompileFlags flags); + +/** + * @overload + * @m_since_latest + */ +/* Separately because this one doesn't rely on duplicate() / interleave() / + generate*Normals() and thus the exe can be smaller when using this function + directly */ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData); + +/** +@brief Compile mesh data using external buffers +@m_since_latest + +Assumes the whole vertex / index data are already uploaded to @p indices / +@p vertices and sets up the mesh using those. Can be used to have a single +index/vertex buffer when multiple @ref Trade::MeshData instances share the same +data arrays, or to allow buffer access later. For example: + +@snippet MagnumMeshTools-gl.cpp compile-external + +Another use case is specifying additional vertex attributes that are not +recognized by the function itself. You can choose among various r-value +overloads depending on whether you want to have the index/vertex buffers owned +by the mesh or not: + +@snippet MagnumMeshTools-gl.cpp compile-external-attributes + +If @p meshData is not indexed, the @p indices parameter is ignored --- in that +case you can pass a @ref NoCreate "NoCreate"-d instance to avoid allocating an +unnecessary OpenGL buffer object. +*/ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer& vertices); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer&& vertices); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer& vertices); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer&& vertices); + /** @brief Compile 2D mesh data diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index 899dc8826d..31939f6eb5 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -100,7 +100,7 @@ if(BUILD_GL_TESTS) MagnumDebugTools MagnumGL MagnumOpenGLTester - MagnumMeshTools + MagnumMeshToolsTestLib MagnumShaders FILES CompileTestFiles/color2D.tga diff --git a/src/Magnum/MeshTools/Test/CompileGLTest.cpp b/src/Magnum/MeshTools/Test/CompileGLTest.cpp index d54cebb60d..0ffc8a0e99 100644 --- a/src/Magnum/MeshTools/Test/CompileGLTest.cpp +++ b/src/Magnum/MeshTools/Test/CompileGLTest.cpp @@ -23,9 +23,12 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include #include +#include #include "Magnum/Image.h" #include "Magnum/ImageView.h" @@ -78,8 +81,14 @@ struct CompileGLTest: GL::OpenGLTester { public: explicit CompileGLTest(); - void twoDimensions(); - void threeDimensions(); + template void twoDimensions(); + template void threeDimensions(); + void unknownAttribute(); + void generateNormalsNoPosition(); + void generateNormals2DPosition(); + + void externalBuffers(); + void externalBuffersInvalid(); private: PluginManager::Manager _manager{"nonexistent"}; @@ -138,6 +147,17 @@ constexpr struct { {"positions, nonindexed + gen smooth normals", Flag::NonIndexed|Flag::GeneratedSmoothNormals}, }; +constexpr struct { + const char* name; + bool indexed, moveIndices, moveVertices; +} DataExternal[] { + {"indexed", true, false, false}, + {"", false, false, false}, + {"move indices", true, true, false}, + {"move vertices", false, false, true}, + {"move both", true, true, true} +}; + using namespace Math::Literals; constexpr Color4ub ImageData[] { @@ -148,11 +168,24 @@ constexpr Color4ub ImageData[] { }; CompileGLTest::CompileGLTest() { - addInstancedTests({&CompileGLTest::twoDimensions}, - Containers::arraySize(Data2D)); + addInstancedTests({ + &CompileGLTest::twoDimensions, + &CompileGLTest::twoDimensions}, + Containers::arraySize(Data2D)); + + addInstancedTests({ + &CompileGLTest::threeDimensions, + &CompileGLTest::threeDimensions}, + Containers::arraySize(Data3D)); - addInstancedTests({&CompileGLTest::threeDimensions}, - Containers::arraySize(Data3D)); + addTests({&CompileGLTest::unknownAttribute, + &CompileGLTest::generateNormalsNoPosition, + &CompileGLTest::generateNormals2DPosition}); + + addInstancedTests({&CompileGLTest::externalBuffers}, + Containers::arraySize(DataExternal)); + + addTests({&CompileGLTest::externalBuffersInvalid}); /* Load the plugins directly from the build tree. Otherwise they're either static and already loaded or not present in the build tree */ @@ -187,7 +220,19 @@ CompileGLTest::CompileGLTest() { .setSubImage(0, {}, ImageView2D{PixelFormat::RGBA8Unorm, {4, 4}, ImageData}); } -void CompileGLTest::twoDimensions() { +template struct MeshTypeName; +template<> struct MeshTypeName { + static const char* name() { return "Trade::MeshData"; } +}; +template<> struct MeshTypeName { + static const char* name() { return "Trade::MeshData2D"; } +}; +template<> struct MeshTypeName { + static const char* name() { return "Trade::MeshData3D"; } +}; + +template void CompileGLTest::twoDimensions() { + setTestCaseTemplateName(MeshTypeName::name()); auto&& data = Data2D[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -202,69 +247,57 @@ void CompileGLTest::twoDimensions() { |/ |/ | 0-----1-----2 */ - std::vector positions{ - {-0.75f, -0.75f}, - { 0.00f, -0.75f}, - { 0.75f, -0.75f}, - - {-0.75f, 0.0f}, - { 0.00f, 0.0f}, - { 0.75f, 0.0f}, - - {-0.75f, 0.75f}, - { 0.0f, 0.75f}, - { 0.75f, 0.75f} + const struct Vertex { + Vector2 position; + Vector2 textureCoordinates; + Color3 color; + } vertexData[]{ + {{-0.75f, -0.75f}, {0.0f, 0.0f}, 0x00ff00_rgbf}, + {{ 0.00f, -0.75f}, {0.5f, 0.0f}, 0x808000_rgbf}, + {{ 0.75f, -0.75f}, {1.0f, 0.0f}, 0xff0000_rgbf}, + + {{-0.75f, 0.00f}, {0.0f, 0.5f}, 0x00ff80_rgbf}, + {{ 0.00f, 0.00f}, {0.5f, 0.5f}, 0x808080_rgbf}, + {{ 0.75f, 0.00f}, {1.0f, 0.5f}, 0xff0080_rgbf}, + + {{-0.75f, 0.75f}, {0.0f, 1.0f}, 0x00ffff_rgbf}, + {{ 0.0f, 0.75f}, {0.5f, 1.0f}, 0x8080ff_rgbf}, + {{ 0.75f, 0.75f}, {1.0f, 1.0f}, 0xff00ff_rgbf} }; - std::vector> textureCoordinates2D; - if(data.flags & Flag::TextureCoordinates2D) textureCoordinates2D.push_back(std::vector{ - {0.0f, 0.0f}, - {0.5f, 0.0f}, - {1.0f, 0.0f}, - - {0.0f, 0.5f}, - {0.5f, 0.5f}, - {1.0f, 0.5f}, - - {0.0f, 1.0f}, - {0.5f, 1.0f}, - {1.0f, 1.0f} - }); - - std::vector> colors; - if(data.flags & Flag::Colors) colors.push_back(std::vector { - 0x00ff00_rgbf, - 0x808000_rgbf, - 0xff0000_rgbf, - - 0x00ff80_rgbf, - 0x808080_rgbf, - 0xff0080_rgbf, - - 0x00ffff_rgbf, - 0x8080ff_rgbf, - 0xff00ff_rgbf - }); - - std::vector indices{ + Containers::Array attributeData; + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::Position, + Containers::stridedArrayView(vertexData, &vertexData[0].position, + Containers::arraySize(vertexData), sizeof(Vertex))}); + if(data.flags & Flag::TextureCoordinates2D) + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexData, &vertexData[0].textureCoordinates, + Containers::arraySize(vertexData), sizeof(Vertex))}); + if(data.flags & Flag::Colors) + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::Color, + Containers::stridedArrayView(vertexData, &vertexData[0].color, + Containers::arraySize(vertexData), sizeof(Vertex))}); + + const UnsignedInt indexData[]{ 0, 1, 4, 0, 4, 3, 1, 2, 5, 1, 5, 4, 3, 4, 7, 3, 7, 6, 4, 5, 8, 4, 8, 7 }; - /* Duplicate positions if data are non-indexed. Testing only positions - alone ATM, don't bother with other attribs. */ - if(data.flags & Flag::NonIndexed) { - CORRADE_INTERNAL_ASSERT(textureCoordinates2D.empty()); - CORRADE_INTERNAL_ASSERT(colors.empty()); - positions = duplicate(indices, positions); - indices.clear(); - } + Trade::MeshData meshData{MeshPrimitive::Triangles, + {}, indexData, Trade::MeshIndexData{indexData}, + {}, vertexData, std::move(attributeData)}; + + /* Duplicate everything if data is non-indexed */ + if(data.flags & Flag::NonIndexed) meshData = duplicate(meshData); MAGNUM_VERIFY_NO_GL_ERROR(); - GL::Mesh mesh = compile(Trade::MeshData2D{MeshPrimitive::Triangles, indices, {positions}, textureCoordinates2D, colors}); + GL::Mesh mesh = compile(T{std::move(meshData)}); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -312,7 +345,8 @@ void CompileGLTest::twoDimensions() { } } -void CompileGLTest::threeDimensions() { +template void CompileGLTest::threeDimensions() { + setTestCaseTemplateName(MeshTypeName::name()); auto&& data = Data3D[testCaseInstanceId()]; setTestCaseDescription(data.name); @@ -327,96 +361,77 @@ void CompileGLTest::threeDimensions() { |/ |/ | 0-----1-----2 */ - std::vector positions{ - {-0.75f, -0.75f, -0.35f}, - { 0.00f, -0.75f, -0.25f}, - { 0.75f, -0.75f, -0.35f}, - - {-0.75f, 0.00f, -0.25f}, - { 0.00f, 0.00f, 0.00f}, - { 0.75f, 0.00f, -0.25f}, - - {-0.75f, 0.75f, -0.35f}, - { 0.0f, 0.75f, -0.25f}, - { 0.75f, 0.75f, -0.35f} + const struct Vertex { + Vector3 position; + Vector3 normal; + Vector2 textureCoordinates; + Color4 color; + } vertexData[]{ + {{-0.75f, -0.75f, -0.35f}, Vector3{-0.5f, -0.5f, 1.0f}.normalized(), + {0.0f, 0.0f}, 0x00ff00_rgbf}, + {{ 0.00f, -0.75f, -0.25f}, Vector3{ 0.0f, -0.5f, 1.0f}.normalized(), + {0.5f, 0.0f}, 0x808000_rgbf}, + {{ 0.75f, -0.75f, -0.35f}, Vector3{ 0.5f, -0.5f, 1.0f}.normalized(), + {1.0f, 0.0f}, 0xff0000_rgbf}, + + {{-0.75f, 0.00f, -0.25f}, Vector3{-0.5f, 0.0f, 1.0f}.normalized(), + {0.0f, 0.5f}, 0x00ff80_rgbf}, + {{ 0.00f, 0.00f, 0.00f}, Vector3{ 0.0f, 0.0f, 1.0f}.normalized(), + {0.5f, 0.5f}, 0x808080_rgbf}, + {{ 0.75f, 0.00f, -0.25f}, Vector3{ 0.5f, 0.0f, 1.0f}.normalized(), + {1.0f, 0.5f}, 0xff0080_rgbf}, + + {{-0.75f, 0.75f, -0.35f}, Vector3{-0.5f, 0.5f, 1.0f}.normalized(), + {0.0f, 1.0f}, 0x00ffff_rgbf}, + {{ 0.0f, 0.75f, -0.25f}, Vector3{ 0.0f, 0.5f, 1.0f}.normalized(), + {0.5f, 1.0f}, 0x8080ff_rgbf}, + {{ 0.75f, 0.75f, -0.35f}, Vector3{ 0.5f, 0.5f, 1.0f}.normalized(), + {1.0f, 1.0f}, 0xff00ff_rgbf} }; - std::vector> normals; - if(data.flags & Flag::Normals) normals.push_back(std::vector{ - Vector3{-0.5f, -0.5f, 1.0f}.normalized(), - Vector3{ 0.0f, -0.5f, 1.0f}.normalized(), - Vector3{ 0.5f, -0.5f, 1.0f}.normalized(), - - Vector3{-0.5f, 0.0f, 1.0f}.normalized(), - Vector3{ 0.0f, 0.0f, 1.0f}.normalized(), - Vector3{ 0.5f, 0.0f, 1.0f}.normalized(), - - Vector3{-0.5f, 0.5f, 1.0f}.normalized(), - Vector3{ 0.0f, 0.5f, 1.0f}.normalized(), - Vector3{ 0.5f, 0.5f, 1.0f}.normalized(), - }); - - std::vector> textureCoordinates2D; - if(data.flags & Flag::TextureCoordinates2D) textureCoordinates2D.push_back(std::vector{ - {0.0f, 0.0f}, - {0.5f, 0.0f}, - {1.0f, 0.0f}, - - {0.0f, 0.5f}, - {0.5f, 0.5f}, - {1.0f, 0.5f}, - - {0.0f, 1.0f}, - {0.5f, 1.0f}, - {1.0f, 1.0f} - }); - - std::vector> colors; - if(data.flags & Flag::Colors) colors.push_back(std::vector { - 0x00ff00_rgbf, - 0x808000_rgbf, - 0xff0000_rgbf, - - 0x00ff80_rgbf, - 0x808080_rgbf, - 0xff0080_rgbf, - - 0x00ffff_rgbf, - 0x8080ff_rgbf, - 0xff00ff_rgbf - }); - - std::vector indices{ + Containers::Array attributeData; + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::Position, + Containers::stridedArrayView(vertexData, &vertexData[0].position, + Containers::arraySize(vertexData), sizeof(Vertex))}); + if(data.flags & Flag::Normals) + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::Normal, + Containers::stridedArrayView(vertexData, &vertexData[0].normal, + Containers::arraySize(vertexData), sizeof(Vertex))}); + if(data.flags & Flag::TextureCoordinates2D) + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexData, &vertexData[0].textureCoordinates, + Containers::arraySize(vertexData), sizeof(Vertex))}); + if(data.flags & Flag::Colors) + arrayAppend(attributeData, Trade::MeshAttributeData{ + Trade::MeshAttribute::Color, + Containers::stridedArrayView(vertexData, &vertexData[0].color, + Containers::arraySize(vertexData), sizeof(Vertex))}); + + const UnsignedByte indexData[]{ 0, 1, 4, 0, 4, 3, 1, 2, 5, 1, 5, 4, 3, 4, 7, 3, 7, 6, 4, 5, 8, 4, 8, 7 }; - /* Duplicate everything if data are non-indexed */ - if(data.flags & Flag::NonIndexed) { - positions = duplicate(indices, positions); - - if(data.flags & Flag::Normals) - normals[0] = duplicate(indices, normals[0]); - - if(data.flags & Flag::TextureCoordinates2D) - textureCoordinates2D[0] = duplicate(indices, textureCoordinates2D[0]); + Trade::MeshData meshData{MeshPrimitive::Triangles, + {}, indexData, Trade::MeshIndexData{indexData}, + {}, vertexData, std::move(attributeData)}; - if(data.flags & Flag::Colors) - colors[0] = duplicate(indices, colors[0]); - - indices.clear(); - } + /* Duplicate everything if data is non-indexed */ + if(data.flags & Flag::NonIndexed) meshData = duplicate(meshData); MAGNUM_VERIFY_NO_GL_ERROR(); CompileFlags flags; if(data.flags & Flag::GeneratedFlatNormals) flags |= CompileFlag::GenerateFlatNormals; - else if(data.flags & Flag::GeneratedSmoothNormals) + if(data.flags & Flag::GeneratedSmoothNormals) flags |= CompileFlag::GenerateSmoothNormals; - GL::Mesh mesh = compile(Trade::MeshData3D{MeshPrimitive::Triangles, indices, {positions}, normals, textureCoordinates2D, colors}, flags); + GL::Mesh mesh = compile(T{std::move(meshData)}, flags); MAGNUM_VERIFY_NO_GL_ERROR(); @@ -527,6 +542,137 @@ void CompileGLTest::threeDimensions() { } } +void CompileGLTest::unknownAttribute() { + Trade::MeshData data{MeshPrimitive::Triangles, + nullptr, {Trade::MeshAttributeData{Trade::meshAttributeCustom(115), + VertexFormat::Short, nullptr}}}; + + std::ostringstream out; + Warning redirectError{&out}; + MeshTools::compile(data); + CORRADE_COMPARE(out.str(), + "MeshTools::compile(): ignoring unknown attribute Trade::MeshAttribute::Custom(115)\n"); +} + +void CompileGLTest::generateNormalsNoPosition() { + Trade::MeshData data{MeshPrimitive::Triangles, 1}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::compile(data, CompileFlag::GenerateFlatNormals); + CORRADE_COMPARE(out.str(), + "MeshTools::compile(): the mesh has no positions, can't generate normals\n"); +} + +void CompileGLTest::generateNormals2DPosition() { + Trade::MeshData data{MeshPrimitive::Triangles, + nullptr, {Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, nullptr}}}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::compile(data, CompileFlag::GenerateFlatNormals); + CORRADE_COMPARE(out.str(), + "MeshTools::compile(): can't generate normals for VertexFormat::Vector2 positions\n"); +} + +void CompileGLTest::externalBuffers() { + auto&& data = DataExternal[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + /* + 6-----7-----8 + | /| /| + | / | / | + |/ |/ | + 3-----4-----5 + | /| /| + | / | / | + |/ |/ | + 0-----1-----2 + */ + Vector2 positions[] { + {-0.75f, -0.75f}, + { 0.00f, -0.75f}, + { 0.75f, -0.75f}, + + {-0.75f, 0.00f}, + { 0.00f, 0.00f}, + { 0.75f, 0.00f}, + + {-0.75f, 0.75f}, + { 0.0f, 0.75f}, + { 0.75f, 0.75f} + }; + + const UnsignedShort indexData[]{ + 0, 1, 4, 0, 4, 3, + 1, 2, 5, 1, 5, 4, + 3, 4, 7, 3, 7, 6, + 4, 5, 8, 4, 8, 7 + }; + + Trade::MeshData meshData{MeshPrimitive::Triangles, + {}, indexData, Trade::MeshIndexData{indexData}, + {}, positions, {Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)}}}; + + /* Duplicate everything if data is non-indexed */ + if(!data.indexed) meshData = duplicate(meshData); + + GL::Buffer indices{NoCreate}; + if(meshData.isIndexed()) { + indices = GL::Buffer{GL::Buffer::TargetHint::ElementArray}; + indices.setData(meshData.indexData()); + } + + GL::Buffer vertices{GL::Buffer::TargetHint::Array}; + vertices.setData(meshData.vertexData()); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + GL::Mesh mesh{NoCreate}; + if(data.moveIndices && data.moveVertices) + mesh = compile(meshData, std::move(indices), std::move(vertices)); + else if(data.moveIndices && !data.moveVertices) + mesh = compile(meshData, std::move(indices), vertices); + else if(!data.moveIndices && data.moveVertices) + mesh = compile(meshData, indices, std::move(vertices)); + else + mesh = compile(meshData, indices, vertices); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + _framebuffer.clear(GL::FramebufferClear::Color); + _flat2D.setColor(0xff3366_rgbf); + mesh.draw(_flat2D); + + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(COMPILEGLTEST_TEST_DIR, "flat2D.tga"), + (DebugTools::CompareImageToFile{_manager})); +} + +void CompileGLTest::externalBuffersInvalid() { + Trade::MeshData data{MeshPrimitive::Triangles, 5}; + Trade::MeshData indexedData{MeshPrimitive::Triangles, + nullptr, Trade::MeshIndexData{MeshIndexType::UnsignedInt, nullptr}, + {}}; + + std::ostringstream out; + Error redirectError{&out}; + compile(data, GL::Buffer{NoCreate}, GL::Buffer{}); /* this is okay */ + compile(data, GL::Buffer{NoCreate}, GL::Buffer{NoCreate}); + compile(indexedData, GL::Buffer{NoCreate}, GL::Buffer{}); + CORRADE_COMPARE(out.str(), + "MeshTools::compile(): invalid external buffer(s)\n" + "MeshTools::compile(): invalid external buffer(s)\n"); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::CompileGLTest) From 5481581c5ed5ac262f88932a6db1be9c05023bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 22 Jan 2020 16:27:42 +0100 Subject: [PATCH 056/107] MeshTools: implement compressIndices() taking a MeshData. --- doc/changelog.dox | 5 +- src/Magnum/MeshTools/CompressIndices.cpp | 55 +++++++++++ src/Magnum/MeshTools/CompressIndices.h | 25 +++++ .../MeshTools/Test/CompressIndicesTest.cpp | 94 ++++++++++++++++++- 4 files changed, 175 insertions(+), 4 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 1f291d811f..81d97b130a 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -115,8 +115,9 @@ 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) - and @ref MeshTools::duplicate(const Trade::MeshData&, Containers::ArrayView) +- Added @ref MeshTools::interleave(const Trade::MeshData&, Containers::ArrayView), + @ref MeshTools::duplicate(const Trade::MeshData&, Containers::ArrayView) + and @ref MeshTools::compressIndices(const Trade::MeshData&, MeshIndexType) that work directly on the new @ref Trade::MeshData API - Added @ref MeshTools::subdivideInPlace() for allocation-less mesh subdivision diff --git a/src/Magnum/MeshTools/CompressIndices.cpp b/src/Magnum/MeshTools/CompressIndices.cpp index b9bf7091a2..81ecbd47b7 100644 --- a/src/Magnum/MeshTools/CompressIndices.cpp +++ b/src/Magnum/MeshTools/CompressIndices.cpp @@ -28,8 +28,10 @@ #include #include #include +#include #include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { @@ -115,6 +117,59 @@ std::pair, MeshIndexType> compressIndices(const Containe return compressIndices(indices, MeshIndexType::UnsignedShort, offset); } +Trade::MeshData compressIndices(Trade::MeshData&& data, MeshIndexType atLeast) { + CORRADE_ASSERT(data.isIndexed(), "MeshTools::compressIndices(): mesh data not indexed", (Trade::MeshData{MeshPrimitive::Triangles, 0})); + + /* Transfer vertex 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); + } + + /* Compress the indices */ + UnsignedInt offset; + std::pair, MeshIndexType> result; + if(data.indexType() == MeshIndexType::UnsignedInt) { + auto indices = data.indices(); + offset = Math::min(indices); + result = compressIndicesImplementation(indices, atLeast, offset); + } else if(data.indexType() == MeshIndexType::UnsignedShort) { + auto indices = data.indices(); + offset = Math::min(indices); + result = compressIndicesImplementation(indices, atLeast, offset); + } else { + CORRADE_INTERNAL_ASSERT(data.indexType() == MeshIndexType::UnsignedByte); + auto indices = data.indices(); + offset = Math::min(indices); + result = compressIndicesImplementation(indices, atLeast, offset); + } + + /* Recreate the attribute array */ + const UnsignedInt newVertexCount = vertexCount - offset; + Containers::Array attributeData{data.attributeCount()}; + for(UnsignedInt i = 0, max = attributeData.size(); i != max; ++i) { + const UnsignedInt stride = data.attributeStride(i); + attributeData[i] = Trade::MeshAttributeData{data.attributeName(i), + data.attributeFormat(i), + Containers::StridedArrayView1D{vertexData, vertexData.data() + data.attributeOffset(i) + offset*stride, newVertexCount, stride}}; + } + + Trade::MeshIndexData indices{result.second, result.first}; + return Trade::MeshData{data.primitive(), std::move(result.first), indices, + std::move(vertexData), std::move(attributeData)}; +} + +Trade::MeshData compressIndices(const Trade::MeshData& data, MeshIndexType atLeast) { + return compressIndices(Trade::MeshData{data.primitive(), + {}, data.indexData(), Trade::MeshIndexData{data.indices()}, + {}, data.vertexData(), Trade::meshAttributeDataNonOwningArray(data.attributeData())}, atLeast); +} + #ifdef MAGNUM_BUILD_DEPRECATED std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> compressIndices(const std::vector& indices) { const auto minmax = Math::minmax(indices); diff --git a/src/Magnum/MeshTools/CompressIndices.h b/src/Magnum/MeshTools/CompressIndices.h index a594a41d51..091e838290 100644 --- a/src/Magnum/MeshTools/CompressIndices.h +++ b/src/Magnum/MeshTools/CompressIndices.h @@ -35,6 +35,7 @@ #include "Magnum/Mesh.h" #include "Magnum/MeshTools/visibility.h" +#include "Magnum/Trade/Trade.h" #ifdef MAGNUM_BUILD_DEPRECATED #include @@ -136,6 +137,30 @@ with @p atLeast set to @ref MeshIndexType::UnsignedShort. */ MAGNUM_MESHTOOLS_EXPORT std::pair, MeshIndexType> compressIndices(const Containers::StridedArrayView2D& indices, Long offset); +/** +@brief Compress mesh data indices +@m_since_latest + +Does the same as @ref compressIndices(const Containers::StridedArrayView2D&, MeshIndexType, Long), +but together with adjusting vertex attribute offsets in the passed +@ref Trade::MeshData instance. This function will unconditionally make a copy +of all vertex data, use @ref compressIndices(Trade::MeshData&&, MeshIndexType) +to avoid that copy. +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData compressIndices(const Trade::MeshData& data, MeshIndexType atLeast = MeshIndexType::UnsignedShort); + +/** +@brief Compress mesh data indices +@m_since_latest + +Compared to @ref compressIndices(const Trade::MeshData&, MeshIndexType) 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. Index and +attribute data are copied always. +@see @ref Trade::MeshData::vertexDataFlags() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData compressIndices(Trade::MeshData&& data, MeshIndexType atLeast = MeshIndexType::UnsignedShort); + #ifdef MAGNUM_BUILD_DEPRECATED /** @brief Compress vertex indices diff --git a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp index 611601291c..d05320743d 100644 --- a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp +++ b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp @@ -32,8 +32,9 @@ #include #include -#include "Magnum/Math/TypeTraits.h" +#include "Magnum/Math/Vector3.h" #include "Magnum/MeshTools/CompressIndices.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { @@ -53,6 +54,10 @@ struct CompressIndicesTest: TestSuite::Tester { void compressDeprecated(); #endif + template void compressMeshData(); + void compressMeshDataMove(); + void compressMeshDataNonIndexed(); + void compressAsShort(); }; @@ -70,11 +75,16 @@ CompressIndicesTest::CompressIndicesTest() { &CompressIndicesTest::compressOffsetNegative, &CompressIndicesTest::compressErasedNonContiguous, &CompressIndicesTest::compressErasedWrongIndexSize, - #ifdef MAGNUM_BUILD_DEPRECATED &CompressIndicesTest::compressDeprecated, #endif + &CompressIndicesTest::compressMeshData, + &CompressIndicesTest::compressMeshData, + &CompressIndicesTest::compressMeshData, + &CompressIndicesTest::compressMeshDataMove, + &CompressIndicesTest::compressMeshDataNonIndexed, + &CompressIndicesTest::compressAsShort}); } @@ -228,6 +238,86 @@ void CompressIndicesTest::compressDeprecated() { } #endif +template void CompressIndicesTest::compressMeshData() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + struct { + Vector2 positions[103]; + Vector3 normals[103]; + } vertexData{}; + vertexData.positions[100] = {1.3f, 0.3f}; + vertexData.positions[101] = {0.87f, 1.1f}; + vertexData.positions[102] = {1.0f, -0.5f}; + vertexData.normals[100] = Vector3::xAxis(); + vertexData.normals[101] = Vector3::yAxis(); + vertexData.normals[102] = Vector3::zAxis(); + + T indices[] = {102, 101, 100, 101, 102}; + 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)} + }}; + CORRADE_COMPARE(data.vertexCount(), 103); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeOffset(1), 103*sizeof(Vector2)); + + Trade::MeshData compressed = compressIndices(data); + CORRADE_COMPARE(compressed.indexCount(), 5); + CORRADE_COMPARE(compressed.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(compressed.indices(), + Containers::arrayView({2, 1, 0, 1, 2}), + TestSuite::Compare::Container); + CORRADE_COMPARE(compressed.vertexCount(), 3); + CORRADE_COMPARE(compressed.attributeOffset(0), 100*sizeof(Vector2)); + CORRADE_COMPARE(compressed.attributeOffset(1), 103*sizeof(Vector2) + 100*sizeof(Vector3)); + CORRADE_COMPARE_AS(compressed.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(compressed.attribute(Trade::MeshAttribute::Normal), + Containers::arrayView({Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}), + TestSuite::Compare::Container); +} + +void CompressIndicesTest::compressMeshDataMove() { + Containers::Array vertexData{103*24}; + Containers::StridedArrayView1D positionView{vertexData, + reinterpret_cast(vertexData.data()), 103, 8}; + Containers::StridedArrayView1D normalView{vertexData, + reinterpret_cast(vertexData.data() + 103*sizeof(Vector2)), 103, 12}; + UnsignedInt indices[] = {102, 101, 100, 101, 102}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + {}, indices, Trade::MeshIndexData{indices}, + std::move(vertexData), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positionView}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normalView} + }}; + CORRADE_COMPARE(data.vertexCount(), 103); + CORRADE_COMPARE(data.attributeOffset(0), 0); + CORRADE_COMPARE(data.attributeOffset(1), 103*sizeof(Vector2)); + + Trade::MeshData compressed = compressIndices(std::move(data)); + CORRADE_COMPARE(compressed.indexCount(), 5); + CORRADE_COMPARE(compressed.indexType(), MeshIndexType::UnsignedShort); + CORRADE_COMPARE_AS(compressed.indices(), + Containers::arrayView({2, 1, 0, 1, 2}), + TestSuite::Compare::Container); + CORRADE_COMPARE(compressed.vertexCount(), 3); + CORRADE_COMPARE(compressed.attributeOffset(0), 100*sizeof(Vector2)); + CORRADE_COMPARE(compressed.attributeOffset(1), 103*sizeof(Vector2) + 100*sizeof(Vector3)); + /* The vertex data should be moved, not copied */ + CORRADE_VERIFY(compressed.vertexData().data() == positionView.data()); +} + +void CompressIndicesTest::compressMeshDataNonIndexed() { + std::ostringstream out; + Error redirectError{&out}; + MeshTools::compressIndices(Trade::MeshData{MeshPrimitive::TriangleFan, 5}); + CORRADE_COMPARE(out.str(), + "MeshTools::compressIndices(): mesh data not indexed\n"); +} + void CompressIndicesTest::compressAsShort() { CORRADE_COMPARE_AS(MeshTools::compressIndicesAs({123, 456}), Containers::arrayView({123, 456}), From 0f4a5adb4dc50b1d4de3edaeab23226f79f08435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 20 Nov 2019 17:15:15 +0100 Subject: [PATCH 057/107] Trade: deprecate AbstractImporter interfaces for MeshDataXD. For backwards compatibility these will delegate to the new MeshData interfaces for 3D (and nothing for 2D, because so far there were no 2D scene importers). --- doc/changelog.dox | 7 ++ src/Magnum/FileCallback.h | 2 +- src/Magnum/Trade/AbstractImporter.cpp | 45 +++++-- src/Magnum/Trade/AbstractImporter.h | 119 +++++++++++++++--- .../Trade/Test/AbstractImporterTest.cpp | 110 +++++++++++++++- 5 files changed, 254 insertions(+), 29 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 81d97b130a..04312abbea 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -378,6 +378,13 @@ See also: @ref GL::Attribute::DataType::Half, @ref GL::DynamicAttribute::DataType::Half and @ref GL::PixelType::Half that are consistent with the @ref Half type used elsewhere. +- @cpp Trade::AbstractImporter::mesh2D() @ce, + @cpp Trade::AbstractImporter::mesh3D() @ce and related APIs are + deprecated in favor of @ref Trade::AbstractImporter::mesh() and the new + @ref Trade::MeshData API. For backwards compatibility, importers + implementing the new API have it exposed through @cpp mesh3D() @ce as well, + returning a subset of @ref Trade::MeshData functionality supported by the + old @cpp Trade::MeshData3D @ce APIs - @cpp Platform::GlfwApplication::Configuration::setCursorMode() @ce and related enum & getter are deprecated in favor of the new extended and more flexible @ref Platform::GlfwApplication::setCursor(). The setting didn't diff --git a/src/Magnum/FileCallback.h b/src/Magnum/FileCallback.h index 3e7665a2c0..f676e79df4 100644 --- a/src/Magnum/FileCallback.h +++ b/src/Magnum/FileCallback.h @@ -67,7 +67,7 @@ enum class InputFileCallbackPolicy: UnsignedByte { * function is called or another file is opened. * * This can be the case for example when importing mesh data using - * @ref Trade::AbstractImporter::mesh3D() --- all vertex data might be + * @ref Trade::AbstractImporter::mesh() --- all vertex data might be * combined in a single binary file and each mesh occupies only a portion * of it. Note, however, that this might not be the case for all importers * --- see documentation of a particular plugin for concrete info. diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index 512794d6b3..45c0a15c7e 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -40,13 +40,16 @@ #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" #include "Magnum/Trade/MeshData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/ObjectData2D.h" #include "Magnum/Trade/ObjectData3D.h" #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/TextureData.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include "Magnum/Trade/MeshData2D.h" +#include "Magnum/Trade/MeshData3D.h" +#endif + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT #include "Magnum/Trade/configure.h" #endif @@ -465,32 +468,41 @@ std::string AbstractImporter::meshAttributeName(MeshAttribute name) { std::string AbstractImporter::doMeshAttributeName(UnsignedShort) { return {}; } +#ifdef MAGNUM_BUILD_DEPRECATED UnsignedInt AbstractImporter::mesh2DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh2DCount(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH return doMesh2DCount(); + CORRADE_IGNORE_DEPRECATED_POP } UnsignedInt AbstractImporter::doMesh2DCount() const { return 0; } Int AbstractImporter::mesh2DForName(const std::string& name) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh2DForName(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH return doMesh2DForName(name); + CORRADE_IGNORE_DEPRECATED_POP } Int AbstractImporter::doMesh2DForName(const std::string&) { return -1; } std::string AbstractImporter::mesh2DName(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh2DName(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ASSERT(id < doMesh2DCount(), "Trade::AbstractImporter::mesh2DName(): index" << id << "out of range for" << doMesh2DCount() << "entries", {}); return doMesh2DName(id); + CORRADE_IGNORE_DEPRECATED_POP } std::string AbstractImporter::doMesh2DName(UnsignedInt) { return {}; } Containers::Optional AbstractImporter::mesh2D(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh2D(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ASSERT(id < doMesh2DCount(), "Trade::AbstractImporter::mesh2D(): index" << id << "out of range for" << doMesh2DCount() << "entries", {}); return doMesh2D(id); + CORRADE_IGNORE_DEPRECATED_POP } Containers::Optional AbstractImporter::doMesh2D(UnsignedInt) { @@ -499,35 +511,54 @@ Containers::Optional AbstractImporter::doMesh2D(UnsignedInt) { UnsignedInt AbstractImporter::mesh3DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh3DCount(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH return doMesh3DCount(); + CORRADE_IGNORE_DEPRECATED_POP } -UnsignedInt AbstractImporter::doMesh3DCount() const { return 0; } +UnsignedInt AbstractImporter::doMesh3DCount() const { + return doMeshCount(); +} Int AbstractImporter::mesh3DForName(const std::string& name) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh3DForName(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH return doMesh3DForName(name); + CORRADE_IGNORE_DEPRECATED_POP } -Int AbstractImporter::doMesh3DForName(const std::string&) { return -1; } +Int AbstractImporter::doMesh3DForName(const std::string& name) { + return doMeshForName(name); +} std::string AbstractImporter::mesh3DName(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh3DName(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ASSERT(id < doMesh3DCount(), "Trade::AbstractImporter::mesh3DName(): index" << id << "out of range for" << doMesh3DCount() << "entries", {}); return doMesh3DName(id); + CORRADE_IGNORE_DEPRECATED_POP } -std::string AbstractImporter::doMesh3DName(UnsignedInt) { return {}; } +std::string AbstractImporter::doMesh3DName(const UnsignedInt id) { + return doMeshName(id); +} Containers::Optional AbstractImporter::mesh3D(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh3D(): no file opened", {}); + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ASSERT(id < doMesh3DCount(), "Trade::AbstractImporter::mesh3D(): index" << id << "out of range for" << doMesh3DCount() << "entries", {}); return doMesh3D(id); + CORRADE_IGNORE_DEPRECATED_POP } -Containers::Optional AbstractImporter::doMesh3D(UnsignedInt) { - CORRADE_ASSERT(false, "Trade::AbstractImporter::mesh3D(): not implemented", {}); +Containers::Optional AbstractImporter::doMesh3D(const UnsignedInt id) { + Containers::Optional out = doMesh(id); + CORRADE_IGNORE_DEPRECATED_PUSH + if(out) return MeshData3D{*out}; + CORRADE_IGNORE_DEPRECATED_POP + return Containers::NullOpt; } +#endif UnsignedInt AbstractImporter::materialCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::materialCount(): no file opened", {}); diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index bcc3374300..29fffef873 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -749,30 +749,34 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ std::string meshAttributeName(MeshAttribute name); + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Two-dimensional mesh count * * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref meshCount() instead. */ - UnsignedInt mesh2DCount() const; + CORRADE_DEPRECATED("use meshCount() instead") UnsignedInt mesh2DCount() const; /** * @brief Two-dimensional mesh ID for given name * * If no mesh for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. + * @m_deprecated_since_latest Use @ref meshForName() instead. * @see @ref mesh2DName() */ - Int mesh2DForName(const std::string& name); + CORRADE_DEPRECATED("use meshForName() instead") Int mesh2DForName(const std::string& name); /** * @brief Two-dimensional mesh name * @param id Mesh ID, from range [0, @ref mesh2DCount()). * * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref meshName() instead. * @see @ref mesh2DForName() */ - std::string mesh2DName(UnsignedInt id); + CORRADE_DEPRECATED("use meshName() instead") std::string mesh2DName(UnsignedInt id); /** * @brief Two-dimensional mesh @@ -780,29 +784,39 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given mesh or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref mesh() instead. */ - Containers::Optional mesh2D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + CORRADE_DEPRECATED("use mesh() instead") Containers::Optional mesh2D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_POP - /** @brief Three-dimensional mesh count */ - UnsignedInt mesh3DCount() const; + /** + * @brief Three-dimensional mesh count + * + * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref meshCount() instead. + */ + CORRADE_DEPRECATED("use meshCount() instead") UnsignedInt mesh3DCount() const; /** * @brief Three-dimensional mesh ID for given name * * If no mesh for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. + * @m_deprecated_since_latest Use @ref meshForName() instead. * @see @ref mesh3DName() */ - Int mesh3DForName(const std::string& name); + CORRADE_DEPRECATED("use meshForName() instead") Int mesh3DForName(const std::string& name); /** * @brief Three-dimensional mesh name * @param id Mesh ID, from range [0, @ref mesh3DCount()). * * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref meshName() instead. * @see @ref mesh3DForName() */ - std::string mesh3DName(UnsignedInt id); + CORRADE_DEPRECATED("use meshName() instead") std::string mesh3DName(UnsignedInt id); /** * @brief Three-dimensional mesh @@ -810,8 +824,12 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given mesh or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @m_deprecated_since_latest Use @ref meshName() instead. */ - Containers::Optional mesh3D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + CORRADE_DEPRECATED("use mesh() instead") Containers::Optional mesh3D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_POP + #endif /** * @brief Material count @@ -1280,53 +1298,118 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ virtual std::string doMeshAttributeName(UnsignedShort name); + #ifdef MAGNUM_BUILD_DEPRECATED /** * @brief Implementation for @ref mesh2DCount() * - * Default implementation returns @cpp 0 @ce. - */ + * Default implementation returns @cpp 0 @ce. There weren't any + * importers in existence known to implement 2D mesh import, so unlike + * @ref doMesh3DCount() this function doesn't delegate to + * @ref doMeshCount(). + * @m_deprecated_since_latest Implement @ref doMeshCount() instead. + */ + /* MSVC warns when overriding such methods and there's no way to + suppress that warning, making the RT build (which treats deprecation + warnings as errors) fail and other builds extremely noisy. So + disabling those on MSVC. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMeshCount() instead") + #endif virtual UnsignedInt doMesh2DCount() const; /** * @brief Implementation for @ref mesh2DForName() * - * Default implementation returns @cpp -1 @ce. + * Default implementation returns @cpp -1 @ce. There weren't any + * importers in existence known to implement 2D mesh import, so unlike + * @ref doMesh3DForName() this function doesn't delegate to + * @ref doMeshForName(). + * @m_deprecated_since_latest Implement @ref doMeshForName() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMeshForName() instead") + #endif /* See above */ virtual Int doMesh2DForName(const std::string& name); /** * @brief Implementation for @ref mesh2DName() * - * Default implementation returns empty string. + * Default implementation returns empty string. There weren't any + * importers in existence known to implement 2D mesh import, so unlike + * @ref doMesh3DName() this function doesn't delegate to + * @ref doMeshName(). + * @m_deprecated_since_latest Implement @ref doMeshName() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMeshName() instead") + #endif /* See above */ virtual std::string doMesh2DName(UnsignedInt id); - /** @brief Implementation for @ref mesh2D() */ + /** + * @brief Implementation for @ref mesh2D() + * + * There weren't any importers in existence known to implement 2D mesh + * import, so unlike @ref doMesh3D() this function doesn't + * delegate to @ref doMesh(). + * @m_deprecated_since_latest Implement @ref doMesh() instead. + */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMesh() instead") + #endif /* See above */ virtual Containers::Optional doMesh2D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_POP /** * @brief Implementation for @ref mesh3DCount() * - * Default implementation returns @cpp 0 @ce. + * Default implementation returns @ref doMeshCount() for backwards + * compatibility. + * @m_deprecated_since_latest Implement @ref doMeshCount() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMeshCount() instead") + #endif /* See above */ virtual UnsignedInt doMesh3DCount() const; /** * @brief Implementation for @ref mesh3DForName() * - * Default implementation returns @cpp -1 @ce. + * Default implementation returns @ref doMeshForName() for backwards + * compatibility. + * @m_deprecated_since_latest Implement @ref doMeshForName() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMeshForName() instead") + #endif /* See above */ virtual Int doMesh3DForName(const std::string& name); /** * @brief Implementation for @ref mesh3DName() * - * Default implementation returns empty string. + * Default implementation returns @ref doMeshName() for backwards + * compatibility. + * @m_deprecated_since_latest Implement @ref doMeshName() instead. */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMeshName() instead") + #endif /* See above */ virtual std::string doMesh3DName(UnsignedInt id); - /** @brief Implementation for @ref mesh3D() */ + /** + * @brief Implementation for @ref mesh3D() + * + * Default implementation returns @ref doMesh() converted to + * @ref MeshData3D for backwards compatibility. + * @m_deprecated_since_latest Implement @ref doMesh() instead. + */ + CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ + #if !(defined(CORRADE_TARGET_MSVC) && !defined(CORRADE_TARGET_CLANG)) + CORRADE_DEPRECATED("implement doMesh() instead") + #endif /* See above */ virtual Containers::Optional doMesh3D(UnsignedInt id); + CORRADE_IGNORE_DEPRECATED_POP + #endif /** * @brief Implementation for @ref materialCount() diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 9dc4256b62..1f3a7c29d0 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -39,14 +39,17 @@ #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" #include "Magnum/Trade/MeshData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/MeshObjectData2D.h" #include "Magnum/Trade/MeshObjectData3D.h" #include "Magnum/Trade/PhongMaterialData.h" #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/TextureData.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include "Magnum/Trade/MeshData2D.h" +#include "Magnum/Trade/MeshData3D.h" +#endif + #include "configure.h" namespace Magnum { namespace Trade { namespace Test { namespace { @@ -161,6 +164,9 @@ struct AbstractImporterTest: TestSuite::Tester { void object3DOutOfRange(); void mesh(); + #ifdef MAGNUM_BUILD_DEPRECATED + void meshDeprecatedFallback(); + #endif void meshCountNotImplemented(); void meshCountNoFile(); void meshForNameNotImplemented(); @@ -181,6 +187,7 @@ struct AbstractImporterTest: TestSuite::Tester { void meshAttributeNameNotImplemented(); void meshAttributeNameNotCustom(); + #ifdef MAGNUM_BUILD_DEPRECATED void mesh2D(); void mesh2DCountNotImplemented(); void mesh2DCountNoFile(); @@ -204,6 +211,7 @@ struct AbstractImporterTest: TestSuite::Tester { void mesh3DNotImplemented(); void mesh3DNoFile(); void mesh3DOutOfRange(); + #endif void material(); void materialCountNotImplemented(); @@ -405,6 +413,9 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::object3DOutOfRange, &AbstractImporterTest::mesh, + #ifdef MAGNUM_BUILD_DEPRECATED + &AbstractImporterTest::meshDeprecatedFallback, + #endif &AbstractImporterTest::meshCountNotImplemented, &AbstractImporterTest::meshCountNoFile, &AbstractImporterTest::meshForNameNotImplemented, @@ -425,6 +436,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::meshAttributeNameNotImplemented, &AbstractImporterTest::meshAttributeNameNotCustom, + #ifdef MAGNUM_BUILD_DEPRECATED &AbstractImporterTest::mesh2D, &AbstractImporterTest::mesh2DCountNotImplemented, &AbstractImporterTest::mesh2DCountNoFile, @@ -448,6 +460,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::mesh3DNotImplemented, &AbstractImporterTest::mesh3DNoFile, &AbstractImporterTest::mesh3DOutOfRange, + #endif &AbstractImporterTest::material, &AbstractImporterTest::materialCountNotImplemented, @@ -2169,6 +2182,45 @@ void AbstractImporterTest::mesh() { CORRADE_COMPARE(data->importerState(), &state); } +#ifdef MAGNUM_BUILD_DEPRECATED +void AbstractImporterTest::meshDeprecatedFallback() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + Int doMeshForName(const std::string& name) override { + if(name == "eighth") return 7; + else return -1; + } + std::string doMeshName(UnsignedInt id) override { + if(id == 7) return "eighth"; + else return {}; + } + Containers::Optional doMesh(UnsignedInt id) override { + if(id == 7) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, &state}; + else return {}; + } + } importer; + + CORRADE_IGNORE_DEPRECATED_PUSH + /* Nothing done for 2D as there were no known importers for these */ + CORRADE_COMPARE(importer.mesh2DCount(), 0); + CORRADE_COMPARE(importer.mesh2DForName("eighth"), -1); + + /* For 3D it's called through */ + CORRADE_COMPARE(importer.mesh3DCount(), 8); + CORRADE_COMPARE(importer.mesh3DForName("eighth"), 7); + CORRADE_COMPARE(importer.mesh3DName(7), "eighth"); + + auto data = importer.mesh3D(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + CORRADE_IGNORE_DEPRECATED_POP +} +#endif + void AbstractImporterTest::meshCountNotImplemented() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -2470,6 +2522,7 @@ void AbstractImporterTest::meshAttributeNameNotCustom() { "Trade::AbstractImporter::meshAttributeName(): Trade::MeshAttribute::Position is not custom\n"); } +#ifdef MAGNUM_BUILD_DEPRECATED void AbstractImporterTest::mesh2D() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -2485,12 +2538,15 @@ void AbstractImporterTest::mesh2D() { if(id == 7) return "eighth"; else return {}; } + CORRADE_IGNORE_DEPRECATED_PUSH Containers::Optional doMesh2D(UnsignedInt id) override { if(id == 7) return MeshData2D{{}, {}, {{}}, {}, {}, &state}; else return {}; } + CORRADE_IGNORE_DEPRECATED_POP } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh2DCount(), 8); CORRADE_COMPARE(importer.mesh2DForName("eighth"), 7); CORRADE_COMPARE(importer.mesh2DName(7), "eighth"); @@ -2498,6 +2554,7 @@ void AbstractImporterTest::mesh2D() { auto data = importer.mesh2D(7); CORRADE_VERIFY(data); CORRADE_COMPARE(data->importerState(), &state); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh2DCountNotImplemented() { @@ -2507,7 +2564,9 @@ void AbstractImporterTest::mesh2DCountNotImplemented() { void doClose() override {} } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh2DCount(), 0); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh2DCountNoFile() { @@ -2520,7 +2579,9 @@ void AbstractImporterTest::mesh2DCountNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2DCount(); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2DCount(): no file opened\n"); } @@ -2531,7 +2592,9 @@ void AbstractImporterTest::mesh2DForNameNotImplemented() { void doClose() override {} } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh2DForName(""), -1); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh2DForNameNoFile() { @@ -2544,7 +2607,9 @@ void AbstractImporterTest::mesh2DForNameNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2DForName(""); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2DForName(): no file opened\n"); } @@ -2557,7 +2622,9 @@ void AbstractImporterTest::mesh2DNameNotImplemented() { UnsignedInt doMesh2DCount() const override { return 8; } } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh2DName(7), ""); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh2DNameNoFile() { @@ -2570,7 +2637,9 @@ void AbstractImporterTest::mesh2DNameNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2DName(42); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2DName(): no file opened\n"); } @@ -2586,7 +2655,9 @@ void AbstractImporterTest::mesh2DNameOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2DName(8); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2DName(): index 8 out of range for 8 entries\n"); } @@ -2602,7 +2673,9 @@ void AbstractImporterTest::mesh2DNotImplemented() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2D(7); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2D(): not implemented\n"); } @@ -2616,7 +2689,9 @@ void AbstractImporterTest::mesh2DNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2D(42); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2D(): no file opened\n"); } @@ -2632,7 +2707,9 @@ void AbstractImporterTest::mesh2DOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh2D(8); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh2D(): index 8 out of range for 8 entries\n"); } @@ -2651,12 +2728,15 @@ void AbstractImporterTest::mesh3D() { if(id == 7) return "eighth"; else return {}; } + CORRADE_IGNORE_DEPRECATED_PUSH Containers::Optional doMesh3D(UnsignedInt id) override { if(id == 7) return MeshData3D{{}, {}, {{}}, {}, {}, {}, &state}; else return {}; } + CORRADE_IGNORE_DEPRECATED_POP } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh3DCount(), 8); CORRADE_COMPARE(importer.mesh3DForName("eighth"), 7); CORRADE_COMPARE(importer.mesh3DName(7), "eighth"); @@ -2664,6 +2744,7 @@ void AbstractImporterTest::mesh3D() { auto data = importer.mesh3D(7); CORRADE_VERIFY(data); CORRADE_COMPARE(data->importerState(), &state); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh3DCountNotImplemented() { @@ -2673,7 +2754,9 @@ void AbstractImporterTest::mesh3DCountNotImplemented() { void doClose() override {} } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh3DCount(), 0); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh3DCountNoFile() { @@ -2686,7 +2769,9 @@ void AbstractImporterTest::mesh3DCountNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3DCount(); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3DCount(): no file opened\n"); } @@ -2697,7 +2782,9 @@ void AbstractImporterTest::mesh3DForNameNotImplemented() { void doClose() override {} } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh3DForName(""), -1); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh3DForNameNoFile() { @@ -2710,7 +2797,9 @@ void AbstractImporterTest::mesh3DForNameNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3DForName(""); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3DForName(): no file opened\n"); } @@ -2723,7 +2812,9 @@ void AbstractImporterTest::mesh3DNameNotImplemented() { UnsignedInt doMesh3DCount() const override { return 8; } } importer; + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(importer.mesh3DName(7), ""); + CORRADE_IGNORE_DEPRECATED_POP } void AbstractImporterTest::mesh3DNameNoFile() { @@ -2736,7 +2827,9 @@ void AbstractImporterTest::mesh3DNameNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3DName(42); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3DName(): no file opened\n"); } @@ -2752,7 +2845,9 @@ void AbstractImporterTest::mesh3DNameOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3DName(8); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3DName(): index 8 out of range for 8 entries\n"); } @@ -2768,8 +2863,12 @@ void AbstractImporterTest::mesh3DNotImplemented() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3D(7); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3D(): not implemented\n"); + CORRADE_IGNORE_DEPRECATED_POP + /* Not mesh3D() because this one delegates into mesh() for backwards + compatibility */ + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): not implemented\n"); } void AbstractImporterTest::mesh3DNoFile() { @@ -2782,7 +2881,9 @@ void AbstractImporterTest::mesh3DNoFile() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3D(42); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3D(): no file opened\n"); } @@ -2798,9 +2899,12 @@ void AbstractImporterTest::mesh3DOutOfRange() { std::ostringstream out; Error redirectError{&out}; + CORRADE_IGNORE_DEPRECATED_PUSH importer.mesh3D(8); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh3D(): index 8 out of range for 8 entries\n"); } +#endif void AbstractImporterTest::material() { struct: AbstractImporter { From 006790969a7ee5d9ddcf02fd587fe545e4b8d8c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 18 Jan 2020 19:35:51 +0100 Subject: [PATCH 058/107] Trade, MeshTools: deprecate MeshDataXD and everything that uses it. --- doc/changelog-old.dox | 4 +- doc/changelog.dox | 30 +++++++------- doc/snippets/MagnumTrade.cpp | 17 +++++++- src/Magnum/GL/Mesh.h | 4 +- src/Magnum/MeshTools/Compile.cpp | 17 +++++--- src/Magnum/MeshTools/Compile.h | 36 +++++++++++------ src/Magnum/MeshTools/Test/CompileGLTest.cpp | 40 ++++++++++++++++++- src/Magnum/Trade/AbstractImporter.cpp | 12 +++--- src/Magnum/Trade/AbstractImporter.h | 4 +- src/Magnum/Trade/CMakeLists.txt | 28 +++++++------ src/Magnum/Trade/MeshData2D.cpp | 6 +++ src/Magnum/Trade/MeshData2D.h | 18 ++++++++- src/Magnum/Trade/MeshData3D.cpp | 7 ++++ src/Magnum/Trade/MeshData3D.h | 18 ++++++++- .../Trade/Test/AbstractImporterTest.cpp | 2 + src/Magnum/Trade/Test/CMakeLists.txt | 14 +++++-- src/Magnum/Trade/Test/MeshData2DTest.cpp | 9 ++++- src/Magnum/Trade/Test/MeshData3DTest.cpp | 9 ++++- src/Magnum/Trade/Trade.h | 6 ++- 19 files changed, 208 insertions(+), 73 deletions(-) diff --git a/doc/changelog-old.dox b/doc/changelog-old.dox index 1ba7d039d6..40a962470e 100644 --- a/doc/changelog-old.dox +++ b/doc/changelog-old.dox @@ -510,8 +510,8 @@ No dependency changes in this release. - New @ref Shaders::Generic class with common definitions, so you can configure mesh for the generic shader and render it with any other compatible shader -- Convenience @cpp hasNormals() @ce, @cpp hasTextureCoords2D() @ce functions to - @ref Trade::MeshData2D and @ref Trade::MeshData3D +- Convenience @cpp hasNormals() @ce, @cpp hasTextureCoords2D() @ce functions + in @cpp Trade::MeshData2D @ce and @cpp Trade::MeshData3D @ce - OpenGL ES 3.0 build now shares list of vendor extensions with OpenGL ES 2.0 build (i.e. only those extensions that aren't part of ES 3.0 are present in @cpp Extensions @ce) diff --git a/doc/changelog.dox b/doc/changelog.dox index 04312abbea..160c07cdcd 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -379,6 +379,8 @@ See also: @ref GL::DynamicAttribute::DataType::Half and @ref GL::PixelType::Half that are consistent with the @ref Half type used elsewhere. - @cpp Trade::AbstractImporter::mesh2D() @ce, + @cpp Trade::MeshData2D @ce, @cpp Trade::MeshData3D @ce, + @cpp Trade::AbstractImporter::mesh2D() @ce, @cpp Trade::AbstractImporter::mesh3D() @ce and related APIs are deprecated in favor of @ref Trade::AbstractImporter::mesh() and the new @ref Trade::MeshData API. For backwards compatibility, importers @@ -721,8 +723,8 @@ Released 2019-10-24, tagged as location - @ref MeshTools::generateSmoothNormals() for generating weighted smooth normals of indexed meshes (see [mosra/magnum#229](https://github.com/mosra/magnum/pull/229)) -- @ref MeshTools::compile(const Trade::MeshData3D&, CompileFlags) now accepts - optional flags to control normal generation +- @cpp MeshTools::compile(const Trade::MeshData3D&, CompileFlags) @ce now + accepts optional flags to control normal generation @subsubsection changelog-2019-10-new-platform Platform libraries @@ -1108,7 +1110,7 @@ Released 2019-10-24, tagged as - Reading of float textures on ES3 contexts using @ref DebugTools::textureSubImage() returned a zero-sized image by accident - @ref MeshTools::compile() was producing an incorrect mesh when - @ref Trade::MeshData3D had both colors and texture coordinates + @cpp Trade::MeshData @ce had both colors and texture coordinates - Properly zero-initializing the UTF-8 buffer in @ref Platform::GlfwApplication::textInputEvent() (see [mosra/magnum#324](https://github.com/mosra/magnum/pull/324)) @@ -1514,9 +1516,9 @@ Released 2019-02-04, tagged as @ref GL::OpenGLTester library instead. Note that the deprecated `Magnum/OpenGLTester.h` header is still present, along with all other deprecated GL-specific headers and APIs in the project root. -- Removed five-argument @ref Trade::MeshData2D and six-argument - @ref Trade::MeshData3D constructors that were deprecated since - February 2017. Use the full six/seven-argument versions instead. +- Removed five-argument @cpp Trade::MeshData2D @ce and six-argument + @cpp Trade::MeshData3D @ce constructors that were deprecated since February + 2017. Use the full six/seven-argument versions instead. - Removed @ref Platform application constructors taking @cpp nullptr @ce, deprecated in June 2016 for windowless apps and in March 2017 for windowed apps. Use constructors taking the @ref NoCreate tag instead. @@ -2016,8 +2018,8 @@ Released 2018-10-23, tagged as clearer naming - @ref MeshTools::compile() taking a @ref GL::BufferUsage and returning a tuple was deprecated, use the simpler version taking just - @ref Trade::MeshData2D / @ref Trade::MeshData3D and directly returning a - @ref GL::Mesh instead + @cpp Trade::MeshData2D @ce / @cpp Trade::MeshData3D @ce and directly + returning a @ref GL::Mesh instead - `Shaders::VertexColor::Color` is deprecated, use the direct @ref Shaders::VertexColor::Color3 or @ref Shaders::VertexColor::Color4 alternatives instead @@ -2427,9 +2429,8 @@ Released 2018-05-01, tagged as In particular, @ref GL::Mesh::primitive() now returns @ref GL::MeshPrimitive instead of @ref Magnum::MeshPrimitive, code depending on the return type being implicitly convertible to - @ref Magnum::MeshPrimitive may break. IN all other cases, - @ref Trade::MeshData2D::primitive() "Trade::MeshData*D::primitive()" etc. - returns @ref Magnum::MeshPrimitive. + @ref Magnum::MeshPrimitive may break. In all other cases, + `Trade::MeshData*D::primitive()` etc. returns @ref Magnum::MeshPrimitive. - Configuration value reader/writers are now for only @ref Magnum::MeshPrimitive and @ref Magnum::MeshIndexType, not for @ref GL::MeshPrimitive or @ref GL::MeshIndexType @@ -2864,7 +2865,8 @@ a high-level overview. - New @ref magnum-imageconverter "magnum-imageconverter" utility - Initial implementation of @ref Trade::CameraData and @ref Trade::LightData -- Vertex color support in @ref Trade::MeshData2D and @ref Trade::MeshData3D +- Vertex color support in @cpp Trade::MeshData2D @ce and + @cpp Trade::MeshData3D @ce - @ref Trade::AbstractImageConverter member functions were changed to non-@cpp const @ce to make implementation of complex converter plugins possible - New @ref Trade::AbstractImageConverter::exportToCompressedImage() plugin @@ -3163,8 +3165,8 @@ a high-level overview. - `Math::normalize()` and `Math::denormalize()` had confusing naming and thus are deprecated, use @ref Math::pack() and @ref Math::unpack() from the @ref Magnum/Math/Packing.h header instead -- @ref Trade::MeshData2D and @ref Trade::MeshData3D constructors without the - `colors` parameter are deprecated, use the full ones instead +- @cpp Trade::MeshData2D @ce and @cpp Trade::MeshData3D @ce constructors + without the `colors` parameter are deprecated, use the full ones instead - @cpp Shaders::Generic::Color @ce vertex attribute implicit constructor is deprecated, use a constructor with explicit component count instead - The bundled @ref std::optional implementation was causing serious conflicts diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index e5cbce5423..c8e681417f 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -39,8 +39,6 @@ #include "Magnum/Trade/AnimationData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/MeshData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/ObjectData2D.h" #include "Magnum/Trade/ObjectData3D.h" #include "Magnum/Trade/PhongMaterialData.h" @@ -50,6 +48,13 @@ #include "Magnum/Shaders/Phong.h" #endif +#ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + +#include "Magnum/Trade/MeshData2D.h" +#include "Magnum/Trade/MeshData3D.h" +#endif + using namespace Magnum; using namespace Magnum::Math::Literals; @@ -312,7 +317,9 @@ MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{2.0f}), /* [MeshData-usage-mutable] */ } +#ifdef MAGNUM_BUILD_DEPRECATED { +CORRADE_IGNORE_DEPRECATED_PUSH Trade::MeshData2D& foo(); Trade::MeshData2D& data = foo(); /* [MeshData2D-transform] */ @@ -322,7 +329,9 @@ Matrix3 transformation = Matrix3::rotation(45.0_degf); MeshTools::transformPointsInPlace(transformation, data.positions(0)); /* [MeshData2D-transform] */ +CORRADE_IGNORE_DEPRECATED_POP } +#endif { Trade::ObjectData2D& baz(); @@ -335,7 +344,9 @@ Matrix3 transformation = static_cast(transformation); } +#ifdef MAGNUM_BUILD_DEPRECATED { +CORRADE_IGNORE_DEPRECATED_PUSH Trade::MeshData3D& bar(); Trade::MeshData3D& data = bar(); /* [MeshData3D-transform] */ @@ -345,7 +356,9 @@ Matrix4 transformation = MeshTools::transformPointsInPlace(transformation, data.positions(0)); MeshTools::transformVectorsInPlace(transformation, data.normals(0)); /* [MeshData3D-transform] */ +CORRADE_IGNORE_DEPRECATED_POP } +#endif { Trade::ObjectData3D& fizz(); diff --git a/src/Magnum/GL/Mesh.h b/src/Magnum/GL/Mesh.h index ba6aac1efa..f3d230f991 100644 --- a/src/Magnum/GL/Mesh.h +++ b/src/Magnum/GL/Mesh.h @@ -196,8 +196,8 @@ layout using @ref setIndexBuffer(). You can also use @ref MeshTools::compressInd to conveniently compress the indices based on the range used. There is also @ref MeshTools::compile() function which operates directly on -@ref Trade::MeshData2D / @ref Trade::MeshData3D and returns fully configured -mesh and vertex/index buffers for use with stock shaders. +@ref Trade::MeshData and returns fully configured mesh and vertex/index buffers +for use with stock shaders. @attention Note that, by default, neither vertex buffers nor index buffer is managed (e.g. deleted on destruction) by the mesh, so you have to manage diff --git a/src/Magnum/MeshTools/Compile.cpp b/src/Magnum/MeshTools/Compile.cpp index 703c2f9c87..104819b963 100644 --- a/src/Magnum/MeshTools/Compile.cpp +++ b/src/Magnum/MeshTools/Compile.cpp @@ -27,19 +27,24 @@ #include #include -#include /** @todo remove once MeshDataXD is gone */ #include "Magnum/GL/Buffer.h" #include "Magnum/GL/Mesh.h" #include "Magnum/Math/Vector3.h" -#include "Magnum/Math/Color.h" -#include "Magnum/MeshTools/CompressIndices.h" #include "Magnum/MeshTools/GenerateNormals.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/Interleave.h" #include "Magnum/Trade/MeshData.h" + +#ifdef MAGNUM_BUILD_DEPRECATED +#include + +#include "Magnum/Math/Color.h" +#include "Magnum/MeshTools/CompressIndices.h" +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" +#endif /* This header is included only privately and doesn't introduce any linker dependency, thus it's completely safe */ @@ -191,6 +196,8 @@ GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buff return mesh; } +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH GL::Mesh compile(const Trade::MeshData2D& meshData) { GL::Mesh mesh; mesh.setPrimitive(meshData.primitive()); @@ -264,13 +271,11 @@ GL::Mesh compile(const Trade::MeshData2D& meshData) { return mesh; } -#ifdef MAGNUM_BUILD_DEPRECATED std::tuple, std::unique_ptr> compile(const Trade::MeshData2D& meshData, GL::BufferUsage) { return std::make_tuple(compile(meshData), std::unique_ptr{new GL::Buffer{NoCreate}}, std::unique_ptr{meshData.isIndexed() ? new GL::Buffer{NoCreate} : nullptr}); } -#endif GL::Mesh compile(const Trade::MeshData3D& meshData, CompileFlags flags) { GL::Mesh mesh; @@ -426,12 +431,12 @@ GL::Mesh compile(const Trade::MeshData3D& meshData, CompileFlags flags) { return mesh; } -#ifdef MAGNUM_BUILD_DEPRECATED std::tuple, std::unique_ptr> compile(const Trade::MeshData3D& meshData, GL::BufferUsage) { return std::make_tuple(compile(meshData), std::unique_ptr{new GL::Buffer{NoCreate}}, std::unique_ptr{meshData.isIndexed() ? new GL::Buffer{NoCreate} : nullptr}); } +CORRADE_IGNORE_DEPRECATED_POP #endif }} diff --git a/src/Magnum/MeshTools/Compile.h b/src/Magnum/MeshTools/Compile.h index 50d5e9c34e..58e2a1fadf 100644 --- a/src/Magnum/MeshTools/Compile.h +++ b/src/Magnum/MeshTools/Compile.h @@ -51,7 +51,7 @@ namespace Magnum { namespace MeshTools { @brief Mesh compilation flag @m_since{2019,10} -@see @ref CompileFlags, @ref compile(const Trade::MeshData3D&, CompileFlags) +@see @ref CompileFlags, @ref compile(const Trade::MeshData&, CompileFlags) */ enum class CompileFlag: UnsignedByte { /** @@ -79,7 +79,7 @@ enum class CompileFlag: UnsignedByte { @brief Mesh compilation flags @m_since{2019,10} -@see @ref compile(const Trade::MeshData3D&, CompileFlags) +@see @ref compile(const Trade::MeshData&, CompileFlags) */ typedef Containers::EnumSet CompileFlags; @@ -176,8 +176,11 @@ MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, GL::Bu */ MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer&& vertices); +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief Compile 2D mesh data +@m_deprecated_since_latest Use @ref compile(const Trade::MeshData&, CompileFlags) + instead. Configures a mesh for @ref Shaders::Generic2D shader with vertex buffer and possibly also an index buffer, if the mesh is indexed. Positions are bound to @@ -200,19 +203,23 @@ greater flexibility. @see @ref shaders-generic */ -MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData2D& meshData); +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_DEPRECATED("use compile(const Trade::MeshData&) instead") MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData2D& meshData); +CORRADE_IGNORE_DEPRECATED_POP -#ifdef MAGNUM_BUILD_DEPRECATED -/** @brief @copybrief compile(const Trade::MeshData2D&) - * @m_deprecated_since{2018,10} Use @ref compile(const Trade::MeshData2D&) +/** @brief Compile 2D mesh data + * @m_deprecated_since{2018,10} Use @ref compile(const Trade::MeshData&) * instead. The @p usage parameter is ignored and returned buffer * instances are empty. */ -CORRADE_DEPRECATED("use compile(const Trade::MeshData2D&) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, std::unique_ptr> compile(const Trade::MeshData2D& meshData, GL::BufferUsage usage); -#endif +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_DEPRECATED("use compile(const Trade::MeshData&) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, std::unique_ptr> compile(const Trade::MeshData2D& meshData, GL::BufferUsage usage); +CORRADE_IGNORE_DEPRECATED_POP /** @brief Compile 3D mesh data +@m_deprecated_since_latest Use @ref compile(const Trade::MeshData&, CompileFlags) + instead. Configures mesh for @ref Shaders::Generic3D shader with vertex buffer and possibly also index buffer, if the mesh is indexed. Positions are bound to @@ -236,15 +243,18 @@ greater flexibility. @see @ref shaders-generic */ -MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData3D& meshData, CompileFlags flags = {}); +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_DEPRECATED("use compile(const Trade::MeshData&, CompileFlags) instead") MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData3D& meshData, CompileFlags flags = {}); +CORRADE_IGNORE_DEPRECATED_POP -#ifdef MAGNUM_BUILD_DEPRECATED -/** @brief @copybrief compile(const Trade::MeshData3D&, CompileFlags) - * @m_deprecated_since{2018,10} Use @ref compile(const Trade::MeshData3D&, CompileFlags) +/** @brief Compile 3D mesh data + * @m_deprecated_since{2018,10} Use @ref compile(const Trade::MeshData&, CompileFlags) * instead. The @p usage parameter is ignored and returned buffer * instances are empty. */ -CORRADE_DEPRECATED("use compile(const Trade::MeshData3D&) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, std::unique_ptr> compile(const Trade::MeshData3D& meshData, GL::BufferUsage usage); +CORRADE_IGNORE_DEPRECATED_PUSH +CORRADE_DEPRECATED("use compile(const Trade::MeshData&) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, std::unique_ptr> compile(const Trade::MeshData3D& meshData, GL::BufferUsage usage); +CORRADE_IGNORE_DEPRECATED_POP #endif }} diff --git a/src/Magnum/MeshTools/Test/CompileGLTest.cpp b/src/Magnum/MeshTools/Test/CompileGLTest.cpp index 0ffc8a0e99..4257c215e8 100644 --- a/src/Magnum/MeshTools/Test/CompileGLTest.cpp +++ b/src/Magnum/MeshTools/Test/CompileGLTest.cpp @@ -50,8 +50,14 @@ #include "Magnum/Shaders/Phong.h" #include "Magnum/Shaders/VertexColor.h" #include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Trade/MeshData.h" + +#ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" +#endif #include "configure.h" @@ -81,6 +87,7 @@ struct CompileGLTest: GL::OpenGLTester { public: explicit CompileGLTest(); + /** @todo remove the template once MeshDataXD is gone */ template void twoDimensions(); template void threeDimensions(); void unknownAttribute(); @@ -169,14 +176,27 @@ constexpr Color4ub ImageData[] { CompileGLTest::CompileGLTest() { addInstancedTests({ - &CompileGLTest::twoDimensions, + &CompileGLTest::twoDimensions}, Containers::arraySize(Data2D)); + + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH + addInstancedTests({ &CompileGLTest::twoDimensions}, Containers::arraySize(Data2D)); + CORRADE_IGNORE_DEPRECATED_POP + #endif addInstancedTests({ - &CompileGLTest::threeDimensions, + &CompileGLTest::threeDimensions}, + Containers::arraySize(Data3D)); + + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH + addInstancedTests({ &CompileGLTest::threeDimensions}, Containers::arraySize(Data3D)); + CORRADE_IGNORE_DEPRECATED_POP + #endif addTests({&CompileGLTest::unknownAttribute, &CompileGLTest::generateNormalsNoPosition, @@ -224,12 +244,16 @@ template struct MeshTypeName; template<> struct MeshTypeName { static const char* name() { return "Trade::MeshData"; } }; +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH template<> struct MeshTypeName { static const char* name() { return "Trade::MeshData2D"; } }; template<> struct MeshTypeName { static const char* name() { return "Trade::MeshData3D"; } }; +CORRADE_IGNORE_DEPRECATED_POP +#endif template void CompileGLTest::twoDimensions() { setTestCaseTemplateName(MeshTypeName::name()); @@ -297,7 +321,13 @@ template void CompileGLTest::twoDimensions() { MAGNUM_VERIFY_NO_GL_ERROR(); + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH /** @todo remove once MeshDataXD is gone */ + #endif GL::Mesh mesh = compile(T{std::move(meshData)}); + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_POP + #endif MAGNUM_VERIFY_NO_GL_ERROR(); @@ -431,7 +461,13 @@ template void CompileGLTest::threeDimensions() { flags |= CompileFlag::GenerateFlatNormals; if(data.flags & Flag::GeneratedSmoothNormals) flags |= CompileFlag::GenerateSmoothNormals; + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_PUSH /** @todo remove once MeshDataXD is gone */ + #endif GL::Mesh mesh = compile(T{std::move(meshData)}, flags); + #ifdef MAGNUM_BUILD_DEPRECATED + CORRADE_IGNORE_DEPRECATED_POP + #endif MAGNUM_VERIFY_NO_GL_ERROR(); diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index 45c0a15c7e..3939dae591 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -46,6 +46,8 @@ #include "Magnum/Trade/TextureData.h" #ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" #endif @@ -497,17 +499,17 @@ std::string AbstractImporter::mesh2DName(const UnsignedInt id) { std::string AbstractImporter::doMesh2DName(UnsignedInt) { return {}; } +CORRADE_IGNORE_DEPRECATED_PUSH Containers::Optional AbstractImporter::mesh2D(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh2D(): no file opened", {}); - CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ASSERT(id < doMesh2DCount(), "Trade::AbstractImporter::mesh2D(): index" << id << "out of range for" << doMesh2DCount() << "entries", {}); return doMesh2D(id); - CORRADE_IGNORE_DEPRECATED_POP } Containers::Optional AbstractImporter::doMesh2D(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::mesh2D(): not implemented", {}); } +CORRADE_IGNORE_DEPRECATED_POP UnsignedInt AbstractImporter::mesh3DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh3DCount(): no file opened", {}); @@ -543,21 +545,19 @@ std::string AbstractImporter::doMesh3DName(const UnsignedInt id) { return doMeshName(id); } +CORRADE_IGNORE_DEPRECATED_PUSH Containers::Optional AbstractImporter::mesh3D(const UnsignedInt id) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh3D(): no file opened", {}); - CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_ASSERT(id < doMesh3DCount(), "Trade::AbstractImporter::mesh3D(): index" << id << "out of range for" << doMesh3DCount() << "entries", {}); return doMesh3D(id); - CORRADE_IGNORE_DEPRECATED_POP } Containers::Optional AbstractImporter::doMesh3D(const UnsignedInt id) { Containers::Optional out = doMesh(id); - CORRADE_IGNORE_DEPRECATED_PUSH if(out) return MeshData3D{*out}; - CORRADE_IGNORE_DEPRECATED_POP return Containers::NullOpt; } +CORRADE_IGNORE_DEPRECATED_POP #endif UnsignedInt AbstractImporter::materialCount() const { diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index 29fffef873..a897e07ee5 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -1049,7 +1049,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @see @ref AbstractMaterialData::importerState(), * @ref AnimationData::importerState(), @ref CameraData::importerState(), * @ref ImageData::importerState(), @ref LightData::importerState(), - * @ref MeshData2D::importerState(), @ref MeshData3D::importerState(), + * @ref MeshData::importerState(), * @ref ObjectData2D::importerState(), @ref ObjectData3D::importerState(), * @ref SceneData::importerState(), @ref TextureData::importerState() */ @@ -1400,7 +1400,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @brief Implementation for @ref mesh3D() * * Default implementation returns @ref doMesh() converted to - * @ref MeshData3D for backwards compatibility. + * @cpp MeshData3D @ce for backwards compatibility. * @m_deprecated_since_latest Implement @ref doMesh() instead. */ CORRADE_IGNORE_DEPRECATED_PUSH /* Clang doesn't warn, but GCC does */ diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index cf4b2a93d7..09a34b12ca 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -42,16 +42,6 @@ set(MagnumTrade_GracefulAssert_SRCS CameraData.cpp ImageData.cpp MeshData.cpp - - # These have to be here instead of in MagnumTrade_SRCS because they include - # MeshData.h and call (and thus instantiate) various functions with inline - # asserts. We need the linker to pick the variant with graceful asserts for - # tests, and if there would be two different copies, it may happen it picks - # the non-graceful-assert variant, causing the tests to blow up. Happens - # only on the MSVC linker, but let's be safe and do this everywhere. - MeshData2D.cpp - MeshData3D.cpp - ObjectData2D.cpp ObjectData3D.cpp PhongMaterialData.cpp) @@ -67,8 +57,6 @@ set(MagnumTrade_HEADERS ImageData.h LightData.h MeshData.h - MeshData2D.h - MeshData3D.h MeshObjectData2D.h MeshObjectData3D.h ObjectData2D.h @@ -83,6 +71,22 @@ set(MagnumTrade_HEADERS set(MagnumTrade_PRIVATE_HEADERS Implementation/arrayUtilities.h) +if(MAGNUM_BUILD_DEPRECATED) + list(APPEND MagnumTrade_GracefulAssert_SRCS + # These have to be here instead of in MagnumTrade_SRCS because they + # include MeshData.h and call (and thus instantiate) various functions + # with inline asserts. We need the linker to pick the variant with + # graceful asserts for tests, and if there would be two different + # copies, it may happen it picks the non-graceful-assert variant, + # causing the tests to blow up. Happens only on the MSVC linker, but + # let's be safe and do this everywhere. + MeshData2D.cpp + MeshData3D.cpp) + list(APPEND MagnumTrade_HEADERS + MeshData2D.h + MeshData3D.h) +endif() + if(NOT CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT) configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/configure.h) diff --git a/src/Magnum/Trade/MeshData2D.cpp b/src/Magnum/Trade/MeshData2D.cpp index ecebb77460..b534d1b3eb 100644 --- a/src/Magnum/Trade/MeshData2D.cpp +++ b/src/Magnum/Trade/MeshData2D.cpp @@ -23,6 +23,8 @@ DEALINGS IN THE SOFTWARE. */ +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + #include "MeshData2D.h" #include @@ -64,19 +66,23 @@ MeshData2D::MeshData2D(const MeshData& other): _primitive{other.primitive()}, _i } #endif +CORRADE_IGNORE_DEPRECATED_PUSH /* MSVC warns here */ MeshData2D::MeshData2D(MeshData2D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept #endif = default; +CORRADE_IGNORE_DEPRECATED_POP MeshData2D::~MeshData2D() = default; +CORRADE_IGNORE_DEPRECATED_PUSH /* GCC why you warn on return and not on param */ MeshData2D& MeshData2D::operator=(MeshData2D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept #endif = default; +CORRADE_IGNORE_DEPRECATED_POP std::vector& MeshData2D::indices() { CORRADE_ASSERT(isIndexed(), "Trade::MeshData2D::indices(): the mesh is not indexed", _indices); diff --git a/src/Magnum/Trade/MeshData2D.h b/src/Magnum/Trade/MeshData2D.h index 52170fa31f..0bd12a8b87 100644 --- a/src/Magnum/Trade/MeshData2D.h +++ b/src/Magnum/Trade/MeshData2D.h @@ -25,14 +25,25 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MAGNUM_BUILD_DEPRECATED /** @file * @brief Class @ref Magnum::Trade::MeshData2D + * @m_deprecated_since_latest Use @ref Magnum/Trade/MeshData.h and the + * @ref Magnum::Trade::MeshData "MeshData" class instead. */ +#endif + +#include "Magnum/configure.h" +#ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Trade/MeshData.h" +#ifndef _MAGNUM_NO_DEPRECATED_MESHDATA +CORRADE_DEPRECATED_FILE("use Magnum/Trade/MeshData.h and the MeshData class instead") +#endif + namespace Magnum { namespace Trade { /** @@ -48,9 +59,11 @@ directly to vertex positions: @snippet MagnumTrade.cpp MeshData2D-transform +@m_deprecated_since_latest Use @ref MeshData instead. + @see @ref AbstractImporter::mesh2D(), @ref MeshData3D */ -class MAGNUM_TRADE_EXPORT MeshData2D { +class CORRADE_DEPRECATED("use MeshData instead") MAGNUM_TRADE_EXPORT MeshData2D { public: /** * @brief Constructor @@ -181,5 +194,8 @@ class MAGNUM_TRADE_EXPORT MeshData2D { }; }} +#else +#error use Magnum/Trade/MeshData.h and the MeshData class instead +#endif #endif diff --git a/src/Magnum/Trade/MeshData3D.cpp b/src/Magnum/Trade/MeshData3D.cpp index edb21cd2ab..a3e990ae47 100644 --- a/src/Magnum/Trade/MeshData3D.cpp +++ b/src/Magnum/Trade/MeshData3D.cpp @@ -23,6 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +/* There's no better way to disable file deprecation warnings */ +#define _MAGNUM_NO_DEPRECATED_MESHDATA + #include "MeshData3D.h" #include @@ -69,19 +72,23 @@ MeshData3D::MeshData3D(const MeshData& other): _primitive{other.primitive()}, _i } #endif +CORRADE_IGNORE_DEPRECATED_PUSH /* MSVC warns here */ MeshData3D::MeshData3D(MeshData3D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept #endif = default; +CORRADE_IGNORE_DEPRECATED_POP MeshData3D::~MeshData3D() = default; +CORRADE_IGNORE_DEPRECATED_PUSH /* GCC why you warn on return and not on param */ MeshData3D& MeshData3D::operator=(MeshData3D&&) #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 noexcept #endif = default; +CORRADE_IGNORE_DEPRECATED_POP std::vector& MeshData3D::indices() { CORRADE_ASSERT(isIndexed(), "Trade::MeshData3D::indices(): the mesh is not indexed", _indices); diff --git a/src/Magnum/Trade/MeshData3D.h b/src/Magnum/Trade/MeshData3D.h index c17416783b..804ef8f383 100644 --- a/src/Magnum/Trade/MeshData3D.h +++ b/src/Magnum/Trade/MeshData3D.h @@ -25,14 +25,25 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MAGNUM_BUILD_DEPRECATED /** @file * @brief Class @ref Magnum::Trade::MeshData3D + * @m_deprecated_since_latest Use @ref Magnum/Trade/MeshData.h and the + * @ref Magnum::Trade::MeshData "MeshData" class instead. */ +#endif + +#include "Magnum/configure.h" +#ifdef MAGNUM_BUILD_DEPRECATED #include #include "Magnum/Trade/MeshData.h" +#ifndef _MAGNUM_NO_DEPRECATED_MESHDATA +CORRADE_DEPRECATED_FILE("use Magnum/Trade/MeshData.h and the MeshData class instead") +#endif + namespace Magnum { namespace Trade { /** @@ -48,9 +59,11 @@ to positions and normals: @snippet MagnumTrade.cpp MeshData3D-transform +@m_deprecated_since_latest Use @ref MeshData instead. + @see @ref AbstractImporter::mesh3D(), @ref MeshData2D */ -class MAGNUM_TRADE_EXPORT MeshData3D { +class CORRADE_DEPRECATED("use MeshData instead") MAGNUM_TRADE_EXPORT MeshData3D { public: /** * @brief Constructor @@ -198,5 +211,8 @@ class MAGNUM_TRADE_EXPORT MeshData3D { }; }} +#else +#error use Magnum/Trade/MeshData.h and the MeshData class instead +#endif #endif diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 1f3a7c29d0..1535abb056 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -46,6 +46,8 @@ #include "Magnum/Trade/TextureData.h" #ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + #include "Magnum/Trade/MeshData2D.h" #include "Magnum/Trade/MeshData3D.h" #endif diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index 2c9177347c..f89f44e453 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -49,8 +49,6 @@ corrade_add_test(TradeImageDataTest ImageDataTest.cpp LIBRARIES MagnumTradeTestL corrade_add_test(TradeLightDataTest LightDataTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeMaterialDataTest MaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeMeshDataTest MeshDataTest.cpp LIBRARIES MagnumTradeTestLib) -corrade_add_test(TradeMeshData2DTest MeshData2DTest.cpp LIBRARIES MagnumTrade) -corrade_add_test(TradeMeshData3DTest MeshData3DTest.cpp LIBRARIES MagnumTrade) corrade_add_test(TradeObjectData2DTest ObjectData2DTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeObjectData3DTest ObjectData3DTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeSceneDataTest SceneDataTest.cpp LIBRARIES MagnumTrade) @@ -69,10 +67,18 @@ set_target_properties( TradeImageDataTest TradeLightDataTest TradeMaterialDataTest - TradeMeshData2DTest - TradeMeshData3DTest TradeObjectData2DTest TradeObjectData3DTest TradeSceneDataTest TradeTextureDataTest PROPERTIES FOLDER "Magnum/Trade/Test") + +if(MAGNUM_BUILD_DEPRECATED) + corrade_add_test(TradeMeshData2DTest MeshData2DTest.cpp LIBRARIES MagnumTrade) + corrade_add_test(TradeMeshData3DTest MeshData3DTest.cpp LIBRARIES MagnumTrade) + + set_target_properties( + TradeMeshData2DTest + TradeMeshData3DTest + PROPERTIES FOLDER "Magnum/Trade/Test") +endif() diff --git a/src/Magnum/Trade/Test/MeshData2DTest.cpp b/src/Magnum/Trade/Test/MeshData2DTest.cpp index 34a38b0c8d..dc1def1c7e 100644 --- a/src/Magnum/Trade/Test/MeshData2DTest.cpp +++ b/src/Magnum/Trade/Test/MeshData2DTest.cpp @@ -23,6 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +/* There's no better way to disable file deprecation warnings */ +#define _MAGNUM_NO_DEPRECATED_MESHDATA + #include #include "Magnum/Mesh.h" @@ -42,6 +45,8 @@ struct MeshData2DTest: TestSuite::Tester { void constructMove(); }; +CORRADE_IGNORE_DEPRECATED_PUSH + using namespace Math::Literals; const UnsignedByte Indices[]{12, 1, 0}; @@ -59,7 +64,6 @@ const struct Vertex { }; const int State = 3; -CORRADE_IGNORE_DEPRECATED_PUSH struct { const char* name; const MeshData2D data, dataNonIndexed; @@ -103,7 +107,6 @@ struct { }, &State} } }; -CORRADE_IGNORE_DEPRECATED_POP MeshData2DTest::MeshData2DTest() { addInstancedTests({&MeshData2DTest::construct, @@ -220,6 +223,8 @@ void MeshData2DTest::constructMove() { CORRADE_COMPARE(d.importerState(), &a); } +CORRADE_IGNORE_DEPRECATED_POP + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::MeshData2DTest) diff --git a/src/Magnum/Trade/Test/MeshData3DTest.cpp b/src/Magnum/Trade/Test/MeshData3DTest.cpp index 1244120ad1..e7ab2dd114 100644 --- a/src/Magnum/Trade/Test/MeshData3DTest.cpp +++ b/src/Magnum/Trade/Test/MeshData3DTest.cpp @@ -23,6 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +/* There's no better way to disable file deprecation warnings */ +#define _MAGNUM_NO_DEPRECATED_MESHDATA + #include #include "Magnum/Mesh.h" @@ -43,6 +46,8 @@ struct MeshData3DTest: TestSuite::Tester { void constructMove(); }; +CORRADE_IGNORE_DEPRECATED_PUSH + using namespace Math::Literals; const UnsignedByte Indices[]{12, 1, 0}; @@ -63,7 +68,6 @@ const struct Vertex { }; const int State = 3; -CORRADE_IGNORE_DEPRECATED_PUSH struct { const char* name; const MeshData3D data, dataNonIndexed; @@ -113,7 +117,6 @@ struct { }, &State} } }; -CORRADE_IGNORE_DEPRECATED_POP MeshData3DTest::MeshData3DTest() { addInstancedTests({&MeshData3DTest::construct, @@ -254,6 +257,8 @@ void MeshData3DTest::constructMove() { CORRADE_COMPARE(d.importerState(), &a); } +CORRADE_IGNORE_DEPRECATED_POP + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::MeshData3DTest) diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index 36c16f70bb..d4df385bde 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -74,8 +74,10 @@ class MeshIndexData; class MeshAttributeData; class MeshData; -class MeshData2D; -class MeshData3D; +#ifdef MAGNUM_BUILD_DEPRECATED +class CORRADE_DEPRECATED("use MeshData instead") MeshData2D; +class CORRADE_DEPRECATED("use MeshData instead") MeshData3D; +#endif class MeshObjectData2D; class MeshObjectData3D; class ObjectData2D; From c8de337c061c506801446521f5061f17e9223f5d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 2 Dec 2019 01:38:23 +0100 Subject: [PATCH 059/107] Primitives: port away from MeshDataXD. The internals don't use any std::vector anymore, only the icosphere needs an std::unordered_map to do duplicate removal. Additionally, the most simple primitives are now simply views on constant data, being completely zero-allocation. On a Mac this resulted in the dylib going down from 1.5 MB to 418 kB in Debug, and from 129 kB to 90 kB in Release. Quite nice. The tests are not ported away from MeshDataXD yet as I want to ensure the behavior is *exactly* as before. --- doc/generated/primitives.cpp | 10 +- doc/snippets/MagnumPrimitives.cpp | 3 +- src/Magnum/Primitives/Axis.cpp | 160 +++++++------ src/Magnum/Primitives/Axis.h | 16 +- src/Magnum/Primitives/Capsule.cpp | 56 +++-- src/Magnum/Primitives/Capsule.h | 25 +- src/Magnum/Primitives/Circle.cpp | 150 +++++++----- src/Magnum/Primitives/Circle.h | 22 +- src/Magnum/Primitives/Cone.cpp | 10 +- src/Magnum/Primitives/Cone.h | 23 +- src/Magnum/Primitives/Crosshair.cpp | 41 +++- src/Magnum/Primitives/Crosshair.h | 12 +- src/Magnum/Primitives/Cube.cpp | 224 ++++++++++-------- src/Magnum/Primitives/Cube.h | 22 +- src/Magnum/Primitives/Cylinder.cpp | 10 +- src/Magnum/Primitives/Cylinder.h | 24 +- src/Magnum/Primitives/Gradient.cpp | 86 ++++--- src/Magnum/Primitives/Gradient.h | 28 ++- src/Magnum/Primitives/Grid.cpp | 159 ++++++++----- src/Magnum/Primitives/Grid.h | 19 +- src/Magnum/Primitives/Icosphere.cpp | 131 ++++++---- src/Magnum/Primitives/Icosphere.h | 7 +- .../Primitives/Implementation/Spheroid.cpp | 220 +++++++++++------ .../Primitives/Implementation/Spheroid.h | 19 +- .../Implementation/WireframeSpheroid.cpp | 64 +++-- .../Implementation/WireframeSpheroid.h | 8 +- src/Magnum/Primitives/Line.cpp | 27 ++- src/Magnum/Primitives/Line.h | 14 +- src/Magnum/Primitives/Plane.cpp | 105 +++++--- src/Magnum/Primitives/Plane.h | 12 +- src/Magnum/Primitives/Square.cpp | 86 +++++-- src/Magnum/Primitives/Square.h | 13 +- src/Magnum/Primitives/UVSphere.cpp | 10 +- src/Magnum/Primitives/UVSphere.h | 14 +- src/Magnum/Trade/MeshData.h | 3 +- 35 files changed, 1137 insertions(+), 696 deletions(-) diff --git a/doc/generated/primitives.cpp b/doc/generated/primitives.cpp index ce45693428..300c4da294 100644 --- a/doc/generated/primitives.cpp +++ b/doc/generated/primitives.cpp @@ -452,7 +452,7 @@ std::pair PrimitiveVisualizer::gradient3DVertica } std::pair PrimitiveVisualizer::capsule2DWireframe() { - auto capsule = Primitives::capsule2DWireframe(8, 1, 0.75f); + Trade::MeshData2D capsule = Primitives::capsule2DWireframe(8, 1, 0.75f); MeshTools::transformPointsInPlace(Matrix3::scaling(Vector2{0.75f}), capsule.positions(0)); return {std::move(capsule), "capsule2dwireframe.png"}; } @@ -466,7 +466,7 @@ std::pair PrimitiveVisualizer::crosshair2D() { } std::pair PrimitiveVisualizer::line2D() { - auto line = Primitives::line2D(); + Trade::MeshData2D line = Primitives::line2D(); MeshTools::transformPointsInPlace(Matrix3::translation(Vector2::xAxis(-1.0f))*Matrix3::scaling(Vector2::xScale(2.0f)), line.positions(0)); return {std::move(line), "line2d.png"}; } @@ -476,7 +476,7 @@ std::pair PrimitiveVisualizer::squareWireframe() } std::pair PrimitiveVisualizer::capsule3DWireframe() { - auto capsule = Primitives::capsule3DWireframe(8, 1, 16, 1.0f); + Trade::MeshData3D capsule = Primitives::capsule3DWireframe(8, 1, 16, 1.0f); MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{0.75f}), capsule.positions(0)); return {std::move(capsule), "capsule3dwireframe.png"}; } @@ -506,7 +506,7 @@ std::pair PrimitiveVisualizer::grid3DWireframe() } std::pair PrimitiveVisualizer::line3D() { - auto line = Primitives::line3D(); + Trade::MeshData3D line = Primitives::line3D(); MeshTools::transformPointsInPlace(Matrix4::translation(Vector3::xAxis(-1.0f))*Matrix4::scaling(Vector3::xScale(2.0f)), line.positions(0)); return {std::move(line), "line3d.png"}; } @@ -528,7 +528,7 @@ std::pair PrimitiveVisualizer::squareSolid() { } std::pair PrimitiveVisualizer::capsule3DSolid() { - auto capsule = Primitives::capsule3DSolid(4, 1, 12, 0.75f); + Trade::MeshData3D capsule = Primitives::capsule3DSolid(4, 1, 12, 0.75f); MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{0.75f}), capsule.positions(0)); return {std::move(capsule), "capsule3dsolid.png"}; } diff --git a/doc/snippets/MagnumPrimitives.cpp b/doc/snippets/MagnumPrimitives.cpp index fcac06d215..7b86926d8c 100644 --- a/doc/snippets/MagnumPrimitives.cpp +++ b/doc/snippets/MagnumPrimitives.cpp @@ -26,8 +26,7 @@ #include "Magnum/Math/Color.h" #include "Magnum/Primitives/Gradient.h" #include "Magnum/Primitives/Line.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" using namespace Magnum; diff --git a/src/Magnum/Primitives/Axis.cpp b/src/Magnum/Primitives/Axis.cpp index 500a4ed097..e18f89351f 100644 --- a/src/Magnum/Primitives/Axis.cpp +++ b/src/Magnum/Primitives/Axis.cpp @@ -27,83 +27,99 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -using namespace Math::Literals; - -Trade::MeshData2D axis2D() { - return Trade::MeshData2D{MeshPrimitive::Lines, - {0, 1, - 1, 2, /* X axis */ - 1, 3, - - 4, 5, - 5, 6, /* Y axis */ - 5, 7}, - {{{ 0.0f, 0.0f}, - { 1.0f, 0.0f}, /* X axis */ - { 0.9f, 0.1f}, - { 0.9f, -0.1f}, - - { 0.0f, 0.0f}, - { 0.0f, 1.0f}, /* Y axis */ - { 0.1f, 0.9f}, - {-0.1f, 0.9f}}}, {}, - {{0xff0000_rgbf, - 0xff0000_rgbf, /* X axis */ - 0xff0000_rgbf, - 0xff0000_rgbf, - - 0x00ff00_rgbf, - 0x00ff00_rgbf, /* Y axis */ - 0x00ff00_rgbf, - 0x00ff00_rgbf}}}; +namespace { + +/* not 8-bit because GPUs (and Vulkan) don't like it nowadays */ +constexpr UnsignedShort Indices2D[]{ + 0, 1, + 1, 2, /* X axis */ + 1, 3, + + 4, 5, + 5, 6, /* Y axis */ + 5, 7 +}; +constexpr UnsignedShort Indices3D[]{ + 0, 1, + 1, 2, /* X axis */ + 1, 3, + + 4, 5, + 5, 6, /* Y axis */ + 5, 7, + + 8, 9, + 9, 10, /* Z axis */ + 9, 11 +}; + +constexpr struct Vertex2D { + Vector2 position; + Color3 color; +} Vertices2D[]{ + {{ 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{ 1.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, /* X axis */ + {{ 0.9f, 0.1f}, {1.0f, 0.0f, 0.0f}}, + {{ 0.9f, -0.1f}, {1.0f, 0.0f, 0.0f}}, + + {{ 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}, + {{ 0.0f, 1.0f}, {0.0f, 1.0f, 0.0f}}, /* Y axis */ + {{ 0.1f, 0.9f}, {0.0f, 1.0f, 0.0f}}, + {{-0.1f, 0.9f}, {0.0f, 1.0f, 0.0f}} +}; +constexpr struct Vertex3D { + Vector3 position; + Color3 color; +} Vertices3D[]{ + {{ 0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{ 1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}}, /* X axis */ + {{ 0.9f, 0.1f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + {{ 0.9f, -0.1f, 0.0f}, {1.0f, 0.0f, 0.0f}}, + + {{ 0.0f, 0.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}, + {{ 0.0f, 1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}}, /* Y axis */ + {{ 0.1f, 0.9f, 0.0f}, {0.0f, 1.0f, 0.0f}}, + {{-0.1f, 0.9f, 0.0f}, {0.0f, 1.0f, 0.0f}}, + + {{ 0.0f, 0.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + {{ 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}}, /* Z axis */ + {{ 0.1f, 0.0f, 0.9f}, {0.0f, 0.0f, 1.0f}}, + {{-0.1f, 0.0f, 0.9f}, {0.0f, 0.0f, 1.0f}} +}; + +constexpr Trade::MeshAttributeData Attributes2D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(Vertices2D, &Vertices2D[0].position, + Containers::arraySize(Vertices2D), sizeof(Vertex2D))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::stridedArrayView(Vertices2D, &Vertices2D[0].color, + Containers::arraySize(Vertices2D), sizeof(Vertex2D))} +}; +constexpr Trade::MeshAttributeData Attributes3D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(Vertices3D, &Vertices3D[0].position, + Containers::arraySize(Vertices3D), sizeof(Vertex3D))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::stridedArrayView(Vertices3D, &Vertices3D[0].color, + Containers::arraySize(Vertices3D), sizeof(Vertex3D))} +}; + +} + +Trade::MeshData axis2D() { + return Trade::MeshData{MeshPrimitive::Lines, + {}, Indices2D, Trade::MeshIndexData{Indices2D}, + {}, Vertices2D, Trade::meshAttributeDataNonOwningArray(Attributes2D)}; } -Trade::MeshData3D axis3D() { - return Trade::MeshData3D{MeshPrimitive::Lines, - {0, 1, - 1, 2, /* X axis */ - 1, 3, - - 4, 5, - 5, 6, /* Y axis */ - 5, 7, - - 8, 9, - 9, 10, /* Z axis */ - 9, 11}, - {{{ 0.0f, 0.0f, 0.0f}, - { 1.0f, 0.0f, 0.0f}, /* X axis */ - { 0.9f, 0.1f, 0.0f}, - { 0.9f, -0.1f, 0.0f}, - - { 0.0f, 0.0f, 0.0f}, - { 0.0f, 1.0f, 0.0f}, /* Y axis */ - { 0.1f, 0.9f, 0.0f}, - {-0.1f, 0.9f, 0.0f}, - - { 0.0f, 0.0f, 0.0f}, - { 0.0f, 0.0f, 1.0f}, /* Z axis */ - { 0.1f, 0.0f, 0.9f}, - {-0.1f, 0.0f, 0.9f}}}, {}, {}, - {{0xff0000_rgbf, - 0xff0000_rgbf, /* X axis */ - 0xff0000_rgbf, - 0xff0000_rgbf, - - 0x00ff00_rgbf, - 0x00ff00_rgbf, /* Y axis */ - 0x00ff00_rgbf, - 0x00ff00_rgbf, - - 0x0000ff_rgbf, - 0x0000ff_rgbf, /* Z axis */ - 0x0000ff_rgbf, - 0x0000ff_rgbf}}}; +Trade::MeshData axis3D() { + return Trade::MeshData{MeshPrimitive::Lines, + {}, Indices3D, Trade::MeshIndexData{Indices3D}, + {}, Vertices3D, Trade::meshAttributeDataNonOwningArray(Attributes3D)}; } }} diff --git a/src/Magnum/Primitives/Axis.h b/src/Magnum/Primitives/Axis.h index 169cd44761..5c25f65475 100644 --- a/src/Magnum/Primitives/Axis.h +++ b/src/Magnum/Primitives/Axis.h @@ -37,26 +37,30 @@ namespace Magnum { namespace Primitives { /** @brief 2D axis -Two color-coded arrows for visualizing orientation (XY is RG). Indexed -@ref MeshPrimitive::Lines with vertex colors. +Two color-coded arrows for visualizing orientation (XY is RG). +@ref MeshPrimitive::Lines with @ref MeshIndexType::UnsignedShort indices, +interleaved @ref VertexFormat::Vector2 positions and @ref VertexFormat::Vector3 +colors. The returned instance references data stored in constant memory. @image html primitives-axis2d.png width=256px @see @ref axis3D(), @ref crosshair2D(), @ref line2D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D axis2D(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData axis2D(); /** @brief 3D axis -Three color-coded arrows for visualizing orientation (XYZ is RGB). Indexed -@ref MeshPrimitive::Lines with vertex colors. +Three color-coded arrows for visualizing orientation (XYZ is RGB). +@ref MeshPrimitive::Lines with @ref MeshIndexType::UnsignedShort indices, +interleaved @ref VertexFormat::Vector3 positions and @ref VertexFormat::Vector3 +colors. The returned instance references data stored in constant memory. @image html primitives-axis3d.png width=256px @see @ref axis2D(), @ref crosshair3D(), @ref line3D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D axis3D(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData axis3D(); }} diff --git a/src/Magnum/Primitives/Capsule.cpp b/src/Magnum/Primitives/Capsule.cpp index 53ebed1af4..e52e088813 100644 --- a/src/Magnum/Primitives/Capsule.cpp +++ b/src/Magnum/Primitives/Capsule.cpp @@ -25,28 +25,29 @@ #include "Capsule.h" +#include + #include "Magnum/Math/Color.h" #include "Magnum/Math/Functions.h" #include "Magnum/Mesh.h" #include "Magnum/Primitives/Implementation/Spheroid.h" #include "Magnum/Primitives/Implementation/WireframeSpheroid.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData2D capsule2DWireframe(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const Float halfLength) { +Trade::MeshData capsule2DWireframe(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const Float halfLength) { CORRADE_ASSERT(hemisphereRings >= 1 && cylinderRings >= 1, "Primitives::capsule2DWireframe(): at least one hemisphere ring and one cylinder ring expected", - (Trade::MeshData2D{MeshPrimitive::Triangles, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); - std::vector positions; - positions.reserve(hemisphereRings*4+2+(cylinderRings-1)*2); + Containers::Array vertexData; + arrayReserve(vertexData, hemisphereRings*4+2+(cylinderRings-1)*2); const Rad angleIncrement(Constants::piHalf()/hemisphereRings); const Float cylinderIncrement = 2.0f*halfLength/cylinderRings; /* Bottom cap vertex */ - positions.emplace_back(0.0f, -halfLength-1.0f); + arrayAppend(vertexData, {0.0f, -halfLength-1.0f}); /* Bottom hemisphere */ for(UnsignedInt i = 0; i != hemisphereRings; ++i) { @@ -54,13 +55,13 @@ Trade::MeshData2D capsule2DWireframe(const UnsignedInt hemisphereRings, const Un const std::pair sincos = Math::sincos(angle); const Float x = sincos.first; const Float y = -sincos.second-halfLength; - positions.insert(positions.end(), {{-x, y}, {x, y}}); + arrayAppend(vertexData, {{-x, y}, {x, y}}); } - /* Cylinder (bottom and top vertices are done within caps */ + /* Cylinder (bottom and top vertices are done within caps) */ for(UnsignedInt i = 0; i != cylinderRings-1; ++i) { const Float y = (i+1)*cylinderIncrement-halfLength; - positions.insert(positions.end(), {{-1.0f, y}, {1.0f, y}}); + arrayAppend(vertexData, {{-1.0f, y}, {1.0f, y}}); } /* Top hemisphere */ @@ -69,35 +70,38 @@ Trade::MeshData2D capsule2DWireframe(const UnsignedInt hemisphereRings, const Un const std::pair sincos = Math::sincos(angle); const Float x = sincos.second; const Float y = sincos.first+halfLength; - positions.insert(positions.end(), {{-x, y}, {x, y}}); + arrayAppend(vertexData, {{-x, y}, {x, y}}); } /* Top cap vertex */ - positions.emplace_back(0.0f, halfLength+1.0f); + arrayAppend(vertexData, {0.0f, halfLength+1.0f}); - std::vector indices; - indices.reserve(hemisphereRings*8+cylinderRings*4); + Containers::Array indexData; + arrayReserve(indexData, hemisphereRings*8+cylinderRings*4); /* Bottom cap indices */ - indices.insert(indices.end(), {0, 1, 0, 2}); + arrayAppend(indexData, {0u, 1u, 0u, 2u}); /* Side indices */ for(UnsignedInt i = 0; i != cylinderRings+hemisphereRings*2-2; ++i) - indices.insert(indices.end(), {i*2+1, i*2+3, - i*2+2, i*2+4}); + arrayAppend(indexData, {i*2+1, i*2+3, i*2+2, i*2+4}); /* Top cap indices */ - indices.insert(indices.end(), - {UnsignedInt(positions.size())-3, UnsignedInt(positions.size())-1, - UnsignedInt(positions.size())-2, UnsignedInt(positions.size())-1}); - - return Trade::MeshData2D{MeshPrimitive::Lines, std::move(indices), {std::move(positions)}, {}, {}, nullptr}; + arrayAppend(indexData, + {UnsignedInt(vertexData.size())-3, UnsignedInt(vertexData.size())-1, + UnsignedInt(vertexData.size())-2, UnsignedInt(vertexData.size())-1}); + + Trade::MeshIndexData indices{indexData}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, Containers::arrayView(vertexData)}; + return Trade::MeshData{MeshPrimitive::Lines, + Containers::arrayAllocatorCast(std::move(indexData)), indices, + Containers::arrayAllocatorCast(std::move(vertexData)), {positions}}; } -Trade::MeshData3D capsule3DSolid(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const UnsignedInt segments, const Float halfLength, const CapsuleTextureCoords textureCoords) { +Trade::MeshData capsule3DSolid(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const UnsignedInt segments, const Float halfLength, const CapsuleTextureCoords textureCoords) { CORRADE_ASSERT(hemisphereRings >= 1 && cylinderRings >= 1 && segments >= 3, "Primitives::capsule3DSolid(): at least one hemisphere ring, one cylinder ring and three segments expected", - (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); Implementation::Spheroid capsule(segments, textureCoords == CapsuleTextureCoords::Generate ? Implementation::Spheroid::TextureCoords::Generate : @@ -130,10 +134,10 @@ Trade::MeshData3D capsule3DSolid(const UnsignedInt hemisphereRings, const Unsign return capsule.finalize(); } -Trade::MeshData3D capsule3DWireframe(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const UnsignedInt segments, const Float halfLength) { +Trade::MeshData capsule3DWireframe(const UnsignedInt hemisphereRings, const UnsignedInt cylinderRings, const UnsignedInt segments, const Float halfLength) { CORRADE_ASSERT(hemisphereRings >= 1 && cylinderRings >= 1 && segments >= 4 && segments%4 == 0, "Primitives::capsule3DWireframe(): at least one hemisphere and cylinder ring and multiples of 4 segments expected", - (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); Implementation::WireframeSpheroid capsule(segments/4); diff --git a/src/Magnum/Primitives/Capsule.h b/src/Magnum/Primitives/Capsule.h index fbc53ffd36..b94e465a97 100644 --- a/src/Magnum/Primitives/Capsule.h +++ b/src/Magnum/Primitives/Capsule.h @@ -43,14 +43,15 @@ namespace Magnum { namespace Primitives { @param halfLength Half the length of cylinder part Cylinder of radius @cpp 1.0f @ce along Y axis with hemispheres instead of caps. -Indexed @ref MeshPrimitive::Lines. +@ref MeshPrimitive::Lines with @ref MeshIndexType::UnsignedInt indices and +@ref VertexFormat::Vector2 positions. @image html primitives-capsule2dwireframe.png width=256px @see @ref capsule3DSolid(), @ref capsule3DWireframe(), @ref circle2DWireframe(), @ref squareWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D capsule2DWireframe(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, Float halfLength); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData capsule2DWireframe(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, Float halfLength); /** @brief Whether to generate capsule texture coordinates @@ -74,20 +75,21 @@ enum class CapsuleTextureCoords: UnsignedByte { @param textureCoords Whether to generate texture coordinates Cylinder of radius @cpp 1.0f @ce along Y axis with hemispheres instead of caps. -Indexed @ref MeshPrimitive::Triangles with normals and optional 2D texture -coordinates. If texture coordinates are generated, vertices of one segment are -duplicated for texture wrapping. +@ref MeshPrimitive::Triangles with @ref MeshIndexType::UnsignedInt indices, +interleaved @ref VertexFormat::Vector3 positions, @ref VertexFormat::Vector3 +normals and optional @ref VertexFormat::Vector2 texture coordinates. If texture +coordinates are generated, vertices of one segment are duplicated for texture +wrapping. @image html primitives-capsule3dsolid.png width=256px The capsule is by default created with radius set to @f$ 1.0 @f$. In order to get radius @f$ r @f$, length @f$ l @f$ and preserve correct normals, set -@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all -@ref Trade::MeshData3D::positions() by @f$ r @f$, for example using -@ref MeshTools::transformPointsInPlace(). +@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all positions by +@f$ r @f$, for example using @ref MeshTools::transformPointsInPlace(). @see @ref capsule3DWireframe(), @ref capsule2DWireframe(), @ref cylinderSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D capsule3DSolid(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float halfLength, CapsuleTextureCoords textureCoords = CapsuleTextureCoords::DontGenerate); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData capsule3DSolid(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float halfLength, CapsuleTextureCoords textureCoords = CapsuleTextureCoords::DontGenerate); /** @brief Wireframe 3D capsule @@ -100,13 +102,14 @@ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D capsule3DSolid(UnsignedInt hemisphere @param halfLength Half the length of cylinder part Cylinder of radius @cpp 1.0f @ce along Y axis with hemispheres instead of caps. -Indexed @ref MeshPrimitive::Lines. +@ref MeshPrimitive::Lines with @ref MeshIndexType::UnsignedInt indices and +@ref VertexFormat::Vector3 positions. @image html primitives-capsule3dwireframe.png width=256px @see @ref capsule2DWireframe(), @ref capsule3DSolid(), @ref cylinderSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D capsule3DWireframe(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float halfLength); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData capsule3DWireframe(UnsignedInt hemisphereRings, UnsignedInt cylinderRings, UnsignedInt segments, Float halfLength); }} diff --git a/src/Magnum/Primitives/Circle.cpp b/src/Magnum/Primitives/Circle.cpp index d38df8ded9..61ddb46657 100644 --- a/src/Magnum/Primitives/Circle.cpp +++ b/src/Magnum/Primitives/Circle.cpp @@ -28,112 +28,140 @@ #include "Magnum/Math/Functions.h" #include "Magnum/Math/Color.h" #include "Magnum/Mesh.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData2D circle2DSolid(const UnsignedInt segments, CircleTextureCoords textureCoords) { +Trade::MeshData circle2DSolid(const UnsignedInt segments, const CircleTextureCoords textureCoords) { CORRADE_ASSERT(segments >= 3, "Primitives::circle2DSolid(): segments must be >= 3", - (Trade::MeshData2D{MeshPrimitive::TriangleFan, {}, {}, {}, {}, nullptr})); - - std::vector positions; - positions.reserve(segments + 2); - - std::vector> textureCoordinates; - if(textureCoords == CircleTextureCoords::Generate) - textureCoordinates.emplace_back(); - - /* Central point */ - positions.emplace_back(); - if(textureCoords == CircleTextureCoords::Generate) - textureCoordinates.front().emplace_back(0.5f, 0.5f); - - /* Points on circle. The first/last point is here twice to close the circle - properly. */ + (Trade::MeshData{MeshPrimitive::TriangleFan, 0})); + + /* Allocate interleaved array for all vertex data */ + std::size_t stride = sizeof(Vector2); + std::size_t attributeCount = 1; + if(textureCoords == CircleTextureCoords::Generate) { + ++attributeCount; + stride += sizeof(Vector2); + } + Containers::Array vertexData{stride*(segments + 2)}; + Containers::Array attributes{attributeCount}; + + /* Fill positions */ + Containers::StridedArrayView1D positions{vertexData, + reinterpret_cast(vertexData.begin()), + segments + 2, std::ptrdiff_t(stride)}; + attributes[0] = + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}; + positions[0] = {}; + /* Points on the circle. The first/last point is here twice to close the + circle properly. */ const Rad angleIncrement(Constants::tau()/segments); for(UnsignedInt i = 0; i != segments + 1; ++i) { const Rad angle(Float(i)*angleIncrement); const std::pair sincos = Math::sincos(angle); - Vector2 position{sincos.second, sincos.first}; - positions.emplace_back(position); + positions[i + 1] = {sincos.second, sincos.first}; + } - if(textureCoords == CircleTextureCoords::Generate) - textureCoordinates.front().emplace_back(position*0.5f + Vector2{0.5f}); + /* Fill texture coords, if any */ + if(textureCoords == CircleTextureCoords::Generate) { + Containers::StridedArrayView1D textureCoords{vertexData, + reinterpret_cast(vertexData.begin() + sizeof(Vector2)), + positions.size(), std::ptrdiff_t(stride)}; + attributes[1] = + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, textureCoords}; + for(std::size_t i = 0; i != positions.size(); ++i) + textureCoords[i] = positions[i]*0.5f + Vector2{0.5f}; } - return Trade::MeshData2D{MeshPrimitive::TriangleFan, {}, {std::move(positions)}, std::move(textureCoordinates), {}, nullptr}; + return Trade::MeshData{MeshPrimitive::TriangleFan, std::move(vertexData), std::move(attributes)}; } -Trade::MeshData2D circle2DWireframe(const UnsignedInt segments) { +Trade::MeshData circle2DWireframe(const UnsignedInt segments) { CORRADE_ASSERT(segments >= 3, "Primitives::circle2DWireframe(): segments must be >= 3", - (Trade::MeshData2D{MeshPrimitive::LineLoop, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::LineLoop, 0})); - std::vector positions; - positions.reserve(segments); + Containers::Array vertexData{segments*sizeof(Vector2)}; + auto positions = Containers::arrayCast(vertexData); /* Points on circle */ const Rad angleIncrement(Constants::tau()/segments); for(UnsignedInt i = 0; i != segments; ++i) { const Rad angle(Float(i)*angleIncrement); const std::pair sincos = Math::sincos(angle); - positions.emplace_back(sincos.second, sincos.first); + positions[i] = {sincos.second, sincos.first}; } - return Trade::MeshData2D{MeshPrimitive::LineLoop, {}, {std::move(positions)}, {}, {}, nullptr}; + return Trade::MeshData{MeshPrimitive::LineLoop, std::move(vertexData), {Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}}}; } -Trade::MeshData3D circle3DSolid(const UnsignedInt segments, CircleTextureCoords textureCoords) { +Trade::MeshData circle3DSolid(const UnsignedInt segments, CircleTextureCoords textureCoords) { CORRADE_ASSERT(segments >= 3, "Primitives::circle3DSolid(): segments must be >= 3", - (Trade::MeshData3D{MeshPrimitive::TriangleFan, {}, {}, {}, {}, {}, nullptr})); - - std::vector positions; - positions.reserve(segments + 2); - - std::vector> textureCoordinates; - if(textureCoords == CircleTextureCoords::Generate) - textureCoordinates.emplace_back(); - - /* Central point */ - positions.emplace_back(); - if(textureCoords == CircleTextureCoords::Generate) - textureCoordinates.front().emplace_back(0.5f, 0.5f); - - /* Points on circle. The first/last point is here twice to close the circle - properly. */ + (Trade::MeshData{MeshPrimitive::TriangleFan, 0})); + + /* Allocate interleaved array for all vertex data */ + std::size_t stride = 2*sizeof(Vector3); + std::size_t attributeCount = 2; + if(textureCoords == CircleTextureCoords::Generate) { + ++attributeCount; + stride += sizeof(Vector2); + } + Containers::Array vertexData{stride*(segments + 2)}; + Containers::Array attributes{attributeCount}; + + /* Fill positions */ + Containers::StridedArrayView1D positions{vertexData, + reinterpret_cast(vertexData.begin()), + segments + 2, std::ptrdiff_t(stride)}; + attributes[0] = + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}; + positions[0] = {}; + /* Points on the circle. The first/last point is here twice to close the + circle properly. */ const Rad angleIncrement(Constants::tau()/segments); for(UnsignedInt i = 0; i != segments + 1; ++i) { const Rad angle(Float(i)*angleIncrement); const std::pair sincos = Math::sincos(angle); - Vector3 position{sincos.second, sincos.first, 0.0f}; - positions.emplace_back(position); - - if(textureCoords == CircleTextureCoords::Generate) - textureCoordinates.front().emplace_back(position.xy()*0.5f + Vector2{0.5f}); + positions[i + 1] = {sincos.second, sincos.first, 0.0f}; } - /* Normals. All pointing in the same direction. */ - std::vector normals{segments + 2, Vector3::zAxis(1.0f)}; + /* Fill normals */ + Containers::StridedArrayView1D normals{vertexData, + reinterpret_cast(vertexData.begin() + sizeof(Vector3)), + segments + 2, std::ptrdiff_t(stride)}; + attributes[1] = + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normals}; + for(Vector3& normal: normals) normal = Vector3::zAxis(1.0f); + + /* Fill texture coords, if any */ + if(textureCoords == CircleTextureCoords::Generate) { + Containers::StridedArrayView1D textureCoords{vertexData, + reinterpret_cast(vertexData.begin() + 2*sizeof(Vector3)), + positions.size(), std::ptrdiff_t(stride)}; + attributes[2] = + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, textureCoords}; + for(std::size_t i = 0; i != positions.size(); ++i) + textureCoords[i] = positions[i].xy()*0.5f + Vector2{0.5f}; + } - return Trade::MeshData3D{MeshPrimitive::TriangleFan, {}, {std::move(positions)}, {std::move(normals)}, std::move(textureCoordinates), {}, nullptr}; + return Trade::MeshData{MeshPrimitive::TriangleFan, std::move(vertexData), std::move(attributes)}; } -Trade::MeshData3D circle3DWireframe(const UnsignedInt segments) { +Trade::MeshData circle3DWireframe(const UnsignedInt segments) { CORRADE_ASSERT(segments >= 3, "Primitives::circle3DWireframe(): segments must be >= 3", - (Trade::MeshData3D{MeshPrimitive::LineLoop, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::LineLoop, 0})); - std::vector positions; - positions.reserve(segments); + Containers::Array vertexData{segments*sizeof(Vector3)}; + auto positions = Containers::arrayCast(vertexData); /* Points on circle */ const Rad angleIncrement(Constants::tau()/segments); for(UnsignedInt i = 0; i != segments; ++i) { const Rad angle(Float(i)*angleIncrement); const std::pair sincos = Math::sincos(angle); - positions.emplace_back(sincos.second, sincos.first, 0.0f); + positions[i] = {sincos.second, sincos.first, 0.0f}; } - return Trade::MeshData3D{MeshPrimitive::LineLoop, {}, {std::move(positions)}, {}, {}, {}, nullptr}; + return Trade::MeshData{MeshPrimitive::LineLoop, std::move(vertexData), {Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}}}; } }} diff --git a/src/Magnum/Primitives/Circle.h b/src/Magnum/Primitives/Circle.h index e8fcd8b3d0..6a0eb90f7a 100644 --- a/src/Magnum/Primitives/Circle.h +++ b/src/Magnum/Primitives/Circle.h @@ -51,26 +51,29 @@ enum class CircleTextureCoords: UnsignedByte { @cpp 3 @ce. @param textureCoords Whether to generate texture coordinates -Circle with radius @cpp 1.0f @ce. Non-indexed @ref MeshPrimitive::TriangleFan. +Circle with radius @cpp 1.0f @ce. @ref MeshPrimitive::TriangleFan with +@ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector2 +positions and optional @ref VertexFormat::Vector2 texture coordinates. @image html primitives-circle2dsolid.png width=256px @see @ref circle2DWireframe(), @ref circle3DSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D circle2DSolid(UnsignedInt segments, CircleTextureCoords textureCoords = CircleTextureCoords::DontGenerate); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData circle2DSolid(UnsignedInt segments, CircleTextureCoords textureCoords = CircleTextureCoords::DontGenerate); /** @brief Wireframe 2D circle @param segments Number of segments. Must be greater or equal to @cpp 3 @ce. -Circle with radius @cpp 1.0f @ce. Non-indexed @ref MeshPrimitive::LineLoop. +Circle with radius @cpp 1.0f @ce. Non-indexed @ref MeshPrimitive::LineLoop with +@ref VertexFormat::Vector2 positions. @image html primitives-circle2dwireframe.png width=256px @see @ref circle2DSolid(), @ref circle3DWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D circle2DWireframe(UnsignedInt segments); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData circle2DWireframe(UnsignedInt segments); /** @brief Solid 3D circle @@ -79,26 +82,27 @@ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D circle2DWireframe(UnsignedInt segment @param textureCoords Whether to generate texture coordinates Circle on the XY plane with radius @cpp 1.0f @ce. Non-indexed -@ref MeshPrimitive::TriangleFan with normals in positive Z direction. +@ref MeshPrimitive::TriangleFan with interleaved @ref VertexFormat::Vector3 +positions, @ref VertexFormat::Vector3 normals in positive Z direction and +optional @ref VertexFormat::Vector2 texture coordinates. @image html primitives-circle3dsolid.png width=256px @see @ref circle3DWireframe(), @ref circle2DSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D circle3DSolid(UnsignedInt segments, CircleTextureCoords textureCoords = CircleTextureCoords::DontGenerate); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData circle3DSolid(UnsignedInt segments, CircleTextureCoords textureCoords = CircleTextureCoords::DontGenerate); /** @brief Wireframe 3D circle @param segments Number of segments. Must be greater or equal to @cpp 3 @ce. -Circle on the XY plane with radius @cpp 1.0f @ce. Non-indexed -@ref MeshPrimitive::LineLoop. +Circle on the XY plane with radius @cpp 1.0f @ce. Non-indexed @ref MeshPrimitive::LineLoop with @ref VertexFormat::Vector2 positions. @image html primitives-circle3dwireframe.png width=256px @see @ref circle2DSolid(), @ref circle3DWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D circle3DWireframe(UnsignedInt segments); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData circle3DWireframe(UnsignedInt segments); }} diff --git a/src/Magnum/Primitives/Cone.cpp b/src/Magnum/Primitives/Cone.cpp index 856b6c9769..af573eec66 100644 --- a/src/Magnum/Primitives/Cone.cpp +++ b/src/Magnum/Primitives/Cone.cpp @@ -29,14 +29,14 @@ #include "Magnum/Math/Color.h" #include "Magnum/Primitives/Implementation/Spheroid.h" #include "Magnum/Primitives/Implementation/WireframeSpheroid.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D coneSolid(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength, const ConeFlags flags) { +Trade::MeshData coneSolid(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength, const ConeFlags flags) { CORRADE_ASSERT(rings >= 1 && segments >= 3, "Primitives::coneSolid(): at least one ring and three segments expected", - (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); Implementation::Spheroid cone{segments, flags & ConeFlag::GenerateTextureCoords ? Implementation::Spheroid::TextureCoords::Generate : Implementation::Spheroid::TextureCoords::DontGenerate}; @@ -64,10 +64,10 @@ Trade::MeshData3D coneSolid(const UnsignedInt rings, const UnsignedInt segments, return cone.finalize(); } -Trade::MeshData3D coneWireframe(const UnsignedInt segments, const Float halfLength) { +Trade::MeshData coneWireframe(const UnsignedInt segments, const Float halfLength) { CORRADE_ASSERT(segments >= 4 && segments%4 == 0, "Primitives::coneWireframe(): multiples of 4 segments expected", - (Trade::MeshData3D{MeshPrimitive::Lines, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Lines, 0})); Implementation::WireframeSpheroid cone{segments/4}; cone.ring(-halfLength); diff --git a/src/Magnum/Primitives/Cone.h b/src/Magnum/Primitives/Cone.h index dcaab335bc..9c147b8b9b 100644 --- a/src/Magnum/Primitives/Cone.h +++ b/src/Magnum/Primitives/Cone.h @@ -65,21 +65,22 @@ CORRADE_ENUMSET_OPERATORS(ConeFlags) @param halfLength Half the cone length @param flags Flags -Cone along Y axis of radius @cpp 1.0f @ce. Indexed -@ref MeshPrimitive::Triangles. Note that in order to have properly smooth -normals over the whole area, the tip consists of @cpp segments*2 @ce vertices -instead of just one. +Cone along Y axis of radius @cpp 1.0f @ce. @ref MeshPrimitive::Triangles with +@ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 +positions, @ref VertexFormat::Vector3 normals and optional +@ref VertexFormat::Vector2 texture coordinates. Note that in order to have +properly smooth normals over the whole area, the tip consists of +@cpp segments*2 @ce vertices instead of just one. @image html primitives-conesolid.png width=256px The cone is by default created with radius set to @f$ 1.0 @f$. In order to get radius @f$ r @f$, length @f$ l @f$ and preserve correct normals, set -@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all -@ref Trade::MeshData3D::positions() by @f$ r @f$, for example using -@ref MeshTools::transformPointsInPlace(). +@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all positions by +@f$ r @f$, for example using @ref MeshTools::transformPointsInPlace(). @see @ref coneWireframe(), @ref cylinderSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D coneSolid(UnsignedInt rings, UnsignedInt segments, Float halfLength, ConeFlags flags = {}); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData coneSolid(UnsignedInt rings, UnsignedInt segments, Float halfLength, ConeFlags flags = {}); /** @brief Wireframe 3D cone @@ -87,13 +88,15 @@ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D coneSolid(UnsignedInt rings, Unsigned @cpp 4 @ce and multiple of @cpp 4 @ce. @param halfLength Half the cone length -Cone along Y axis of radius @cpp 1.0f @ce. Indexed @ref MeshPrimitive::Lines. +Cone along Y axis of radius @cpp 1.0f @ce. @ref MeshPrimitive::Lines with +@ref MeshIndexType::UnsignedInt indices and @ref VertexFormat::Vector3 +positions. @image html primitives-conewireframe.png width=256px @see @ref coneSolid(), @ref cylinderWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D coneWireframe(UnsignedInt segments, Float halfLength); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData coneWireframe(UnsignedInt segments, Float halfLength); }} diff --git a/src/Magnum/Primitives/Crosshair.cpp b/src/Magnum/Primitives/Crosshair.cpp index eed1fefe1e..0b1b307515 100644 --- a/src/Magnum/Primitives/Crosshair.cpp +++ b/src/Magnum/Primitives/Crosshair.cpp @@ -27,24 +27,39 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData2D crosshair2D() { - return Trade::MeshData2D{MeshPrimitive::Lines, {}, {{ - {-1.0f, 0.0f}, {1.0f, 0.0f}, - { 0.0f, -1.0f}, {0.0f, 1.0f} - }}, {}, {}, nullptr}; +namespace { + +constexpr Vector2 Positions2D[]{ + {-1.0f, 0.0f}, {1.0f, 0.0f}, + { 0.0f, -1.0f}, {0.0f, 1.0f} +}; +constexpr Vector3 Positions3D[]{ + {-1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, + { 0.0f, -1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, + { 0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f} +}; + +constexpr Trade::MeshAttributeData Attributes2D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(Positions2D)} +}; +constexpr Trade::MeshAttributeData Attributes3D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(Positions3D)} +}; + +} + +Trade::MeshData crosshair2D() { + return Trade::MeshData{MeshPrimitive::Lines, {}, Positions2D, + Trade::meshAttributeDataNonOwningArray(Attributes2D)}; } -Trade::MeshData3D crosshair3D() { - return Trade::MeshData3D{MeshPrimitive::Lines, {}, {{ - {-1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, - { 0.0f, -1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, - { 0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f} - }}, {}, {}, {}, nullptr}; +Trade::MeshData crosshair3D() { + return Trade::MeshData{MeshPrimitive::Lines, {}, Positions3D, + Trade::meshAttributeDataNonOwningArray(Attributes3D)}; } }} diff --git a/src/Magnum/Primitives/Crosshair.h b/src/Magnum/Primitives/Crosshair.h index 72151394d2..873dda2eb8 100644 --- a/src/Magnum/Primitives/Crosshair.h +++ b/src/Magnum/Primitives/Crosshair.h @@ -37,24 +37,28 @@ namespace Magnum { namespace Primitives { /** @brief 2D crosshair -2x2 crosshair (two crossed lines), non-indexed @ref MeshPrimitive::Lines. +2x2 crosshair (two crossed lines). Non-indexed @ref MeshPrimitive::Lines with +@ref VertexFormat::Vector2 positions. The returned instance references data +stored in constant memory. @image html primitives-crosshair2d.png width=256px @see @ref crosshair3D(), @ref axis2D(), @ref line2D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D crosshair2D(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData crosshair2D(); /** @brief 3D crosshair -2x2x2 crosshair (three crossed lines), non-indexed @ref MeshPrimitive::Lines. +2x2x2 crosshair (three crossed lines). Non-indexed @ref MeshPrimitive::Lines +with @ref VertexFormat::Vector3 positions. The returned instance references +data stored in constant memory. @image html primitives-crosshair3d.png width=256px @see @ref crosshair2D(), @ref axis2D(), @ref line3D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D crosshair3D(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData crosshair3D(); }} diff --git a/src/Magnum/Primitives/Cube.cpp b/src/Magnum/Primitives/Cube.cpp index b5d8c4716d..1929ee70c4 100644 --- a/src/Magnum/Primitives/Cube.cpp +++ b/src/Magnum/Primitives/Cube.cpp @@ -27,82 +27,75 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D cubeSolid() { - return Trade::MeshData3D{MeshPrimitive::Triangles, { - 0, 1, 2, 0, 2, 3, /* +Z */ - 4, 5, 6, 4, 6, 7, /* +X */ - 8, 9, 10, 8, 10, 11, /* +Y */ - 12, 13, 14, 12, 14, 15, /* -Z */ - 16, 17, 18, 16, 18, 19, /* -Y */ - 20, 21, 22, 20, 22, 23 /* -X */ - }, {{ - {-1.0f, -1.0f, 1.0f}, - { 1.0f, -1.0f, 1.0f}, - { 1.0f, 1.0f, 1.0f}, /* +Z */ - {-1.0f, 1.0f, 1.0f}, - - { 1.0f, -1.0f, 1.0f}, - { 1.0f, -1.0f, -1.0f}, - { 1.0f, 1.0f, -1.0f}, /* +X */ - { 1.0f, 1.0f, 1.0f}, - - {-1.0f, 1.0f, 1.0f}, - { 1.0f, 1.0f, 1.0f}, - { 1.0f, 1.0f, -1.0f}, /* +Y */ - {-1.0f, 1.0f, -1.0f}, - - { 1.0f, -1.0f, -1.0f}, - {-1.0f, -1.0f, -1.0f}, - {-1.0f, 1.0f, -1.0f}, /* -Z */ - { 1.0f, 1.0f, -1.0f}, - - {-1.0f, -1.0f, -1.0f}, - { 1.0f, -1.0f, -1.0f}, - { 1.0f, -1.0f, 1.0f}, /* -Y */ - {-1.0f, -1.0f, 1.0f}, - - {-1.0f, -1.0f, -1.0f}, - {-1.0f, -1.0f, 1.0f}, - {-1.0f, 1.0f, 1.0f}, /* -X */ - {-1.0f, 1.0f, -1.0f} - }}, {{ - { 0.0f, 0.0f, 1.0f}, - { 0.0f, 0.0f, 1.0f}, - { 0.0f, 0.0f, 1.0f}, /* +Z */ - { 0.0f, 0.0f, 1.0f}, - - { 1.0f, 0.0f, 0.0f}, - { 1.0f, 0.0f, 0.0f}, - { 1.0f, 0.0f, 0.0f}, /* +X */ - { 1.0f, 0.0f, 0.0f}, - - { 0.0f, 1.0f, 0.0f}, - { 0.0f, 1.0f, 0.0f}, - { 0.0f, 1.0f, 0.0f}, /* +Y */ - { 0.0f, 1.0f, 0.0f}, - - { 0.0f, 0.0f, -1.0f}, - { 0.0f, 0.0f, -1.0f}, - { 0.0f, 0.0f, -1.0f}, /* -Z */ - { 0.0f, 0.0f, -1.0f}, - - { 0.0f, -1.0f, 0.0f}, - { 0.0f, -1.0f, 0.0f}, - { 0.0f, -1.0f, 0.0f}, /* -Y */ - { 0.0f, -1.0f, 0.0f}, - - {-1.0f, 0.0f, 0.0f}, - {-1.0f, 0.0f, 0.0f}, - {-1.0f, 0.0f, 0.0f}, /* -X */ - {-1.0f, 0.0f, 0.0f} - }}, {}, {}, nullptr}; +namespace { + +/* not 8-bit because GPUs (and Vulkan) don't like it nowadays */ +constexpr UnsignedShort IndicesSolid[]{ + 0, 1, 2, 0, 2, 3, /* +Z */ + 4, 5, 6, 4, 6, 7, /* +X */ + 8, 9, 10, 8, 10, 11, /* +Y */ + 12, 13, 14, 12, 14, 15, /* -Z */ + 16, 17, 18, 16, 18, 19, /* -Y */ + 20, 21, 22, 20, 22, 23 /* -X */ +}; +constexpr struct VertexSolid { + Vector3 position; + Vector3 normal; +} VerticesSolid[]{ + {{-1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, + {{ 1.0f, -1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, /* +Z */ + {{-1.0f, 1.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}}, + + {{ 1.0f, -1.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}, + {{ 1.0f, -1.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}}, + {{ 1.0f, 1.0f, -1.0f}, { 1.0f, 0.0f, 0.0f}}, /* +X */ + {{ 1.0f, 1.0f, 1.0f}, { 1.0f, 0.0f, 0.0f}}, + + {{-1.0f, 1.0f, 1.0f}, { 0.0f, 1.0f, 0.0f}}, + {{ 1.0f, 1.0f, 1.0f}, { 0.0f, 1.0f, 0.0f}}, + {{ 1.0f, 1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}, /* +Y */ + {{-1.0f, 1.0f, -1.0f}, { 0.0f, 1.0f, 0.0f}}, + + {{ 1.0f, -1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, + {{-1.0f, -1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, + {{-1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, /* -Z */ + {{ 1.0f, 1.0f, -1.0f}, { 0.0f, 0.0f, -1.0f}}, + + {{-1.0f, -1.0f, -1.0f}, { 0.0f, -1.0f, 0.0f}}, + {{ 1.0f, -1.0f, -1.0f}, { 0.0f, -1.0f, 0.0f}}, + {{ 1.0f, -1.0f, 1.0f}, { 0.0f, -1.0f, 0.0f}}, /* -Y */ + {{-1.0f, -1.0f, 1.0f}, { 0.0f, -1.0f, 0.0f}}, + + {{-1.0f, -1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}}, + {{-1.0f, -1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, + {{-1.0f, 1.0f, 1.0f}, {-1.0f, 0.0f, 0.0f}}, /* -X */ + {{-1.0f, 1.0f, -1.0f}, {-1.0f, 0.0f, 0.0f}} +}; +constexpr Trade::MeshAttributeData AttributesSolid[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesSolid, &VerticesSolid[0].position, + Containers::arraySize(VerticesSolid), sizeof(VertexSolid))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + Containers::stridedArrayView(VerticesSolid, &VerticesSolid[0].normal, + Containers::arraySize(VerticesSolid), sizeof(VertexSolid))} +}; + +} + +Trade::MeshData cubeSolid() { + return Trade::MeshData{MeshPrimitive::Triangles, + {}, IndicesSolid, Trade::MeshIndexData{IndicesSolid}, + {}, VerticesSolid, Trade::meshAttributeDataNonOwningArray(AttributesSolid)}; } -Trade::MeshData3D cubeSolidStrip() { +namespace { + +constexpr Vector3 VerticesSolidStrip[]{ /* Sources: https://twitter.com/Donzanoid/status/436843034966507520 http://www.asmcommunity.net/forums/topic/?id=6284#post-45209 @@ -126,41 +119,64 @@ Trade::MeshData3D cubeSolidStrip() { |F \| 2---3 */ - return Trade::MeshData3D{MeshPrimitive::TriangleStrip, {}, {{ - { 1.0f, 1.0f, 1.0f}, /* 3 */ - {-1.0f, 1.0f, 1.0f}, /* 2 */ - { 1.0f, -1.0f, 1.0f}, /* 6 */ - {-1.0f, -1.0f, 1.0f}, /* 7 */ - {-1.0f, -1.0f, -1.0f}, /* 4 */ - {-1.0f, 1.0f, 1.0f}, /* 2 */ - {-1.0f, 1.0f, -1.0f}, /* 0 */ - { 1.0f, 1.0f, 1.0f}, /* 3 */ - { 1.0f, 1.0f, -1.0f}, /* 1 */ - { 1.0f, -1.0f, 1.0f}, /* 6 */ - { 1.0f, -1.0f, -1.0f}, /* 5 */ - {-1.0f, -1.0f, -1.0f}, /* 4 */ - { 1.0f, 1.0f, -1.0f}, /* 1 */ - {-1.0f, 1.0f, -1.0f} /* 0 */ - }}, {}, {}, {}, nullptr}; + { 1.0f, 1.0f, 1.0f}, /* 3 */ + {-1.0f, 1.0f, 1.0f}, /* 2 */ + { 1.0f, -1.0f, 1.0f}, /* 6 */ + {-1.0f, -1.0f, 1.0f}, /* 7 */ + {-1.0f, -1.0f, -1.0f}, /* 4 */ + {-1.0f, 1.0f, 1.0f}, /* 2 */ + {-1.0f, 1.0f, -1.0f}, /* 0 */ + { 1.0f, 1.0f, 1.0f}, /* 3 */ + { 1.0f, 1.0f, -1.0f}, /* 1 */ + { 1.0f, -1.0f, 1.0f}, /* 6 */ + { 1.0f, -1.0f, -1.0f}, /* 5 */ + {-1.0f, -1.0f, -1.0f}, /* 4 */ + { 1.0f, 1.0f, -1.0f}, /* 1 */ + {-1.0f, 1.0f, -1.0f} /* 0 */ +}; +constexpr Trade::MeshAttributeData AttributesSolidStrip[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesSolidStrip)} +}; + +} + +Trade::MeshData cubeSolidStrip() { + return Trade::MeshData{MeshPrimitive::TriangleStrip, + {}, VerticesSolidStrip, Trade::meshAttributeDataNonOwningArray(AttributesSolidStrip)}; +} + +namespace { + +/* not 8-bit because GPUs (and Vulkan) don't like it nowadays */ +constexpr UnsignedShort IndicesWireframe[]{ + 0, 1, 1, 2, 2, 3, 3, 0, /* +Z */ + 4, 5, 5, 6, 6, 7, 7, 4, /* -Z */ + 1, 5, 2, 6, /* +X */ + 0, 4, 3, 7 /* -X */ +}; +constexpr Vector3 VerticesWireframe[]{ + {-1.0f, -1.0f, 1.0f}, + { 1.0f, -1.0f, 1.0f}, + { 1.0f, 1.0f, 1.0f}, + {-1.0f, 1.0f, 1.0f}, + + {-1.0f, -1.0f, -1.0f}, + { 1.0f, -1.0f, -1.0f}, + { 1.0f, 1.0f, -1.0f}, + {-1.0f, 1.0f, -1.0f} +}; +constexpr Trade::MeshAttributeData AttributesWireframe[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesWireframe)} +}; + } -Trade::MeshData3D cubeWireframe() { - return Trade::MeshData3D{MeshPrimitive::Lines, { - 0, 1, 1, 2, 2, 3, 3, 0, /* +Z */ - 4, 5, 5, 6, 6, 7, 7, 4, /* -Z */ - 1, 5, 2, 6, /* +X */ - 0, 4, 3, 7 /* -X */ - }, {{ - {-1.0f, -1.0f, 1.0f}, - { 1.0f, -1.0f, 1.0f}, - { 1.0f, 1.0f, 1.0f}, - {-1.0f, 1.0f, 1.0f}, - - {-1.0f, -1.0f, -1.0f}, - { 1.0f, -1.0f, -1.0f}, - { 1.0f, 1.0f, -1.0f}, - {-1.0f, 1.0f, -1.0f} - }}, {}, {}, {}, nullptr}; +Trade::MeshData cubeWireframe() { + return Trade::MeshData{MeshPrimitive::Lines, + {}, IndicesWireframe, Trade::MeshIndexData{IndicesWireframe}, + {}, VerticesWireframe, Trade::meshAttributeDataNonOwningArray(AttributesWireframe)}; } }} diff --git a/src/Magnum/Primitives/Cube.h b/src/Magnum/Primitives/Cube.h index 4e31f7f69b..14e42e266a 100644 --- a/src/Magnum/Primitives/Cube.h +++ b/src/Magnum/Primitives/Cube.h @@ -37,36 +37,42 @@ namespace Magnum { namespace Primitives { /** @brief Solid 3D cube -Indexed @ref MeshPrimitive::Triangles with flat normals. +@ref MeshPrimitive::Triangles with @ref MeshIndexType::UnsignedShort indices, +interleaved @ref VertexFormat::Vector3 positions and flat +@ref VertexFormat::Vector3 normals. The returned instance references data +stored in constant memory. @image html primitives-cubesolid.png width=256px @see @ref cubeSolidStrip(), @ref cubeWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cubeSolid(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData cubeSolid(); /** @brief Solid 3D cube as a single strip -Non-indexed @ref MeshPrimitive::TriangleStrip. Just positions, no -normals or anything else. +Non-indexed @ref MeshPrimitive::TriangleStrip with @ref VertexFormat::Vector3 +positions. The returned instance references data stored in constant memory. No +normals or anything else --- use @ref cubeSolid() instead if you need these. @image html primitives-cubesolid.png width=256px -@see @ref cubeSolid(), @ref cubeWireframe() +@see @ref cubeWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cubeSolidStrip(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData cubeSolidStrip(); /** @brief Wireframe 3D cube -Indexed @ref MeshPrimitive::Lines. +@ref MeshPrimitive::Lines with @ref MeshIndexType::UnsignedShort indices and +@ref VertexFormat::Vector3 positions. The returned instance references data +stored in constant memory. @image html primitives-cubewireframe.png width=256px @see @ref cubeSolid(), @ref cubeSolidStrip() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cubeWireframe(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData cubeWireframe(); }} diff --git a/src/Magnum/Primitives/Cylinder.cpp b/src/Magnum/Primitives/Cylinder.cpp index 640f91f48b..060c1bce48 100644 --- a/src/Magnum/Primitives/Cylinder.cpp +++ b/src/Magnum/Primitives/Cylinder.cpp @@ -29,14 +29,14 @@ #include "Magnum/Math/Color.h" #include "Magnum/Primitives/Implementation/Spheroid.h" #include "Magnum/Primitives/Implementation/WireframeSpheroid.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D cylinderSolid(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength, const CylinderFlags flags) { +Trade::MeshData cylinderSolid(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength, const CylinderFlags flags) { CORRADE_ASSERT(rings >= 1 && segments >= 3, "Primitives::cylinderSolid(): at least one ring and three segments expected", - (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); Implementation::Spheroid cylinder(segments, flags & CylinderFlag::GenerateTextureCoords ? Implementation::Spheroid::TextureCoords::Generate : Implementation::Spheroid::TextureCoords::DontGenerate); @@ -71,10 +71,10 @@ Trade::MeshData3D cylinderSolid(const UnsignedInt rings, const UnsignedInt segme return cylinder.finalize(); } -Trade::MeshData3D cylinderWireframe(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength) { +Trade::MeshData cylinderWireframe(const UnsignedInt rings, const UnsignedInt segments, const Float halfLength) { CORRADE_ASSERT(rings >= 1 && segments >= 4 && segments%4 == 0, "Primitives::cylinderWireframe(): at least one ring and multiples of 4 segments expected", - (Trade::MeshData3D{MeshPrimitive::Lines, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Lines, 0})); Implementation::WireframeSpheroid cylinder(segments/4); diff --git a/src/Magnum/Primitives/Cylinder.h b/src/Magnum/Primitives/Cylinder.h index 70a408028c..0a57b95ba8 100644 --- a/src/Magnum/Primitives/Cylinder.h +++ b/src/Magnum/Primitives/Cylinder.h @@ -65,21 +65,22 @@ CORRADE_ENUMSET_OPERATORS(CylinderFlags) @param halfLength Half the cylinder length @param flags Flags -Cylinder along Y axis of radius @cpp 1.0f @ce. Indexed -@ref MeshPrimitive::Triangles with normals, optional 2D texture coordinates and -optional capped ends. If texture coordinates are generated, vertices of one -segment are duplicated for texture wrapping. +Cylinder along Y axis of radius @cpp 1.0f @ce. @ref MeshPrimitive::Triangles +with @ref MeshIndexType::UnsignedInt indices, interleaved +@ref VertexFormat::Vector3 positions, @ref VertexFormat::Vector3 normals, +optional @ref VertexFormat::Vector2 texture coordinates and optional capped +ends. If texture coordinates are generated, vertices of one segment are +duplicated for texture wrapping. @image html primitives-cylindersolid.png width=256px The cylinder is by default created with radius set to @f$ 1.0 @f$. In order to get radius @f$ r @f$, length @f$ l @f$ and preserve correct normals, set -@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all -@ref Trade::MeshData3D::positions() by @f$ r @f$, for example using -@ref MeshTools::transformPointsInPlace(). +@p halfLength to @f$ 0.5 \frac{l}{r} @f$ and then scale all positions by +@f$ r @f$, for example using @ref MeshTools::transformPointsInPlace(). @see @ref cylinderWireframe(), @ref coneSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cylinderSolid(UnsignedInt rings, UnsignedInt segments, Float halfLength, CylinderFlags flags = {}); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData cylinderSolid(UnsignedInt rings, UnsignedInt segments, Float halfLength, CylinderFlags flags = {}); /** @brief Wireframe 3D cylinder @@ -89,14 +90,15 @@ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cylinderSolid(UnsignedInt rings, Unsi @cpp 4 @ce and multiple of @cpp 4 @ce. @param halfLength Half the cylinder length -Cylinder along Y axis of radius @cpp 1.0f @ce. Indexed -@ref MeshPrimitive::Lines. +Cylinder along Y axis of radius @cpp 1.0f @ce. @ref MeshPrimitive::Lines with +@ref MeshIndexType::UnsignedInt indices and @ref VertexFormat::Vector3 +positions. @image html primitives-cylinderwireframe.png width=256px @see @ref cylinderSolid(), @ref coneWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D cylinderWireframe(UnsignedInt rings, UnsignedInt segments, Float halfLength); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData cylinderWireframe(UnsignedInt rings, UnsignedInt segments, Float halfLength); }} diff --git a/src/Magnum/Primitives/Gradient.cpp b/src/Magnum/Primitives/Gradient.cpp index 074ef78d78..a4931453bb 100644 --- a/src/Magnum/Primitives/Gradient.cpp +++ b/src/Magnum/Primitives/Gradient.cpp @@ -28,17 +28,22 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" #include "Magnum/Math/Intersection.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData2D gradient2D(const Vector2& a, const Color4& colorA, const Vector2& b, const Color4& colorB) { - std::vector positions{Vector2{ 1.0f, -1.0f}, - Vector2{ 1.0f, 1.0f}, - Vector2{-1.0f, -1.0f}, - Vector2{-1.0f, 1.0f}}; - std::vector colors{4}; +Trade::MeshData gradient2D(const Vector2& a, const Color4& colorA, const Vector2& b, const Color4& colorB) { + struct Vertex { + Vector2 position; + Color4 color; + }; + + Containers::Array vertexData{sizeof(Vertex)*4}; + auto vertices = Containers::arrayCast(vertexData); + vertices[0].position = { 1.0f, -1.0f}; + vertices[1].position = { 1.0f, 1.0f}; + vertices[2].position = {-1.0f, -1.0f}; + vertices[3].position = {-1.0f, 1.0f}; /* For every corner, take a line perpendicular to the gradient direction and passing through the corner. The calculated intersection position @@ -47,27 +52,45 @@ Trade::MeshData2D gradient2D(const Vector2& a, const Color4& colorA, const Vecto const Vector2 direction = b - a; const Vector2 perpendicular = direction.perpendicular(); for(std::size_t i = 0; i != 4; ++i) { - const Float t = Math::Intersection::lineSegmentLine(a, direction, positions[i], perpendicular); - colors[i] = Math::lerp(colorA, colorB, t); + const Float t = Math::Intersection::lineSegmentLine(a, direction, vertices[i].position, perpendicular); + vertices[i].color = Math::lerp(colorA, colorB, t); } - return Trade::MeshData2D{MeshPrimitive::TriangleStrip, {}, {std::move(positions)}, {}, {std::move(colors)}, nullptr}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::stridedArrayView(vertices, &vertices[0].position, + vertices.size(), sizeof(Vertex))}; + Trade::MeshAttributeData colors{Trade::MeshAttribute::Color, + Containers::stridedArrayView(vertices, &vertices[0].color, + vertices.size(), sizeof(Vertex))}; + return Trade::MeshData{MeshPrimitive::TriangleStrip, + std::move(vertexData), {positions, colors}}; } -Trade::MeshData2D gradient2DHorizontal(const Color4& colorLeft, const Color4& colorRight) { +Trade::MeshData gradient2DHorizontal(const Color4& colorLeft, const Color4& colorRight) { return Primitives::gradient2D({-1.0f, 0.0f}, colorLeft, {1.0f, 0.0f}, colorRight); } -Trade::MeshData2D gradient2DVertical(const Color4& colorBottom, const Color4& colorTop) { +Trade::MeshData gradient2DVertical(const Color4& colorBottom, const Color4& colorTop) { return Primitives::gradient2D({0.0f, -1.0f}, colorBottom, {0.0f, 1.0f}, colorTop); } -Trade::MeshData3D gradient3D(const Vector3& a, const Color4& colorA, const Vector3& b, const Color4& colorB) { - std::vector positions{Vector3{ 1.0f, -1.0f, 0.0f}, - Vector3{ 1.0f, 1.0f, 0.0f}, - Vector3{-1.0f, -1.0f, 0.0f}, - Vector3{-1.0f, 1.0f, 0.0f}}; - std::vector colors{4}; +Trade::MeshData gradient3D(const Vector3& a, const Color4& colorA, const Vector3& b, const Color4& colorB) { + struct Vertex { + Vector3 position; + Vector3 normal; + Color4 color; + }; + + Containers::Array vertexData{sizeof(Vertex)*4}; + auto vertices = Containers::arrayCast(vertexData); + vertices[0].position = { 1.0f, -1.0f, 0}; + vertices[1].position = { 1.0f, 1.0f, 0}; + vertices[2].position = {-1.0f, -1.0f, 0}; + vertices[3].position = {-1.0f, 1.0f, 0}; + vertices[0].normal = {0.0f, 0.0f, 1.0f}; + vertices[1].normal = {0.0f, 0.0f, 1.0f}; + vertices[2].normal = {0.0f, 0.0f, 1.0f}; + vertices[3].normal = {0.0f, 0.0f, 1.0f}; /* For every corner, take a plane perpendicular to the gradient direction and passing through the corner. The calculated intersection position @@ -75,24 +98,29 @@ Trade::MeshData3D gradient3D(const Vector3& a, const Color4& colorA, const Vecto for given corner. */ const Vector3 direction = b - a; for(std::size_t i = 0; i != 4; ++i) { - const Vector4 plane = Math::planeEquation(direction, positions[i]); + const Vector4 plane = Math::planeEquation(direction, vertices[i].position); const Float t = Math::Intersection::planeLine(plane, a, direction); - colors[i] = Math::lerp(colorA, colorB, t); + vertices[i].color = Math::lerp(colorA, colorB, t); } - return Trade::MeshData3D{MeshPrimitive::TriangleStrip, {}, {std::move(positions)}, {{ - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f} - }}, {}, {std::move(colors)}, nullptr}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::stridedArrayView(vertices, &vertices[0].position, + vertices.size(), sizeof(Vertex))}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::stridedArrayView(vertices, &vertices[0].normal, + vertices.size(), sizeof(Vertex))}; + Trade::MeshAttributeData colors{Trade::MeshAttribute::Color, + Containers::stridedArrayView(vertices, &vertices[0].color, + vertices.size(), sizeof(Vertex))}; + return Trade::MeshData{MeshPrimitive::TriangleStrip, + std::move(vertexData), {positions, normals, colors}}; } -Trade::MeshData3D gradient3DHorizontal(const Color4& colorLeft, const Color4& colorRight) { +Trade::MeshData gradient3DHorizontal(const Color4& colorLeft, const Color4& colorRight) { return Primitives::gradient3D({-1.0f, 0.0f, 0.0f}, colorLeft, {1.0f, 0.0f, 0.0f}, colorRight); } -Trade::MeshData3D gradient3DVertical(const Color4& colorBottom, const Color4& colorTop) { +Trade::MeshData gradient3DVertical(const Color4& colorBottom, const Color4& colorTop) { return Primitives::gradient3D({0.0f, -1.0f, 0.0f}, colorBottom, {0.0f, 1.0f, 0.0f}, colorTop); } diff --git a/src/Magnum/Primitives/Gradient.h b/src/Magnum/Primitives/Gradient.h index 0a8780ed45..b391603344 100644 --- a/src/Magnum/Primitives/Gradient.h +++ b/src/Magnum/Primitives/Gradient.h @@ -38,16 +38,18 @@ namespace Magnum { namespace Primitives { /** @brief 2D square with a gradient -2x2 square with vertex colors. Non-indexed @ref MeshPrimitive::TriangleStrip. -Vertex colors correspond to the gradient defined by the endpoints @p a and -@p b, linearly interpolated from @p colorA to @p colorB. +2x2 square with vertex colors. Non-indexed @ref MeshPrimitive::TriangleStrip +with interleaved @ref VertexFormat::Vector2 positions and +@ref VertexFormat::Vector4 colors. Vertex colors correspond to the gradient +defined by the endpoints @p a and @p b, linearly interpolated from @p colorA to +@p colorB. @image html primitives-gradient2d.png width=256px @see @ref gradient2DHorizontal(), @ref gradient2DVertical(), @ref gradient3D(), @ref squareSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D gradient2D(const Vector2& a, const Color4& colorA, const Vector2& b, const Color4& colorB); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData gradient2D(const Vector2& a, const Color4& colorA, const Vector2& b, const Color4& colorB); /** @brief 2D square with a horizontal gradient @@ -60,7 +62,7 @@ Equivalent to calling @ref gradient2D() like this: @see @ref gradient2DVertical(), @ref gradient3DHorizontal(), @ref squareSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D gradient2DHorizontal(const Color4& colorLeft, const Color4& colorRight); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData gradient2DHorizontal(const Color4& colorLeft, const Color4& colorRight); /** @brief 2D square with a vertical gradient @@ -73,22 +75,24 @@ Equivalent to calling @ref gradient2D() like this: @see @ref gradient2DHorizontal(), @ref gradient3DVertical(), @ref squareSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D gradient2DVertical(const Color4& colorBottom, const Color4& colorTop); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData gradient2DVertical(const Color4& colorBottom, const Color4& colorTop); /** @brief 3D plane with a gradient 2x2 plane with vertex colors. Non-indexed @ref MeshPrimitive::TriangleStrip on -the XY plane with normals in positive Z direction. Vertex colors correspond to -the gradient defined by the endpoints @p a and @p b, linearly interpolated from -@p colorA to @p colorB. +the XY plane with interleaved @ref VertexFormat::Vector3 positions, +@ref VertexFormat::Vector3 normals in positive Z direction and +@ref VertexFormat::Vector4 colors. Vertex colors correspond to the gradient +defined by the endpoints @p a and @p b, linearly interpolated from @p colorA to +@p colorB. @image html primitives-gradient3d.png width=256px @see @ref gradient3DHorizontal(), @ref gradient3DVertical(), @ref gradient2D(), @ref planeSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D gradient3D(const Vector3& a, const Color4& colorA, const Vector3& b, const Color4& colorB); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData gradient3D(const Vector3& a, const Color4& colorA, const Vector3& b, const Color4& colorB); /** @brief 3D plane with a horizontal gradient @@ -101,7 +105,7 @@ Equivalent to calling @ref gradient3D() like this: @see @ref gradient3DVertical(), @ref gradient2DHorizontal(), @ref planeSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D gradient3DHorizontal(const Color4& colorLeft, const Color4& colorRight); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData gradient3DHorizontal(const Color4& colorLeft, const Color4& colorRight); /** @brief 3D plane with a vertical gradient @@ -114,7 +118,7 @@ Equivalent to calling @ref gradient3D() like this: @see @ref gradient3DHorizontal(), @ref gradient2DVertical(), @ref planeSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D gradient3DVertical(const Color4& colorBottom, const Color4& colorTop); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData gradient3DVertical(const Color4& colorBottom, const Color4& colorTop); }} diff --git a/src/Magnum/Primitives/Grid.cpp b/src/Magnum/Primitives/Grid.cpp index 498d42d0dd..d85262df82 100644 --- a/src/Magnum/Primitives/Grid.cpp +++ b/src/Magnum/Primitives/Grid.cpp @@ -27,82 +27,133 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D grid3DSolid(const Vector2i& subdivisions, const GridFlags flags) { +Trade::MeshData grid3DSolid(const Vector2i& subdivisions, const GridFlags flags) { const Vector2i vertexCount = subdivisions + Vector2i{2}; const Vector2i faceCount = subdivisions + Vector2i{1}; - std::vector positions; - positions.reserve(vertexCount.product()); - for(Int y = 0; y != vertexCount.y(); ++y) - for(Int x = 0; x != vertexCount.x(); ++x) - positions.emplace_back((Vector2(x, y)/Vector2(faceCount))*2.0f - Vector2{1.0f}, 0.0f); - - std::vector indices; - indices.reserve(faceCount.product()*6); - for(Int y = 0; y != faceCount.y(); ++y) { - for(Int x = 0; x != faceCount.x(); ++x) { - /* 2--1 5 - | / /| - |/ / | - 0 3--4 */ - indices.insert(indices.end(), { - UnsignedInt(y*vertexCount.x() + x), - UnsignedInt((y + 1)*vertexCount.x() + x + 1), - UnsignedInt((y + 1)*vertexCount.x() + x + 0), - UnsignedInt(y*vertexCount.x() + x), - UnsignedInt(y*vertexCount.x() + x + 1), - UnsignedInt((y + 1)*vertexCount.x() + x + 1)}); + /* Indices */ + Containers::Array indexData{std::size_t(faceCount.product()*6)*sizeof(UnsignedInt)}; + auto indices = Containers::arrayCast(indexData); + { + std::size_t i = 0; + for(Int y = 0; y != faceCount.y(); ++y) { + for(Int x = 0; x != faceCount.x(); ++x) { + /* 2--1 5 + | / /| + |/ / | + 0 3--4 */ + indices[i++] = UnsignedInt(y*vertexCount.x() + x); + indices[i++] = UnsignedInt((y + 1)*vertexCount.x() + x + 1); + indices[i++] = UnsignedInt((y + 1)*vertexCount.x() + x + 0); + indices[i++] = UnsignedInt(y*vertexCount.x() + x); + indices[i++] = UnsignedInt(y*vertexCount.x() + x + 1); + indices[i++] = UnsignedInt((y + 1)*vertexCount.x() + x + 1); + } } } - std::vector> normals; - if(flags & GridFlag::GenerateNormals) - normals.emplace_back(positions.size(), Vector3::zAxis(1.0f)); + /* Allocate interleaved array for all vertex data */ + std::size_t stride = sizeof(Vector3); + std::size_t attributeCount = 1; + if(flags & GridFlag::GenerateNormals) { + ++attributeCount; + stride += sizeof(Vector3); + } + if(flags & GridFlag::GenerateTextureCoords) { + ++attributeCount; + stride += sizeof(Vector2); + } + Containers::Array vertexData{stride*vertexCount.product()}; + Containers::Array attributes{attributeCount}; + std::size_t attributeIndex = 0; + std::size_t attributeOffset = 0; + + /* Fill positions */ + Containers::StridedArrayView1D positions{vertexData, + reinterpret_cast(vertexData.begin()), + std::size_t(vertexCount.product()), std::ptrdiff_t(stride)}; + attributes[attributeIndex++] = + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}; + attributeOffset += sizeof(Vector3); + { + std::size_t i = 0; + for(Int y = 0; y != vertexCount.y(); ++y) + for(Int x = 0; x != vertexCount.x(); ++x) + positions[i++] = {(Vector2(x, y)/Vector2(faceCount))*2.0f - Vector2{1.0f}, 0.0f}; + } + + /* Fill normals, if any. It's always the second attribute, right after + positions. */ + if(flags & GridFlag::GenerateNormals) { + Containers::StridedArrayView1D normals{vertexData, + reinterpret_cast(vertexData.begin() + attributeOffset), + std::size_t(vertexCount.product()), std::ptrdiff_t(stride)}; + attributes[attributeIndex++] = + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normals}; + attributeOffset += sizeof(Vector3); + for(auto&& i: normals) i = Vector3::zAxis(1.0f); + } - std::vector> textureCoordinates; if(flags & GridFlag::GenerateTextureCoords) { - textureCoordinates.emplace_back(); - textureCoordinates[0].reserve(positions.size()); + Containers::StridedArrayView1D textureCoords{vertexData, + reinterpret_cast(vertexData.begin() + attributeOffset), + std::size_t(vertexCount.product()), std::ptrdiff_t(stride)}; + attributes[attributeIndex++] = + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, textureCoords}; + attributeOffset += sizeof(Vector2); for(std::size_t i = 0; i != positions.size(); ++i) - textureCoordinates[0].emplace_back(positions[i].xy()*0.5f + Vector2{0.5f}); + textureCoords[i] = positions[i].xy()*0.5f + Vector2{0.5f}; } - return Trade::MeshData3D{MeshPrimitive::Triangles, std::move(indices), {std::move(positions)}, std::move(normals), std::move(textureCoordinates), {}, nullptr}; + return Trade::MeshData{MeshPrimitive::Triangles, + std::move(indexData), Trade::MeshIndexData{indices}, + std::move(vertexData), std::move(attributes)}; } -Trade::MeshData3D grid3DWireframe(const Vector2i& subdivisions) { +Trade::MeshData grid3DWireframe(const Vector2i& subdivisions) { const Vector2i vertexCount = subdivisions + Vector2i{2}; const Vector2i faceCount = subdivisions + Vector2i{1}; - std::vector positions; - positions.reserve(vertexCount.product()); - for(Int y = 0; y != vertexCount.y(); ++y) - for(Int x = 0; x != vertexCount.x(); ++x) - positions.emplace_back((Vector2(x, y)/Vector2(faceCount))*2.0f - Vector2{1.0f}, 0.0f); - - std::vector indices; - indices.reserve(vertexCount.y()*(vertexCount.x() - 1)*2 + - vertexCount.x()*(vertexCount.y() - 1)*2); - for(Int y = 0; y != vertexCount.y(); ++y) { - for(Int x = 0; x != vertexCount.x(); ++x) { - /* 3 7 - | | ... - 2 6 - 0--1 4--5 ... */ - if(x != vertexCount.x() - 1) indices.insert(indices.end(), { - UnsignedInt(y*vertexCount.x() + x), - UnsignedInt(y*vertexCount.x() + x + 1)}); - if(y != vertexCount.y() - 1) indices.insert(indices.end(), { - UnsignedInt(y*vertexCount.x() + x), - UnsignedInt((y + 1)*vertexCount.x() + x)}); + Containers::Array indexData{sizeof(UnsignedInt)* + (vertexCount.y()*(vertexCount.x() - 1)*2 + + vertexCount.x()*(vertexCount.y() - 1)*2)}; + auto indices = Containers::arrayCast(indexData); + { + std::size_t i = 0; + for(Int y = 0; y != vertexCount.y(); ++y) { + for(Int x = 0; x != vertexCount.x(); ++x) { + /* 3 7 + | | ... + 2 6 + 0--1 4--5 ... */ + if(x != vertexCount.x() - 1) { + indices[i++] = UnsignedInt(y*vertexCount.x() + x); + indices[i++] = UnsignedInt(y*vertexCount.x() + x + 1); + } + if(y != vertexCount.y() - 1) { + indices[i++] = UnsignedInt(y*vertexCount.x() + x); + indices[i++] = UnsignedInt((y + 1)*vertexCount.x() + x); + } + } } } - return Trade::MeshData3D{MeshPrimitive::Lines, std::move(indices), {std::move(positions)}, {}, {}, {}, nullptr}; + Containers::Array vertexData{sizeof(Vector3)*vertexCount.product()}; + auto positions = Containers::arrayCast(vertexData); + { + std::size_t i = 0; + for(Int y = 0; y != vertexCount.y(); ++y) + for(Int x = 0; x != vertexCount.x(); ++x) + positions[i++] = {(Vector2(x, y)/Vector2(faceCount))*2.0f - Vector2{1.0f}, 0.0f}; + } + + return Trade::MeshData{MeshPrimitive::Lines, + std::move(indexData), Trade::MeshIndexData{indices}, + std::move(vertexData), {Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}}}; } }} diff --git a/src/Magnum/Primitives/Grid.h b/src/Magnum/Primitives/Grid.h index 09f7b8779b..60f524e07e 100644 --- a/src/Magnum/Primitives/Grid.h +++ b/src/Magnum/Primitives/Grid.h @@ -66,8 +66,11 @@ CORRADE_ENUMSET_OPERATORS(GridFlags) /** @brief Solid 3D grid -2x2 grid in the XY plane with normals in positive Z direction. Indexed -@ref MeshPrimitive::Triangles with optional normals and texture coordinates. +2x2 grid in the XY plane with normals in positive Z direction. +@ref MeshPrimitive::Triangles with @ref MeshIndexType::UnsignedInt indices, +interleaved @ref VertexFormat::Vector3 positions, optional +@ref VertexFormat::Vector3 normals and @ref VertexFormat::Vector2 texture +coordinates. @image html primitives-grid3dsolid.png width=256px @@ -78,12 +81,14 @@ cells horizontally and 4 vertically. In particular, this is different from the `subdivisions` parameter in @ref icosphereSolid(). @see @ref grid3DWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D grid3DSolid(const Vector2i& subdivisions, GridFlags flags = GridFlag::GenerateNormals); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData grid3DSolid(const Vector2i& subdivisions, GridFlags flags = GridFlag::GenerateNormals); /** @brief Wireframe 3D grid -2x2 grid in the XY plane. Indexed @ref MeshPrimitive::Lines. +2x2 grid in the XY plane. @ref MeshPrimitive::Lines with +@ref MeshIndexType::UnsignedInt indices and @ref VertexFormat::Vector3 +positions. @image html primitives-grid3dwireframe.png width=256px @@ -91,10 +96,12 @@ The @p subdivisions parameter describes how many times the plane gets cut in each direction. Specifying @cpp {0, 0} @ce will make the result an (indexed) equivalent to @ref planeWireframe(); @cpp {5, 3} @ce will make the grid have 6 cells horizontally and 4 vertically. In particular, this is different from the -`subdivisions` parameter in @ref icosphereSolid(). +`subdivisions` parameter in @ref icosphereSolid(). Also please note the grid +has vertices in each intersection to be suitable for deformation along the Z +axis --- not just long lines crossing each other. @see @ref grid3DSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D grid3DWireframe(const Vector2i& subdivisions); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData grid3DWireframe(const Vector2i& subdivisions); }} diff --git a/src/Magnum/Primitives/Icosphere.cpp b/src/Magnum/Primitives/Icosphere.cpp index 80ee71b661..2ee07eb888 100644 --- a/src/Magnum/Primitives/Icosphere.cpp +++ b/src/Magnum/Primitives/Icosphere.cpp @@ -25,62 +25,103 @@ #include "Icosphere.h" +#include + #include "Magnum/Mesh.h" -#include "Magnum/Math/Color.h" +#include "Magnum/Math/Vector3.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Subdivide.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D icosphereSolid(const UnsignedInt subdivisions) { - std::vector indices{ - 1, 2, 6, - 1, 7, 2, - 3, 4, 5, - 4, 3, 8, - 6, 5, 11, - 5, 6, 10, - 9, 10, 2, - 10, 9, 3, - 7, 8, 9, - 8, 7, 0, - 11, 0, 1, - 0, 11, 4, - 6, 2, 10, - 1, 6, 11, - 3, 5, 10, - 5, 4, 11, - 2, 7, 9, - 7, 1, 0, - 3, 9, 8, - 4, 8, 0 - }; +namespace { + +constexpr UnsignedInt Indices[]{ + 1, 2, 6, + 1, 7, 2, + 3, 4, 5, + 4, 3, 8, + 6, 5, 11, + 5, 6, 10, + 9, 10, 2, + 10, 9, 3, + 7, 8, 9, + 8, 7, 0, + 11, 0, 1, + 0, 11, 4, + 6, 2, 10, + 1, 6, 11, + 3, 5, 10, + 5, 4, 11, + 2, 7, 9, + 7, 1, 0, + 3, 9, 8, + 4, 8, 0 +}; + +constexpr Vector3 Positions[]{ + {0.0f, -0.525731f, 0.850651f}, + {0.850651f, 0.0f, 0.525731f}, + {0.850651f, 0.0f, -0.525731f}, + {-0.850651f, 0.0f, -0.525731f}, + {-0.850651f, 0.0f, 0.525731f}, + {-0.525731f, 0.850651f, 0.0f}, + {0.525731f, 0.850651f, 0.0f}, + {0.525731f, -0.850651f, 0.0f}, + {-0.525731f, -0.850651f, 0.0f}, + {0.0f, -0.525731f, -0.850651f}, + {0.0f, 0.525731f, -0.850651f}, + {0.0f, 0.525731f, 0.850651f} +}; + +} + +Trade::MeshData icosphereSolid(const UnsignedInt subdivisions) { + const std::size_t indexCount = Containers::arraySize(Indices)*(1 << subdivisions*2); + const std::size_t vertexCount = Containers::arraySize(Positions) + ((indexCount - Containers::arraySize(Indices))/3); + + Containers::Array indexData{indexCount*sizeof(UnsignedInt)}; + auto indices = Containers::arrayCast(indexData); + std::memcpy(indices.begin(), Indices, sizeof(Indices)); - std::vector positions{ - {0.0f, -0.525731f, 0.850651f}, - {0.850651f, 0.0f, 0.525731f}, - {0.850651f, 0.0f, -0.525731f}, - {-0.850651f, 0.0f, -0.525731f}, - {-0.850651f, 0.0f, 0.525731f}, - {-0.525731f, 0.850651f, 0.0f}, - {0.525731f, 0.850651f, 0.0f}, - {0.525731f, -0.850651f, 0.0f}, - {-0.525731f, -0.850651f, 0.0f}, - {0.0f, -0.525731f, -0.850651f}, - {0.0f, 0.525731f, -0.850651f}, - {0.0f, 0.525731f, 0.850651f} + struct Vertex { + Vector3 position; + Vector3 normal; }; + Containers::Array vertexData; + arrayResize(vertexData, Containers::NoInit, sizeof(Vertex)*vertexCount); + + /* Build up the subdivided positions */ + { + auto vertices = Containers::arrayCast(vertexData); + Containers::StridedArrayView1D positions{vertices, &vertices[0].position, vertices.size(), sizeof(Vertex)}; + for(std::size_t i = 0; i != Containers::arraySize(Positions); ++i) + positions[i] = Positions[i]; + + for(std::size_t i = 0; i != subdivisions; ++i) { + const std::size_t iterationIndexCount = Containers::arraySize(Indices)*(1 << (i + 1)*2); + const std::size_t iterationVertexCount = Containers::arraySize(Positions) + ((iterationIndexCount - Containers::arraySize(Indices))/3); + MeshTools::subdivideInPlace(indices.prefix(iterationIndexCount), positions.prefix(iterationVertexCount), [](const Vector3& a, const Vector3& b) { + return (a+b).normalized(); + }); + } - for(std::size_t i = 0; i != subdivisions; ++i) - MeshTools::subdivide(indices, positions, [](const Vector3& a, const Vector3& b) { - return (a+b).normalized(); - }); + /** @todo i need arrayShrinkAndGiveUpMemoryIfItDoesntCauseRealloc() */ + arrayResize(vertexData, MeshTools::removeDuplicatesIndexedInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(positions))*sizeof(Vertex)); + } - positions.resize(MeshTools::removeDuplicatesIndexedInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(positions))); + /* Build up the views again with correct size, fill the normals */ + auto vertices = Containers::arrayCast(vertexData); + Containers::StridedArrayView1D positions{vertices, &vertices[0].position, vertices.size(), sizeof(Vertex)}; + Containers::StridedArrayView1D normals{vertices, &vertices[0].normal, vertices.size(), sizeof(Vertex)}; + for(std::size_t i = 0; i != positions.size(); ++i) + normals[i] = positions[i]; - std::vector normals(positions); - return Trade::MeshData3D{MeshPrimitive::Triangles, std::move(indices), {std::move(positions)}, {std::move(normals)}, {}, {}, nullptr}; + return Trade::MeshData{MeshPrimitive::Triangles, std::move(indexData), + Trade::MeshIndexData{indices}, std::move(vertexData), + {Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normals}}}; } }} diff --git a/src/Magnum/Primitives/Icosphere.h b/src/Magnum/Primitives/Icosphere.h index b1bceb884b..9a7981cf16 100644 --- a/src/Magnum/Primitives/Icosphere.h +++ b/src/Magnum/Primitives/Icosphere.h @@ -38,8 +38,9 @@ namespace Magnum { namespace Primitives { @brief Solid 3D icosphere @param subdivisions Number of subdivisions -Sphere with radius @cpp 1.0f @ce. Indexed @ref MeshPrimitive::Triangles with -normals. +Sphere with radius @cpp 1.0f @ce. @ref MeshPrimitive::Triangles with +@ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 +positions and @ref VertexFormat::Vector3 normals. @image html primitives-icospheresolid.png width=256px @@ -51,7 +52,7 @@ result in 320 faces and so on. In particular, this is different from the `subdivisions` parameter in @ref grid3DSolid() or @ref grid3DWireframe(). @see @ref uvSphereSolid(), @ref uvSphereWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D icosphereSolid(UnsignedInt subdivisions); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData icosphereSolid(UnsignedInt subdivisions); }} diff --git a/src/Magnum/Primitives/Implementation/Spheroid.cpp b/src/Magnum/Primitives/Implementation/Spheroid.cpp index 9af9a762da..a1c89a8695 100644 --- a/src/Magnum/Primitives/Implementation/Spheroid.cpp +++ b/src/Magnum/Primitives/Implementation/Spheroid.cpp @@ -25,25 +25,59 @@ #include "Spheroid.h" +#include + #include "Magnum/Math/Functions.h" #include "Magnum/Math/Color.h" #include "Magnum/Mesh.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Implementation { -Spheroid::Spheroid(UnsignedInt segments, TextureCoords textureCoords): segments(segments), textureCoords(textureCoords) {} +Spheroid::Spheroid(UnsignedInt segments, TextureCoords textureCoords): _segments(segments), _textureCoords(textureCoords) {} -void Spheroid::capVertex(Float y, Float normalY, Float textureCoordsV) { - positions.emplace_back(0.0f, y, 0.0f); - normals.emplace_back(0.0f, normalY, 0.0f); +namespace { + +struct Vertex { + Vector3 position; + Vector3 normal; +}; + +struct VertexTextureCoords { + Vector3 position; + Vector3 normal; + Vector2 textureCoords; +}; + +} - if(textureCoords == TextureCoords::Generate) - textureCoords2D.emplace_back(0.5, textureCoordsV); +/** @todo gah this is fugly, any idea how to do this less awful? expose + arrayGrow? also, with current growth strategy this might realloc too much + at the beginning since the growth is optimized for adding a single + element */ +void Spheroid::append(const Vector3& position, const Vector3& normal, const Vector2& textureCoords) { + if(_textureCoords == TextureCoords::Generate) { + const VertexTextureCoords v[]{{position, normal, textureCoords}}; + arrayAppend(_vertexData, Containers::arrayCast(Containers::arrayView(v))); + } else { + const Vertex v[]{{position, normal}}; + arrayAppend(_vertexData, Containers::arrayCast(Containers::arrayView(v))); + } +} + +void Spheroid::setLastVertexTextureCoords(const Vector2& textureCoords) { + /* Assuming append() was called before */ + Containers::arrayCast(_vertexData).back().textureCoords = textureCoords; +} + +void Spheroid::capVertex(Float y, Float normalY, Float textureCoordsV) { + append({0.0f, y, 0.0f}, {0.0f, normalY, 0.0f}); + if(_textureCoords == TextureCoords::Generate) + setLastVertexTextureCoords({0.5f, textureCoordsV}); } void Spheroid::hemisphereVertexRings(UnsignedInt count, Float centerY, Rad startRingAngle, Rad ringAngleIncrement, Float startTextureCoordsV, Float textureCoordsVIncrement) { - const Rad segmentAngleIncrement(Constants::tau()/segments); + const Rad segmentAngleIncrement(Constants::tau()/_segments); for(UnsignedInt i = 0; i != count; ++i) { const Rad ringAngle = startRingAngle + Float(i)*ringAngleIncrement; const std::pair ringSinCos = Math::sincos(ringAngle); @@ -51,21 +85,23 @@ void Spheroid::hemisphereVertexRings(UnsignedInt count, Float centerY, Rad start const Float z = ringSinCos.second; const Float y = ringSinCos.first; - for(UnsignedInt j = 0; j != segments; ++j) { + for(UnsignedInt j = 0; j != _segments; ++j) { const Rad segmentAngle = Float(j)*segmentAngleIncrement; const std::pair segmentSinCos = Math::sincos(segmentAngle); - positions.emplace_back(x*segmentSinCos.first, centerY+y, z*segmentSinCos.second); - normals.emplace_back(x*segmentSinCos.first, y, z*segmentSinCos.second); + append({x*segmentSinCos.first, centerY+y, z*segmentSinCos.second}, + {x*segmentSinCos.first, y, z*segmentSinCos.second}); - if(textureCoords == TextureCoords::Generate) - textureCoords2D.emplace_back(j*1.0f/segments, startTextureCoordsV + i*textureCoordsVIncrement); + if(_textureCoords == TextureCoords::Generate) + setLastVertexTextureCoords({j*1.0f/_segments, startTextureCoordsV + i*textureCoordsVIncrement}); } /* Duplicate first segment in the ring for additional vertex for texture coordinate */ - if(textureCoords == TextureCoords::Generate) { - positions.push_back(positions[positions.size()-segments]); - normals.push_back(normals[normals.size()-segments]); - textureCoords2D.emplace_back(1.0f, startTextureCoordsV + i*textureCoordsVIncrement); + if(_textureCoords == TextureCoords::Generate) { + /* This view will become dangling right after append() */ + auto typedVertices = Containers::arrayCast(_vertexData); + append(typedVertices[typedVertices.size()-_segments].position, + typedVertices[typedVertices.size()-_segments].normal, + {1.0f, startTextureCoordsV + i*textureCoordsVIncrement}); } } } @@ -74,23 +110,25 @@ void Spheroid::cylinderVertexRings(const UnsignedInt count, const Float startY, const Vector2 baseNormal = -increment.perpendicular().normalized(); Vector2 base = {1.0f, startY}; - const Rad segmentAngleIncrement(Constants::tau()/segments); + const Rad segmentAngleIncrement(Constants::tau()/_segments); for(UnsignedInt i = 0; i != count; ++i) { - for(UnsignedInt j = 0; j != segments; ++j) { + for(UnsignedInt j = 0; j != _segments; ++j) { const Rad segmentAngle = Float(j)*segmentAngleIncrement; const std::pair segmentSinCos = Math::sincos(segmentAngle); - positions.emplace_back(base.x()*segmentSinCos.first, base.y(), base.x()*segmentSinCos.second); - normals.emplace_back(baseNormal.x()*segmentSinCos.first, baseNormal.y(), baseNormal.x()*segmentSinCos.second); + append({base.x()*segmentSinCos.first, base.y(), base.x()*segmentSinCos.second}, + {baseNormal.x()*segmentSinCos.first, baseNormal.y(), baseNormal.x()*segmentSinCos.second}); - if(textureCoords == TextureCoords::Generate) - textureCoords2D.emplace_back(j*1.0f/segments, startTextureCoordsV + i*textureCoordsVIncrement); + if(_textureCoords == TextureCoords::Generate) + setLastVertexTextureCoords({j*1.0f/_segments, startTextureCoordsV + i*textureCoordsVIncrement}); } /* Duplicate first segment in the ring for additional vertex for texture coordinate */ - if(textureCoords == TextureCoords::Generate) { - positions.push_back(positions[positions.size()-segments]); - normals.push_back(normals[normals.size()-segments]); - textureCoords2D.emplace_back(1.0f, startTextureCoordsV + i*textureCoordsVIncrement); + if(_textureCoords == TextureCoords::Generate) { + /* This view will become dangling right after append() */ + auto typedVertices = Containers::arrayCast(_vertexData); + append(typedVertices[typedVertices.size()-_segments].position, + typedVertices[typedVertices.size()-_segments].normal, + {1.0f, startTextureCoordsV + i*textureCoordsVIncrement}); } base += increment; @@ -98,80 +136,118 @@ void Spheroid::cylinderVertexRings(const UnsignedInt count, const Float startY, } void Spheroid::bottomFaceRing() { - for(UnsignedInt j = 0; j != segments; ++j) { - /* Bottom vertex */ - indices.push_back(0); - - /* Top right vertex */ - indices.push_back((j != segments-1 || textureCoords == TextureCoords::Generate) ? - j+2 : 1); - - /* Top left vertex */ - indices.push_back(j+1); + for(UnsignedInt j = 0; j != _segments; ++j) { + arrayAppend(_indexData, { + /* Bottom vertex */ + 0u, + + /* Top right vertex */ + (j != _segments-1 || _textureCoords == TextureCoords::Generate) ? + j+2 : 1, + + /* Top left vertex */ + j+1 + }); } } void Spheroid::faceRings(UnsignedInt count, UnsignedInt offset) { - const UnsignedInt vertexSegments = segments + (textureCoords == TextureCoords::Generate ? 1 : 0); + const UnsignedInt vertexSegments = _segments + (_textureCoords == TextureCoords::Generate ? 1 : 0); for(UnsignedInt i = 0; i != count; ++i) { - for(UnsignedInt j = 0; j != segments; ++j) { + for(UnsignedInt j = 0; j != _segments; ++j) { const UnsignedInt bottomLeft = i*vertexSegments+j+offset; - const UnsignedInt bottomRight = ((j != segments-1 || textureCoords == TextureCoords::Generate) ? - i*vertexSegments+j+1+offset : i*segments+offset); + const UnsignedInt bottomRight = ((j != _segments-1 || _textureCoords == TextureCoords::Generate) ? + i*vertexSegments+j+1+offset : i*_segments+offset); const UnsignedInt topLeft = bottomLeft+vertexSegments; const UnsignedInt topRight = bottomRight+vertexSegments; - indices.push_back(bottomLeft); - indices.push_back(bottomRight); - indices.push_back(topRight); - indices.push_back(bottomLeft); - indices.push_back(topRight); - indices.push_back(topLeft); + arrayAppend(_indexData, { + bottomLeft, + bottomRight, + topRight, + bottomLeft, + topRight, + topLeft + }); } } } void Spheroid::topFaceRing() { - const UnsignedInt vertexSegments = segments + (textureCoords == TextureCoords::Generate ? 1 : 0); - - for(UnsignedInt j = 0; j != segments; ++j) { - /* Bottom left vertex */ - indices.push_back(normals.size()-vertexSegments+j-1); - - /* Bottom right vertex */ - indices.push_back((j != segments-1 || textureCoords == TextureCoords::Generate) ? - normals.size()-vertexSegments+j : normals.size()-segments-1); - - /* Top vertex */ - indices.push_back(normals.size()-1); + const UnsignedInt vertexSegments = _segments + (_textureCoords == TextureCoords::Generate ? 1 : 0); + + UnsignedInt vertexCount; + if(_textureCoords == TextureCoords::Generate) + vertexCount = _vertexData.size()/sizeof(VertexTextureCoords); + else + vertexCount = _vertexData.size()/sizeof(Vertex); + + for(UnsignedInt j = 0; j != _segments; ++j) { + arrayAppend(_indexData, { + /* Bottom left vertex */ + vertexCount - vertexSegments + j - 1, + + /* Bottom right vertex */ + (j != _segments-1 || _textureCoords == TextureCoords::Generate) ? + vertexCount - vertexSegments + j : vertexCount - _segments - 1, + + /* Top vertex */ + vertexCount - 1 + }); } } void Spheroid::capVertexRing(Float y, Float textureCoordsV, const Vector3& normal) { - const Rad segmentAngleIncrement(Constants::tau()/segments); + const Rad segmentAngleIncrement(Constants::tau()/_segments); - for(UnsignedInt i = 0; i != segments; ++i) { + for(UnsignedInt i = 0; i != _segments; ++i) { const Rad segmentAngle = Float(i)*segmentAngleIncrement; const std::pair segmentSinCos = Math::sincos(segmentAngle); - positions.emplace_back(segmentSinCos.first, y, segmentSinCos.second); - normals.push_back(normal); + append({segmentSinCos.first, y, segmentSinCos.second}, normal); - if(textureCoords == TextureCoords::Generate) - textureCoords2D.emplace_back(i*1.0f/segments, textureCoordsV); + if(_textureCoords == TextureCoords::Generate) + setLastVertexTextureCoords({i*1.0f/_segments, textureCoordsV}); } /* Duplicate first segment in the ring for additional vertex for texture coordinate */ - if(textureCoords == TextureCoords::Generate) { - positions.push_back(positions[positions.size()-segments]); - normals.push_back(normal); - textureCoords2D.emplace_back(1.0f, textureCoordsV); + if(_textureCoords == TextureCoords::Generate) { + /* This view will become dangling right after append() */ + auto typedVertices = Containers::arrayCast(_vertexData); + append(typedVertices[typedVertices.size()-_segments].position, + normal, + {1.0f, textureCoordsV}); } } -Trade::MeshData3D Spheroid::finalize() { - return Trade::MeshData3D{MeshPrimitive::Triangles, std::move(indices), {std::move(positions)}, {std::move(normals)}, - textureCoords == TextureCoords::Generate ? std::vector>{std::move(textureCoords2D)} : std::vector>(), {}, nullptr}; +Trade::MeshData Spheroid::finalize() { + Trade::MeshIndexData indices{_indexData}; + + const std::size_t stride = _textureCoords == TextureCoords::Generate ? + sizeof(VertexTextureCoords) : sizeof(Vertex); + const std::size_t size = _vertexData.size()/stride; + + auto typedVertices = reinterpret_cast(_vertexData.data()); + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::stridedArrayView(_vertexData, &typedVertices[0].position, + size, stride)}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::stridedArrayView(_vertexData, &typedVertices[0].normal, + size, stride)}; + + Containers::Array attributes; + if(_textureCoords == TextureCoords::Generate) { + Trade::MeshAttributeData textureCoords{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(_vertexData, &typedVertices[0].textureCoords, + size, stride)}; + attributes = Containers::Array{Containers::InPlaceInit, {positions, normals, textureCoords}}; + } else { + attributes = Containers::Array{Containers::InPlaceInit, {positions, normals}}; + } + + return Trade::MeshData{MeshPrimitive::Triangles, + Containers::arrayAllocatorCast(std::move(_indexData)), indices, + std::move(_vertexData), std::move(attributes)}; } }}} diff --git a/src/Magnum/Primitives/Implementation/Spheroid.h b/src/Magnum/Primitives/Implementation/Spheroid.h index 4162f716ad..62f03afe94 100644 --- a/src/Magnum/Primitives/Implementation/Spheroid.h +++ b/src/Magnum/Primitives/Implementation/Spheroid.h @@ -25,9 +25,11 @@ DEALINGS IN THE SOFTWARE. */ -#include +#include +#include #include "Magnum/Magnum.h" +#include "Magnum/Math/Vector2.h" #include "Magnum/Trade/Trade.h" namespace Magnum { namespace Primitives { namespace Implementation { @@ -49,16 +51,17 @@ class Spheroid { void topFaceRing(); void capVertexRing(Float y, Float textureCoordsV, const Vector3& normal); - Trade::MeshData3D finalize(); + Trade::MeshData finalize(); private: - UnsignedInt segments; - TextureCoords textureCoords; + UnsignedInt _segments; + TextureCoords _textureCoords; - std::vector indices; - std::vector positions; - std::vector normals; - std::vector textureCoords2D; + Containers::Array _indexData; + Containers::Array _vertexData; + + void append(const Vector3& position, const Vector3& normal, const Vector2& textureCoords = {}); + void setLastVertexTextureCoords(const Vector2& textureCoords); }; }}} diff --git a/src/Magnum/Primitives/Implementation/WireframeSpheroid.cpp b/src/Magnum/Primitives/Implementation/WireframeSpheroid.cpp index 64dccafcae..6b40ca3aa4 100644 --- a/src/Magnum/Primitives/Implementation/WireframeSpheroid.cpp +++ b/src/Magnum/Primitives/Implementation/WireframeSpheroid.cpp @@ -25,24 +25,26 @@ #include "WireframeSpheroid.h" +#include + #include "Magnum/Math/Functions.h" #include "Magnum/Math/Color.h" #include "Magnum/Mesh.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Implementation { WireframeSpheroid::WireframeSpheroid(const UnsignedInt segments): _segments(segments) {} void WireframeSpheroid::bottomHemisphere(const Float endY, const UnsignedInt rings) { - CORRADE_INTERNAL_ASSERT(_positions.empty()); + CORRADE_INTERNAL_ASSERT(_vertexData.empty()); /* Initial vertex */ - _positions.push_back(Vector3::yAxis(endY - 1.0f)); + arrayAppend(_vertexData, Vector3::yAxis(endY - 1.0f)); /* Connect initial vertex to first ring */ for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {0, i+1}); + arrayAppend(_indexData, {0u, i+1}); /* Hemisphere vertices and indices */ const Rad ringAngleIncrement(Constants::piHalf()/rings); @@ -50,21 +52,24 @@ void WireframeSpheroid::bottomHemisphere(const Float endY, const UnsignedInt rin const Rad angle = Float(j+1)*ringAngleIncrement; const std::pair sincos = Math::sincos(angle); - _positions.emplace_back(0.0f, endY - sincos.second, sincos.first); - _positions.emplace_back(sincos.first, endY - sincos.second, 0.0f); - _positions.emplace_back(0.0f, endY - sincos.second, -sincos.first); - _positions.emplace_back(-sincos.first, endY - sincos.second, 0.0f); + arrayAppend(_vertexData, { + {0.0f, endY - sincos.second, sincos.first}, + {sincos.first, endY - sincos.second, 0.0f}, + {0.0f, endY - sincos.second, -sincos.first}, + {-sincos.first, endY - sincos.second, 0.0f} + }); /* Connect vertices to next ring */ - for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {UnsignedInt(_positions.size())-4+i, UnsignedInt(_positions.size())+i}); + for(UnsignedInt i = 0; i != 4; ++i) { + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4 + i, UnsignedInt(_vertexData.size()) + i}); + } } } void WireframeSpheroid::topHemisphere(const Float startY, const UnsignedInt rings) { /* Connect previous ring to following vertices (if any) */ if(rings > 1) for(UnsignedInt i = 0; i != 4; ++i) { - _indices.insert(_indices.end(), {UnsignedInt(_positions.size())-4*_segments+i, UnsignedInt(_positions.size())+i}); + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4*_segments + i, UnsignedInt(_vertexData.size()) + i}); } /* Hemisphere vertices and indices */ @@ -74,23 +79,26 @@ void WireframeSpheroid::topHemisphere(const Float startY, const UnsignedInt ring const std::pair sincos = Math::sincos(angle); /* Connect previous hemisphere ring to current vertices */ - if(j != 0) for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {UnsignedInt(_positions.size())-4+i, UnsignedInt(_positions.size())+i}); + if(j != 0) for(UnsignedInt i = 0; i != 4; ++i) { + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4 + i, UnsignedInt(_vertexData.size()) + i}); + } - _positions.emplace_back(0.0f, startY + sincos.first, sincos.second); - _positions.emplace_back(sincos.second, startY + sincos.first, 0.0f); - _positions.emplace_back(0.0f, startY + sincos.first, -sincos.second); - _positions.emplace_back(-sincos.second, startY + sincos.first, 0.0f); + arrayAppend(_vertexData, { + {0.0f, startY + sincos.first, sincos.second}, + {sincos.second, startY + sincos.first, 0.0f}, + {0.0f, startY + sincos.first, -sincos.second}, + {-sincos.second, startY + sincos.first, 0.0f} + }); } /* Final vertex */ - _positions.push_back(Vector3::yAxis(startY + 1.0f)); + arrayAppend(_vertexData, Vector3::yAxis(startY + 1.0f)); /* Connect last ring to final vertex */ if(rings > 1) for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {UnsignedInt(_positions.size()) -5 + i, UnsignedInt(_positions.size()) - 1}); + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 5 + i, UnsignedInt(_vertexData.size()) - 1}); else for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {UnsignedInt(_positions.size()) - 4*_segments + i- 1 , UnsignedInt(_positions.size()) - 1}); + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4*_segments + i - 1, UnsignedInt(_vertexData.size()) - 1}); } void WireframeSpheroid::ring(const Float y) { @@ -100,24 +108,28 @@ void WireframeSpheroid::ring(const Float y) { for(UnsignedInt i = 0; i != 4; ++i) { const Rad segmentAngle = Rad(Float(i)*Constants::piHalf()) + Float(j)*segmentAngleIncrement; const std::pair sincos = Math::sincos(segmentAngle); - if(j != 0) _indices.insert(_indices.end(), {UnsignedInt(_positions.size()-4), UnsignedInt(_positions.size())}); - _positions.emplace_back(sincos.first, y, sincos.second); + if(j != 0) arrayAppend(_indexData, {UnsignedInt(_vertexData.size() - 4), UnsignedInt(_vertexData.size())}); + arrayAppend(_vertexData, {sincos.first, y, sincos.second}); } } /* Close the ring */ for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {UnsignedInt(_positions.size())-4+i, UnsignedInt(_positions.size())-4*_segments+(i+1)%4}); + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4 + i, UnsignedInt(_vertexData.size()) - 4*_segments + (i + 1)%4}); } void WireframeSpheroid::cylinder() { /* Connect four vertex pairs of previous and next ring */ for(UnsignedInt i = 0; i != 4; ++i) - _indices.insert(_indices.end(), {UnsignedInt(_positions.size())-4*_segments+i, UnsignedInt(_positions.size())+i}); + arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4*_segments + i, UnsignedInt(_vertexData.size()) + i}); } -Trade::MeshData3D WireframeSpheroid::finalize() { - return Trade::MeshData3D{MeshPrimitive::Lines, std::move(_indices), {std::move(_positions)}, {}, {}, {}, nullptr}; +Trade::MeshData WireframeSpheroid::finalize() { + Trade::MeshIndexData indices{_indexData}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, Containers::arrayView(_vertexData)}; + return Trade::MeshData{MeshPrimitive::Lines, + Containers::arrayAllocatorCast(std::move(_indexData)), indices, + Containers::arrayAllocatorCast(std::move(_vertexData)), {positions}}; } }}} diff --git a/src/Magnum/Primitives/Implementation/WireframeSpheroid.h b/src/Magnum/Primitives/Implementation/WireframeSpheroid.h index 24bd3d2395..60042bcc7d 100644 --- a/src/Magnum/Primitives/Implementation/WireframeSpheroid.h +++ b/src/Magnum/Primitives/Implementation/WireframeSpheroid.h @@ -25,7 +25,7 @@ DEALINGS IN THE SOFTWARE. */ -#include +#include #include "Magnum/Magnum.h" #include "Magnum/Trade/Trade.h" @@ -41,13 +41,13 @@ class WireframeSpheroid { void ring(Float y); void cylinder(); - Trade::MeshData3D finalize(); + Trade::MeshData finalize(); private: UnsignedInt _segments; - std::vector _indices; - std::vector _positions; + Containers::Array _indexData; + Containers::Array _vertexData; }; }}} diff --git a/src/Magnum/Primitives/Line.cpp b/src/Magnum/Primitives/Line.cpp index 2f78cba966..37a05460a2 100644 --- a/src/Magnum/Primitives/Line.cpp +++ b/src/Magnum/Primitives/Line.cpp @@ -27,24 +27,35 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData2D line2D(const Vector2& a, const Vector2& b) { - return Trade::MeshData2D{MeshPrimitive::Lines, {}, {{a, b}}, {}, {}, nullptr}; +Trade::MeshData line2D(const Vector2& a, const Vector2& b) { + Containers::Array vertexData{sizeof(Vector2)*2}; + auto positions = Containers::arrayCast(vertexData); + positions[0] = a; + positions[1] = b; + + return Trade::MeshData{MeshPrimitive::Lines, std::move(vertexData), + {Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}}}; } -Trade::MeshData3D line3D(const Vector3& a, const Vector3& b) { - return Trade::MeshData3D{MeshPrimitive::Lines, {}, {{a, b}}, {}, {}, {}, nullptr}; +Trade::MeshData line3D(const Vector3& a, const Vector3& b) { + Containers::Array vertexData{sizeof(Vector3)*2}; + auto positions = Containers::arrayCast(vertexData); + positions[0] = a; + positions[1] = b; + + return Trade::MeshData{MeshPrimitive::Lines, std::move(vertexData), + {Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}}}; } -Trade::MeshData2D line2D() { +Trade::MeshData line2D() { return line2D({0.0f, 0.0f}, {1.0f, 0.0f}); } -Trade::MeshData3D line3D() { +Trade::MeshData line3D() { return line3D({0.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}); } diff --git a/src/Magnum/Primitives/Line.h b/src/Magnum/Primitives/Line.h index 7bc9863d69..8c48eccfbd 100644 --- a/src/Magnum/Primitives/Line.h +++ b/src/Magnum/Primitives/Line.h @@ -38,14 +38,15 @@ namespace Magnum { namespace Primitives { /** @brief 2D line -Non-indexed @ref MeshPrimitive::Lines going from @p a to @p b. +Non-indexed @ref MeshPrimitive::Lines with @ref VertexFormat::Vector2 positions +going from @p a to @p b. @image html primitives-line2d.png width=256px @see @ref line3D(), @ref line3D(const Vector3&, const Vector3&), @ref axis2D(), @ref crosshair2D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D line2D(const Vector2& a, const Vector2& b); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData line2D(const Vector2& a, const Vector2& b); /** @brief 2D line in an identity transformation @@ -54,19 +55,20 @@ Equivalent to calling @ref line2D(const Vector2&, const Vector2&) as @snippet MagnumPrimitives.cpp line2D-identity */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D line2D(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData line2D(); /** @brief 3D line -Non-indexed @ref MeshPrimitive::Lines going from @p a to @p b. +Non-indexed @ref MeshPrimitive::Lines with @ref VertexFormat::Vector3 positions +going from @p a to @p b. @image html primitives-line3d.png width=256px @see @ref line3D(), @ref line2D(const Vector2&, const Vector2&), @ref axis3D(), @ref crosshair3D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D line3D(const Vector3& a, const Vector3& b); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData line3D(const Vector3& a, const Vector3& b); /** @brief 3D line in an identity transformation @@ -76,7 +78,7 @@ Unit-size line in direction of positive X axis. Equivalent to calling @snippet MagnumPrimitives.cpp line3D-identity */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D line3D(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData line3D(); }} diff --git a/src/Magnum/Primitives/Plane.cpp b/src/Magnum/Primitives/Plane.cpp index 48797668a2..a6cd27a9b5 100644 --- a/src/Magnum/Primitives/Plane.cpp +++ b/src/Magnum/Primitives/Plane.cpp @@ -26,40 +26,87 @@ #include "Plane.h" #include "Magnum/Mesh.h" -#include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Math/Vector3.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D planeSolid(const PlaneTextureCoords textureCoords) { - std::vector> coords; - if(textureCoords == PlaneTextureCoords::Generate) coords.push_back({ - {1.0f, 0.0f}, - {1.0f, 1.0f}, - {0.0f, 0.0f}, - {0.0f, 1.0f} - }); - - return Trade::MeshData3D{MeshPrimitive::TriangleStrip, {}, {{ - {1.0f, -1.0f, 0.0f}, - {1.0f, 1.0f, 0.0f}, - {-1.0f, -1.0f, 0.0f}, - {-1.0f, 1.0f, 0.0f} - }}, {{ - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f}, - {0.0f, 0.0f, 1.0f} - }}, std::move(coords), {}, nullptr}; +namespace { + +constexpr struct VertexSolid { + Vector3 position; + Vector3 normal; +} VerticesSolid[] { + {{ 1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + {{ 1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + {{-1.0f, -1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}}, + {{-1.0f, 1.0f, 0.0f}, {0.0f, 0.0f, 1.0f}} +}; +constexpr struct VertexSolidTextureCoords { + Vector3 position; + Vector3 normal; + Vector2 textureCoords; +} VerticesSolidTextureCoords[] { + {VerticesSolid[0].position, VerticesSolid[0].normal, {1.0f, 0.0f}}, + {VerticesSolid[1].position, VerticesSolid[1].normal, {1.0f, 1.0f}}, + {VerticesSolid[2].position, VerticesSolid[2].normal, {0.0f, 0.0f}}, + {VerticesSolid[3].position, VerticesSolid[3].normal, {0.0f, 1.0f}} +}; +constexpr Trade::MeshAttributeData AttributesSolid[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesSolid, &VerticesSolid[0].position, + Containers::arraySize(VerticesSolid), sizeof(VertexSolid))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + Containers::stridedArrayView(VerticesSolid, &VerticesSolid[0].normal, + Containers::arraySize(VerticesSolid), sizeof(VertexSolid))} +}; +constexpr Trade::MeshAttributeData AttributesSolidTextureCoords[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesSolidTextureCoords, + &VerticesSolidTextureCoords[0].position, + Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + Containers::stridedArrayView(VerticesSolidTextureCoords, + &VerticesSolidTextureCoords[0].normal, + Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(VerticesSolidTextureCoords, + &VerticesSolidTextureCoords[0].textureCoords, + Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))}, +}; + +} + +Trade::MeshData planeSolid(const PlaneTextureCoords textureCoords) { + if(textureCoords != PlaneTextureCoords::Generate) + return Trade::MeshData{MeshPrimitive::TriangleStrip, + {}, VerticesSolid, + Trade::meshAttributeDataNonOwningArray(AttributesSolid)}; + + return Trade::MeshData{MeshPrimitive::TriangleStrip, + {}, VerticesSolidTextureCoords, + Trade::meshAttributeDataNonOwningArray(AttributesSolidTextureCoords)}; +} + +namespace { + +constexpr Vector3 VerticesWireframe[]{ + {-1.0f, -1.0f, 0.0f}, + { 1.0f, -1.0f, 0.0f}, + { 1.0f, 1.0f, 0.0f}, + {-1.0f, 1.0f, 0.0f} +}; +constexpr Trade::MeshAttributeData AttributesWireframe[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(VerticesWireframe)} +}; + } -Trade::MeshData3D planeWireframe() { - return Trade::MeshData3D{MeshPrimitive::LineLoop, {}, {{ - {-1.0f, -1.0f, 0.0f}, - {1.0f, -1.0f, 0.0f}, - {1.0f, 1.0f, 0.0f}, - {-1.0f, 1.0f, 0.0f} - }}, {}, {}, {}, nullptr}; +Trade::MeshData planeWireframe() { + return Trade::MeshData{MeshPrimitive::LineLoop, + {}, VerticesWireframe, + Trade::meshAttributeDataNonOwningArray(AttributesWireframe)}; } }} diff --git a/src/Magnum/Primitives/Plane.h b/src/Magnum/Primitives/Plane.h index 0f2cab9cd1..2bb902b81b 100644 --- a/src/Magnum/Primitives/Plane.h +++ b/src/Magnum/Primitives/Plane.h @@ -50,24 +50,28 @@ enum class PlaneTextureCoords: UnsignedByte { @brief Solid 3D plane 2x2 plane. Non-indexed @ref MeshPrimitive::TriangleStrip on the XY plane with -normals in positive Z direction. +@ref VertexFormat::Vector3 positions and @ref VertexFormat::Vector3 normals in +positive Z direction. The returned instance references data stored in constant +memory. @image html primitives-planesolid.png width=256px @see @ref planeWireframe(), @ref squareSolid(), @ref gradient3D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D planeSolid(PlaneTextureCoords textureCoords = PlaneTextureCoords::DontGenerate); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData planeSolid(PlaneTextureCoords textureCoords = PlaneTextureCoords::DontGenerate); /** @brief Wireframe 3D plane -2x2 plane. Non-indexed @ref MeshPrimitive::LineLoop on the XY plane. +2x2 plane. Non-indexed @ref MeshPrimitive::LineLoop on the XY plane with +@ref VertexFormat::Vector3 positions. The returned instance references data +stored in constant memory. @image html primitives-planewireframe.png width=256px @see @ref planeSolid(), @ref squareWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D planeWireframe(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData planeWireframe(); }} diff --git a/src/Magnum/Primitives/Square.cpp b/src/Magnum/Primitives/Square.cpp index 93c833e958..82dd8165be 100644 --- a/src/Magnum/Primitives/Square.cpp +++ b/src/Magnum/Primitives/Square.cpp @@ -27,34 +27,74 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData2D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData2D squareSolid(const SquareTextureCoords textureCoords) { - std::vector> coords; - if(textureCoords == SquareTextureCoords::Generate) coords.push_back({ - {1.0f, 0.0f}, - {1.0f, 1.0f}, - {0.0f, 0.0f}, - {0.0f, 1.0f} - }); - - return Trade::MeshData2D{MeshPrimitive::TriangleStrip, {}, {{ - {1.0f, -1.0f}, - {1.0f, 1.0f}, - {-1.0f, -1.0f}, - {-1.0f, 1.0f} - }}, std::move(coords), {}, nullptr}; +namespace { + +constexpr Vector2 VerticesSolid[] { + { 1.0f, -1.0f}, + { 1.0f, 1.0f}, + {-1.0f, -1.0f}, + {-1.0f, 1.0f} +}; +constexpr struct VertexSolidTextureCoords { + Vector2 position; + Vector2 textureCoords; +} VerticesSolidTextureCoords[] { + {{ 1.0f, -1.0f}, {1.0f, 0.0f}}, + {{ 1.0f, 1.0f}, {1.0f, 1.0f}}, + {{-1.0f, -1.0f}, {0.0f, 0.0f}}, + {{-1.0f, 1.0f}, {0.0f, 1.0f}} +}; +constexpr Trade::MeshAttributeData AttributesSolid[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesSolid)} +}; +constexpr Trade::MeshAttributeData AttributesSolidTextureCoords[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesSolidTextureCoords, + &VerticesSolidTextureCoords[0].position, + Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(VerticesSolidTextureCoords, + &VerticesSolidTextureCoords[0].textureCoords, + Containers::arraySize(VerticesSolidTextureCoords), sizeof(VertexSolidTextureCoords))} +}; + +} + +Trade::MeshData squareSolid(const SquareTextureCoords textureCoords) { + if(textureCoords != SquareTextureCoords::Generate) + return Trade::MeshData{MeshPrimitive::TriangleStrip, + {}, VerticesSolid, + Trade::meshAttributeDataNonOwningArray(AttributesSolid)}; + + return Trade::MeshData{MeshPrimitive::TriangleStrip, + {}, VerticesSolidTextureCoords, + Trade::meshAttributeDataNonOwningArray(AttributesSolidTextureCoords)}; +} + +namespace { + +constexpr Vector2 VerticesWireframe[]{ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 1.0f, 1.0f}, + {-1.0f, 1.0f} +}; +constexpr Trade::MeshAttributeData AttributesWireframe[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(VerticesWireframe)} +}; + } -Trade::MeshData2D squareWireframe() { - return Trade::MeshData2D{MeshPrimitive::LineLoop, {}, {{ - {-1.0f, -1.0f}, - {1.0f, -1.0f}, - {1.0f, 1.0f}, - {-1.0f, 1.0f} - }}, {}, {}, nullptr}; +Trade::MeshData squareWireframe() { + return Trade::MeshData{MeshPrimitive::LineLoop, + {}, VerticesWireframe, + Trade::meshAttributeDataNonOwningArray(AttributesWireframe)}; } }} diff --git a/src/Magnum/Primitives/Square.h b/src/Magnum/Primitives/Square.h index 4e0940dc9b..5db993e926 100644 --- a/src/Magnum/Primitives/Square.h +++ b/src/Magnum/Primitives/Square.h @@ -49,24 +49,29 @@ enum class SquareTextureCoords: UnsignedByte { /** @brief Solid 2D square -2x2 square. Non-indexed @ref MeshPrimitive::TriangleStrip. +2x2 square. Non-indexed @ref MeshPrimitive::TriangleStrip with interleaved +@ref VertexFormat::Vector2 positions and optional @ref VertexFormat::Vector2 +texture coordinates. The returned instance references data stored in constant +memory. @image html primitives-squaresolid.png width=256px @see @ref squareWireframe(), @ref planeSolid(), @ref gradient2D() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D squareSolid(SquareTextureCoords textureCoords = SquareTextureCoords::DontGenerate); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData squareSolid(SquareTextureCoords textureCoords = SquareTextureCoords::DontGenerate); /** @brief Wireframe 2D square -2x2 square. Non-indexed @ref MeshPrimitive::LineLoop. +2x2 square. Non-indexed @ref MeshPrimitive::LineLoop with +@ref VertexFormat::Vector2 positions. The returned instance references data +stored in constant memory. @image html primitives-squarewireframe.png width=256px @see @ref squareSolid(), @ref planeWireframe() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData2D squareWireframe(); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData squareWireframe(); }} diff --git a/src/Magnum/Primitives/UVSphere.cpp b/src/Magnum/Primitives/UVSphere.cpp index 597310027d..b4434d2516 100644 --- a/src/Magnum/Primitives/UVSphere.cpp +++ b/src/Magnum/Primitives/UVSphere.cpp @@ -29,14 +29,14 @@ #include "Magnum/Math/Color.h" #include "Magnum/Primitives/Implementation/Spheroid.h" #include "Magnum/Primitives/Implementation/WireframeSpheroid.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { -Trade::MeshData3D uvSphereSolid(UnsignedInt rings, UnsignedInt segments, UVSphereTextureCoords textureCoords) { +Trade::MeshData uvSphereSolid(UnsignedInt rings, UnsignedInt segments, UVSphereTextureCoords textureCoords) { CORRADE_ASSERT(rings >= 2 && segments >= 3, "Primitives::uvSphereSolid(): at least two rings and three segments expected", - (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); Implementation::Spheroid sphere(segments, textureCoords == UVSphereTextureCoords::Generate ? Implementation::Spheroid::TextureCoords::Generate : @@ -62,10 +62,10 @@ Trade::MeshData3D uvSphereSolid(UnsignedInt rings, UnsignedInt segments, UVSpher return sphere.finalize(); } -Trade::MeshData3D uvSphereWireframe(const UnsignedInt rings, const UnsignedInt segments) { +Trade::MeshData uvSphereWireframe(const UnsignedInt rings, const UnsignedInt segments) { CORRADE_ASSERT(rings >= 2 && rings%2 == 0 && segments >= 4 && segments%4 == 0, "Primitives::uvSphereWireframe(): multiples of 2 rings and multiples of 4 segments expected", - (Trade::MeshData3D{MeshPrimitive::Triangles, {}, {}, {}, {}, {}, nullptr})); + (Trade::MeshData{MeshPrimitive::Triangles, 0})); Implementation::WireframeSpheroid sphere(segments/4); diff --git a/src/Magnum/Primitives/UVSphere.h b/src/Magnum/Primitives/UVSphere.h index 24c5959050..ccec5c2d71 100644 --- a/src/Magnum/Primitives/UVSphere.h +++ b/src/Magnum/Primitives/UVSphere.h @@ -52,15 +52,17 @@ enum class UVSphereTextureCoords: UnsignedByte { equal to @cpp 3 @ce. @param textureCoords Whether to generate texture coordinates -Sphere with radius @cpp 1.0f @ce. Indexed @ref MeshPrimitive::Triangles with -normals and optional 2D texture coordinates. If texture coordinates are +Sphere with radius @cpp 1.0f @ce. @ref MeshPrimitive::Triangles with +@ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 +positions, @ref VertexFormat::Vector3 normals and optional +@ref VertexFormat::Vector2 texture coordinates. If texture coordinates are generated, vertices of one segment are duplicated for texture wrapping. @image html primitives-uvspheresolid.png width=256px @see @ref icosphereSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D uvSphereSolid(UnsignedInt rings, UnsignedInt segments, UVSphereTextureCoords textureCoords = UVSphereTextureCoords::DontGenerate); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData uvSphereSolid(UnsignedInt rings, UnsignedInt segments, UVSphereTextureCoords textureCoords = UVSphereTextureCoords::DontGenerate); /** @brief Wireframe 3D UV sphere @@ -69,13 +71,15 @@ MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D uvSphereSolid(UnsignedInt rings, Unsi @param segments Number of (line) segments. Must be larger or equal to @cpp 4 @ce and multiple of @cpp 4 @ce. -Sphere with radius @cpp 1.0f @ce. Indexed @ref MeshPrimitive::Lines. +Sphere with radius @cpp 1.0f @ce. @ref MeshPrimitive::Lines with +@ref MeshIndexType::UnsignedInt indices and @ref VertexFormat::Vector3 +positions. @image html primitives-uvspherewireframe.png width=256px @see @ref icosphereSolid() */ -MAGNUM_PRIMITIVES_EXPORT Trade::MeshData3D uvSphereWireframe(UnsignedInt rings, UnsignedInt segments); +MAGNUM_PRIMITIVES_EXPORT Trade::MeshData uvSphereWireframe(UnsignedInt rings, UnsignedInt segments); }} diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index c710d4aa36..3409e5ddcc 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -312,7 +312,8 @@ Containers::Array MAGNUM_TRADE_EXPORT meshAttributeDataNonOwn Provides access to mesh vertex and index data, together with additional information such as primitive type. Populated instances of this class are -returned from @ref AbstractImporter::mesh(). +returned from @ref AbstractImporter::mesh() and from particular functions in +the @ref Primitives library. @section Trade-MeshData-usage Basic usage From a68946df5bd98d45d0484fe929ad05a7c15e63ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 18 Jan 2020 18:08:45 +0100 Subject: [PATCH 060/107] Primitives: port tests and related tools away from MeshDataXD. Improving the tests a bit for trivial primitives to ensure the attribute layouts are correct, not just counts. --- doc/generated/primitives.cpp | 204 +++++++++---------- doc/generated/shaders.cpp | 15 +- src/Magnum/Primitives/Test/AxisTest.cpp | 31 ++- src/Magnum/Primitives/Test/CapsuleTest.cpp | 49 +++-- src/Magnum/Primitives/Test/CircleTest.cpp | 57 +++--- src/Magnum/Primitives/Test/ConeTest.cpp | 71 ++++--- src/Magnum/Primitives/Test/CrosshairTest.cpp | 21 +- src/Magnum/Primitives/Test/CubeTest.cpp | 37 ++-- src/Magnum/Primitives/Test/CylinderTest.cpp | 71 ++++--- src/Magnum/Primitives/Test/GradientTest.cpp | 68 ++++--- src/Magnum/Primitives/Test/GridTest.cpp | 39 ++-- src/Magnum/Primitives/Test/IcosphereTest.cpp | 51 +++-- src/Magnum/Primitives/Test/LineTest.cpp | 21 +- src/Magnum/Primitives/Test/PlaneTest.cpp | 40 ++-- src/Magnum/Primitives/Test/SquareTest.cpp | 34 ++-- src/Magnum/Primitives/Test/UVSphereTest.cpp | 38 ++-- 16 files changed, 488 insertions(+), 359 deletions(-) diff --git a/doc/generated/primitives.cpp b/doc/generated/primitives.cpp index 300c4da294..4e0033362b 100644 --- a/doc/generated/primitives.cpp +++ b/doc/generated/primitives.cpp @@ -76,8 +76,7 @@ #include #include #include -#include -#include +#include #include using namespace Magnum; @@ -93,46 +92,46 @@ struct PrimitiveVisualizer: Platform::WindowlessApplication { int exec() override; - std::pair axis2D(); - std::pair axis3D(); - - std::pair capsule2DWireframe(); - std::pair circle2DWireframe(); - std::pair crosshair2D(); - std::pair line2D(); - std::pair squareWireframe(); - - std::pair capsule3DWireframe(); - std::pair circle3DWireframe(); - std::pair crosshair3D(); - std::pair coneWireframe(); - std::pair cubeWireframe(); - std::pair cylinderWireframe(); - std::pair grid3DWireframe(); - std::pair line3D(); - std::pair planeWireframe(); - std::pair uvSphereWireframe(); - - std::pair circle2DSolid(); - std::pair squareSolid(); - - std::pair capsule3DSolid(); - std::pair circle3DSolid(); - std::pair coneSolid(); - std::pair cubeSolid(); - std::pair cylinderSolid(); - std::pair grid3DSolid(); - std::pair icosphereSolid(); - std::pair planeSolid(); - std::pair uvSphereSolid(); - - std::pair gradient2D(); - std::pair gradient2DHorizontal(); - std::pair gradient2DVertical(); - - std::pair gradient3D(); - std::pair gradient3DHorizontal(); - std::pair gradient3DVertical(); + std::pair axis2D(); + std::pair axis3D(); + + std::pair capsule2DWireframe(); + std::pair circle2DWireframe(); + std::pair crosshair2D(); + std::pair line2D(); + std::pair squareWireframe(); + + std::pair capsule3DWireframe(); + std::pair circle3DWireframe(); + std::pair crosshair3D(); + std::pair coneWireframe(); + std::pair cubeWireframe(); + std::pair cylinderWireframe(); + std::pair grid3DWireframe(); + std::pair line3D(); + std::pair planeWireframe(); + std::pair uvSphereWireframe(); + + std::pair circle2DSolid(); + std::pair squareSolid(); + + std::pair capsule3DSolid(); + std::pair circle3DSolid(); + std::pair coneSolid(); + std::pair cubeSolid(); + std::pair cylinderSolid(); + std::pair grid3DSolid(); + std::pair icosphereSolid(); + std::pair planeSolid(); + std::pair uvSphereSolid(); + + std::pair gradient2D(); + std::pair gradient2DHorizontal(); + std::pair gradient2DVertical(); + + std::pair gradient3D(); + std::pair gradient3DHorizontal(); + std::pair gradient3DVertical(); }; namespace { @@ -191,7 +190,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data).draw(shader); @@ -210,7 +209,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data).draw(shader); @@ -235,7 +234,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data).draw(shader); @@ -265,7 +264,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data).draw(shader); @@ -299,18 +298,12 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); - /* TODO: use MeshTools::compile() and MeshVisualizer2D once it exists */ - GL::Buffer vertices; - vertices.setData(data->positions(0), GL::BufferUsage::StaticDraw); - GL::Mesh mesh; - mesh.addVertexBuffer(vertices, 0, Shaders::MeshVisualizer::Position{Shaders::MeshVisualizer::Position::Components::Two}) - .setCount(data->positions(0).size()) - .setPrimitive(data->primitive()); - - mesh.draw(flat) + /* TODO: use MeshVisualizer2D once it exists */ + MeshTools::compile(*data) + .draw(flat) .draw(wireframe2D); GL::AbstractFramebuffer::blit(multisampleFramebuffer, framebuffer, framebuffer.viewport(), GL::FramebufferBlit::Color); @@ -349,7 +342,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data) @@ -372,7 +365,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data) @@ -395,7 +388,7 @@ int PrimitiveVisualizer::exec() { multisampleFramebuffer.clear(GL::FramebufferClear::Color|GL::FramebufferClear::Depth); std::string filename; - Containers::Optional data; + Containers::Optional data; std::tie(data, filename) = (this->*fun)(); MeshTools::compile(*data) @@ -411,11 +404,11 @@ int PrimitiveVisualizer::exec() { return 0; } -std::pair PrimitiveVisualizer::axis2D() { +std::pair PrimitiveVisualizer::axis2D() { return {Primitives::axis2D(), "axis2d.png"}; } -std::pair PrimitiveVisualizer::gradient2D() { +std::pair PrimitiveVisualizer::gradient2D() { return {Primitives::gradient2D({1.0f, -2.0f}, 0x2f83cc_srgbf, {-1.0f, 2.0f}, 0x3bd267_srgbf), "gradient2d.png"}; } @@ -426,142 +419,147 @@ namespace { const Color3 Gradient80Percent = Math::lerp(0x2f83cc_srgbf, 0x3bd267_srgbf, 0.8f); } -std::pair PrimitiveVisualizer::gradient2DHorizontal() { +std::pair PrimitiveVisualizer::gradient2DHorizontal() { return {Primitives::gradient2DHorizontal(Gradient20Percent, Gradient80Percent), "gradient2dhorizontal.png"}; } -std::pair PrimitiveVisualizer::gradient2DVertical() { +std::pair PrimitiveVisualizer::gradient2DVertical() { /* End colors are 20%/80% blends of the above to match the range */ return {Primitives::gradient2DVertical(Gradient20Percent, Gradient80Percent), "gradient2dvertical.png"}; } -std::pair PrimitiveVisualizer::axis3D() { +std::pair PrimitiveVisualizer::axis3D() { return {Primitives::axis3D(), "axis3d.png"}; } -std::pair PrimitiveVisualizer::gradient3D() { +std::pair PrimitiveVisualizer::gradient3D() { return {Primitives::gradient3D({1.0f, -2.0f, -1.5f}, 0x2f83cc_srgbf, {-1.0f, 2.0f, -1.5f}, 0x3bd267_srgbf), "gradient3d.png"}; } -std::pair PrimitiveVisualizer::gradient3DHorizontal() { +std::pair PrimitiveVisualizer::gradient3DHorizontal() { return {Primitives::gradient3DHorizontal(Gradient20Percent, Gradient80Percent), "gradient3dhorizontal.png"}; } -std::pair PrimitiveVisualizer::gradient3DVertical() { +std::pair PrimitiveVisualizer::gradient3DVertical() { return {Primitives::gradient3DVertical(Gradient20Percent, Gradient80Percent), "gradient3dvertical.png"}; } -std::pair PrimitiveVisualizer::capsule2DWireframe() { - Trade::MeshData2D capsule = Primitives::capsule2DWireframe(8, 1, 0.75f); - MeshTools::transformPointsInPlace(Matrix3::scaling(Vector2{0.75f}), capsule.positions(0)); +std::pair PrimitiveVisualizer::capsule2DWireframe() { + Trade::MeshData capsule = Primitives::capsule2DWireframe(8, 1, 0.75f); + MeshTools::transformPointsInPlace(Matrix3::scaling(Vector2{0.75f}), + capsule.mutableAttribute(Trade::MeshAttribute::Position)); return {std::move(capsule), "capsule2dwireframe.png"}; } -std::pair PrimitiveVisualizer::circle2DWireframe() { +std::pair PrimitiveVisualizer::circle2DWireframe() { return {Primitives::circle2DWireframe(32), "circle2dwireframe.png"}; } -std::pair PrimitiveVisualizer::crosshair2D() { +std::pair PrimitiveVisualizer::crosshair2D() { return {Primitives::crosshair2D(), "crosshair2d.png"}; } -std::pair PrimitiveVisualizer::line2D() { - Trade::MeshData2D line = Primitives::line2D(); - MeshTools::transformPointsInPlace(Matrix3::translation(Vector2::xAxis(-1.0f))*Matrix3::scaling(Vector2::xScale(2.0f)), line.positions(0)); +std::pair PrimitiveVisualizer::line2D() { + Trade::MeshData line = Primitives::line2D(); + MeshTools::transformPointsInPlace(Matrix3::translation(Vector2::xAxis(-1.0f))*Matrix3::scaling(Vector2::xScale(2.0f)), + line.mutableAttribute(Trade::MeshAttribute::Position)); return {std::move(line), "line2d.png"}; } -std::pair PrimitiveVisualizer::squareWireframe() { +std::pair PrimitiveVisualizer::squareWireframe() { return {Primitives::squareWireframe(), "squarewireframe.png"}; } -std::pair PrimitiveVisualizer::capsule3DWireframe() { - Trade::MeshData3D capsule = Primitives::capsule3DWireframe(8, 1, 16, 1.0f); - MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{0.75f}), capsule.positions(0)); +std::pair PrimitiveVisualizer::capsule3DWireframe() { + Trade::MeshData capsule = Primitives::capsule3DWireframe(8, 1, 16, 1.0f); + MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{0.75f}), + capsule.mutableAttribute(Trade::MeshAttribute::Position)); return {std::move(capsule), "capsule3dwireframe.png"}; } -std::pair PrimitiveVisualizer::circle3DWireframe() { +std::pair PrimitiveVisualizer::circle3DWireframe() { return {Primitives::circle3DWireframe(32), "circle3dwireframe.png"}; } -std::pair PrimitiveVisualizer::crosshair3D() { +std::pair PrimitiveVisualizer::crosshair3D() { return {Primitives::crosshair3D(), "crosshair3d.png"}; } -std::pair PrimitiveVisualizer::coneWireframe() { +std::pair PrimitiveVisualizer::coneWireframe() { return {Primitives::coneWireframe(32, 1.25f), "conewireframe.png"}; } -std::pair PrimitiveVisualizer::cubeWireframe() { +std::pair PrimitiveVisualizer::cubeWireframe() { return {Primitives::cubeWireframe(), "cubewireframe.png"}; } -std::pair PrimitiveVisualizer::cylinderWireframe() { +std::pair PrimitiveVisualizer::cylinderWireframe() { return {Primitives::cylinderWireframe(1, 32, 1.0f), "cylinderwireframe.png"}; } -std::pair PrimitiveVisualizer::grid3DWireframe() { +std::pair PrimitiveVisualizer::grid3DWireframe() { return {Primitives::grid3DWireframe({5, 3}), "grid3dwireframe.png"}; } -std::pair PrimitiveVisualizer::line3D() { - Trade::MeshData3D line = Primitives::line3D(); - MeshTools::transformPointsInPlace(Matrix4::translation(Vector3::xAxis(-1.0f))*Matrix4::scaling(Vector3::xScale(2.0f)), line.positions(0)); +std::pair PrimitiveVisualizer::line3D() { + Trade::MeshData line = Primitives::line3D(); + MeshTools::transformPointsInPlace(Matrix4::translation(Vector3::xAxis(-1.0f))*Matrix4::scaling(Vector3::xScale(2.0f)), + line.mutableAttribute(Trade::MeshAttribute::Position)); return {std::move(line), "line3d.png"}; } -std::pair PrimitiveVisualizer::planeWireframe() { +std::pair PrimitiveVisualizer::planeWireframe() { return {Primitives::planeWireframe(), "planewireframe.png"}; } -std::pair PrimitiveVisualizer::uvSphereWireframe() { +std::pair PrimitiveVisualizer::uvSphereWireframe() { return {Primitives::uvSphereWireframe(16, 32), "uvspherewireframe.png"}; } -std::pair PrimitiveVisualizer::circle2DSolid() { +std::pair PrimitiveVisualizer::circle2DSolid() { return {Primitives::circle2DSolid(16), "circle2dsolid.png"}; } -std::pair PrimitiveVisualizer::squareSolid() { +std::pair PrimitiveVisualizer::squareSolid() { return {Primitives::squareSolid(), "squaresolid.png"}; } -std::pair PrimitiveVisualizer::capsule3DSolid() { - Trade::MeshData3D capsule = Primitives::capsule3DSolid(4, 1, 12, 0.75f); - MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{0.75f}), capsule.positions(0)); +std::pair PrimitiveVisualizer::capsule3DSolid() { + Trade::MeshData capsule = Primitives::capsule3DSolid(4, 1, 12, 0.75f); + MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{0.75f}), + capsule.mutableAttribute(Trade::MeshAttribute::Position)); return {std::move(capsule), "capsule3dsolid.png"}; } -std::pair PrimitiveVisualizer::circle3DSolid() { +std::pair PrimitiveVisualizer::circle3DSolid() { return {Primitives::circle3DSolid(16), "circle3dsolid.png"}; } -std::pair PrimitiveVisualizer::coneSolid() { +std::pair PrimitiveVisualizer::coneSolid() { return {Primitives::coneSolid(1, 12, 1.25f, Primitives::ConeFlag::CapEnd), "conesolid.png"}; } -std::pair PrimitiveVisualizer::cubeSolid() { +std::pair PrimitiveVisualizer::cubeSolid() { return {Primitives::cubeSolid(), "cubesolid.png"}; } -std::pair PrimitiveVisualizer::cylinderSolid() { +std::pair PrimitiveVisualizer::cylinderSolid() { return {Primitives::cylinderSolid(1, 12, 1.0f, Primitives::CylinderFlag::CapEnds), "cylindersolid.png"}; } -std::pair PrimitiveVisualizer::grid3DSolid() { +std::pair PrimitiveVisualizer::grid3DSolid() { return {Primitives::grid3DSolid({5, 3}), "grid3dsolid.png"}; } -std::pair PrimitiveVisualizer::icosphereSolid() { +std::pair PrimitiveVisualizer::icosphereSolid() { return {Primitives::icosphereSolid(1), "icospheresolid.png"}; } -std::pair PrimitiveVisualizer::planeSolid() { +std::pair PrimitiveVisualizer::planeSolid() { return {Primitives::planeSolid(), "planesolid.png"}; } -std::pair PrimitiveVisualizer::uvSphereSolid() { +std::pair PrimitiveVisualizer::uvSphereSolid() { return {Primitives::uvSphereSolid(8, 16), "uvspheresolid.png"}; } diff --git a/doc/generated/shaders.cpp b/doc/generated/shaders.cpp index 100e372434..cf488d12dc 100644 --- a/doc/generated/shaders.cpp +++ b/doc/generated/shaders.cpp @@ -66,8 +66,7 @@ #include #include #include -#include -#include +#include #include using namespace Magnum; @@ -192,22 +191,22 @@ std::string ShaderVisualizer::flat() { } std::string ShaderVisualizer::vertexColor() { - Trade::MeshData3D sphere = Primitives::uvSphereSolid(32, 64); + Trade::MeshData sphere = Primitives::uvSphereSolid(32, 64); /* Color vertices nearest to given position */ auto target = Vector3{2.0f, 2.0f, 7.0f}.normalized(); std::vector colors; - colors.reserve(sphere.positions(0).size()); - for(Vector3 position: sphere.positions(0)) + colors.reserve(sphere.vertexCount()); + for(Vector3 position: sphere.attribute(Trade::MeshAttribute::Position)) colors.push_back(Color3::fromHsv({Math::lerp(240.0_degf, 420.0_degf, Math::max(1.0f - (position - target).length(), 0.0f)), 0.85f, 0.666f})); GL::Buffer vertices, indices; - vertices.setData(MeshTools::interleave(sphere.positions(0), colors), GL::BufferUsage::StaticDraw); - indices.setData(sphere.indices(), GL::BufferUsage::StaticDraw); + vertices.setData(MeshTools::interleave(sphere.attribute(Trade::MeshAttribute::Position), colors), GL::BufferUsage::StaticDraw); + indices.setData(sphere.indices(), GL::BufferUsage::StaticDraw); GL::Mesh mesh; mesh.setPrimitive(GL::MeshPrimitive::Triangles) - .setCount(sphere.indices().size()) + .setCount(sphere.indexCount()) .addVertexBuffer(vertices, 0, Shaders::VertexColor3D::Position{}, Shaders::VertexColor3D::Color3{}) diff --git a/src/Magnum/Primitives/Test/AxisTest.cpp b/src/Magnum/Primitives/Test/AxisTest.cpp index f1f93ac364..56ac3f5a6e 100644 --- a/src/Magnum/Primitives/Test/AxisTest.cpp +++ b/src/Magnum/Primitives/Test/AxisTest.cpp @@ -28,8 +28,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" #include "Magnum/Primitives/Axis.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -46,21 +45,33 @@ AxisTest::AxisTest() { } void AxisTest::twoDimensions() { - Trade::MeshData2D axis = Primitives::axis2D(); + Trade::MeshData axis = Primitives::axis2D(); CORRADE_COMPARE(axis.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(axis.indices().size(), 12); - CORRADE_COMPARE(axis.positions(0).size(), 8); - CORRADE_COMPARE(axis.colors(0).size(), 8); + CORRADE_VERIFY(axis.isIndexed()); + CORRADE_COMPARE(axis.indexCount(), 12); + CORRADE_COMPARE(axis.vertexCount(), 8); + CORRADE_COMPARE(axis.attributeCount(), 2); + CORRADE_COMPARE(axis.indices()[5], 3); + CORRADE_COMPARE(axis.attribute(Trade::MeshAttribute::Position)[3], + (Vector2{0.9f, -0.1f})); + CORRADE_COMPARE(axis.attribute(Trade::MeshAttribute::Color)[6], + (Color3{0.0f, 1.0f, 0.0f})); } void AxisTest::threeDimensions() { - Trade::MeshData3D axis = Primitives::axis3D(); + Trade::MeshData axis = Primitives::axis3D(); CORRADE_COMPARE(axis.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(axis.indices().size(), 18); - CORRADE_COMPARE(axis.positions(0).size(), 12); - CORRADE_COMPARE(axis.colors(0).size(), 12); + CORRADE_VERIFY(axis.isIndexed()); + CORRADE_COMPARE(axis.indexCount(), 18); + CORRADE_COMPARE(axis.vertexCount(), 12); + CORRADE_COMPARE(axis.attributeCount(), 2); + CORRADE_COMPARE(axis.indices()[12], 8); + CORRADE_COMPARE(axis.attribute(Trade::MeshAttribute::Position)[6], + (Vector3{0.1f, 0.9f, 0.0f})); + CORRADE_COMPARE(axis.attribute(Trade::MeshAttribute::Color)[4], + (Color3{0.0f, 1.0f, 0.0f})); } }}}} diff --git a/src/Magnum/Primitives/Test/CapsuleTest.cpp b/src/Magnum/Primitives/Test/CapsuleTest.cpp index 4ae068aaab..bcb3578859 100644 --- a/src/Magnum/Primitives/Test/CapsuleTest.cpp +++ b/src/Magnum/Primitives/Test/CapsuleTest.cpp @@ -27,8 +27,7 @@ #include #include "Magnum/Math/Vector3.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Primitives/Capsule.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -51,9 +50,13 @@ CapsuleTest::CapsuleTest() { } void CapsuleTest::wireframe2D() { - Trade::MeshData2D capsule = capsule2DWireframe(2, 4, 0.5f); + Trade::MeshData capsule = capsule2DWireframe(2, 4, 0.5f); - CORRADE_COMPARE_AS(capsule.positions(0), (std::vector{ + CORRADE_COMPARE(capsule.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(capsule.isIndexed()); + CORRADE_COMPARE(capsule.attributeCount(), 1); + + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f}, {-0.707107f, -1.20711f}, @@ -80,7 +83,7 @@ void CapsuleTest::wireframe2D() { {0.0f, 1.5f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(capsule.indices(), (std::vector{ + CORRADE_COMPARE_AS(capsule.indices(), Containers::arrayView({ 0, 1, 0, 2, 1, 3, 2, 4, @@ -95,9 +98,13 @@ void CapsuleTest::wireframe2D() { } void CapsuleTest::solid3DWithoutTextureCoords() { - Trade::MeshData3D capsule = capsule3DSolid(2, 4, 3, 0.5f); + Trade::MeshData capsule = capsule3DSolid(2, 4, 3, 0.5f); + + CORRADE_COMPARE(capsule.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(capsule.isIndexed()); + CORRADE_COMPARE(capsule.attributeCount(), 2); - CORRADE_COMPARE_AS(capsule.positions(0), (std::vector{ + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 0.0f}, {0.0f, -1.20711f, 0.707107f}, @@ -131,7 +138,7 @@ void CapsuleTest::solid3DWithoutTextureCoords() { {0.0f, 1.5f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(capsule.normals(0), (std::vector{ + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, {0.0f, -0.707107f, 0.707107f}, @@ -165,7 +172,7 @@ void CapsuleTest::solid3DWithoutTextureCoords() { {0.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(capsule.indices(), (std::vector{ + CORRADE_COMPARE_AS(capsule.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 1, 3, 1, 2, 5, 1, 5, 4, 2, 3, 6, 2, 6, 5, 3, 1, 4, 3, 4, 6, 4, 5, 8, 4, 8, 7, 5, 6, 9, 5, 9, 8, 6, 4, 7, 6, 7, 9, @@ -178,9 +185,13 @@ void CapsuleTest::solid3DWithoutTextureCoords() { } void CapsuleTest::solid3DWithTextureCoords() { - Trade::MeshData3D capsule = capsule3DSolid(2, 2, 3, 0.5f, CapsuleTextureCoords::Generate); + Trade::MeshData capsule = capsule3DSolid(2, 2, 3, 0.5f, CapsuleTextureCoords::Generate); + + CORRADE_COMPARE(capsule.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(capsule.isIndexed()); + CORRADE_COMPARE(capsule.attributeCount(), 3); - CORRADE_COMPARE_AS(capsule.positions(0), (std::vector{ + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 0.0f}, {0.0f, -1.20711f, 0.707107f}, @@ -211,7 +222,7 @@ void CapsuleTest::solid3DWithTextureCoords() { {0.0f, 1.5f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(capsule.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.5f, 0.0f}, {0.0f, 0.166667f}, @@ -242,7 +253,7 @@ void CapsuleTest::solid3DWithTextureCoords() { {0.5f, 1.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(capsule.indices(), (std::vector{ + CORRADE_COMPARE_AS(capsule.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 4, 3, 1, 2, 6, 1, 6, 5, 2, 3, 7, 2, 7, 6, 3, 4, 8, 3, 8, 7, 5, 6, 10, 5, 10, 9, 6, 7, 11, 6, 11, 10, 7, 8, 12, 7, 12, 11, @@ -253,9 +264,13 @@ void CapsuleTest::solid3DWithTextureCoords() { } void CapsuleTest::wireframe3D() { - Trade::MeshData3D capsule = capsule3DWireframe(2, 2, 8, 0.5f); + Trade::MeshData capsule = capsule3DWireframe(2, 2, 8, 0.5f); - CORRADE_COMPARE_AS(capsule.positions(0), (std::vector{ + CORRADE_COMPARE(capsule.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(capsule.isIndexed()); + CORRADE_COMPARE(capsule.attributeCount(), 1); + + CORRADE_COMPARE_AS(capsule.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 0.0f}, {0.0f, -1.20711f, 0.707107f}, @@ -298,9 +313,7 @@ void CapsuleTest::wireframe3D() { {0.0f, 1.5f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(capsule.normalArrayCount(), 0); - - CORRADE_COMPARE_AS(capsule.indices(), (std::vector{ + CORRADE_COMPARE_AS(capsule.indices(), Containers::arrayView({ 0, 1, 0, 2, 0, 3, 0, 4, 1, 5, 2, 6, 3, 7, 4, 8, 5, 9, 6, 10, 7, 11, 8, 12, diff --git a/src/Magnum/Primitives/Test/CircleTest.cpp b/src/Magnum/Primitives/Test/CircleTest.cpp index 92dda4a6c5..df97ad2940 100644 --- a/src/Magnum/Primitives/Test/CircleTest.cpp +++ b/src/Magnum/Primitives/Test/CircleTest.cpp @@ -29,8 +29,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Circle.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -59,11 +58,12 @@ CircleTest::CircleTest() { } void CircleTest::solid2D() { - Trade::MeshData2D circle = Primitives::circle2DSolid(8); + Trade::MeshData circle = Primitives::circle2DSolid(8); - CORRADE_VERIFY(!circle.isIndexed()); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::TriangleFan); - CORRADE_COMPARE_AS(circle.positions(0), (std::vector{ + CORRADE_VERIFY(!circle.isIndexed()); + CORRADE_COMPARE(circle.attributeCount(), 1); + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 0.0f, 0.0f}, { 1.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, { 0.0f, 1.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, @@ -71,15 +71,15 @@ void CircleTest::solid2D() { { 0.0f, -1.0f}, { Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f}, { 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(circle.textureCoords2DArrayCount(), 0); } void CircleTest::solid2DTextureCoords() { - Trade::MeshData2D circle = Primitives::circle2DSolid(8, Primitives::CircleTextureCoords::Generate); + Trade::MeshData circle = Primitives::circle2DSolid(8, Primitives::CircleTextureCoords::Generate); - CORRADE_VERIFY(!circle.isIndexed()); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::TriangleFan); - CORRADE_COMPARE_AS(circle.positions(0), (std::vector{ + CORRADE_VERIFY(!circle.isIndexed()); + CORRADE_COMPARE(circle.attributeCount(), 2); + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 0.0f, 0.0f}, { 1.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, { 0.0f, 1.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, @@ -87,8 +87,7 @@ void CircleTest::solid2DTextureCoords() { { 0.0f, -1.0f}, { Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f}, { 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(circle.textureCoords2DArrayCount(), 1); - CORRADE_COMPARE_AS(circle.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.5f, 0.5f}, {1.0f, 0.5f}, {0.5f + Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, {0.5f, 1.0f}, {0.5f - Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, @@ -99,11 +98,12 @@ void CircleTest::solid2DTextureCoords() { } void CircleTest::solid3D() { - Trade::MeshData3D circle = Primitives::circle3DSolid(8); + Trade::MeshData circle = Primitives::circle3DSolid(8); - CORRADE_VERIFY(!circle.isIndexed()); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::TriangleFan); - CORRADE_COMPARE_AS(circle.positions(0), (std::vector{ + CORRADE_VERIFY(!circle.isIndexed()); + CORRADE_COMPARE(circle.attributeCount(), 2); + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 0.0f, 0.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, { 0.0f, 1.0f, 0.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, @@ -111,7 +111,7 @@ void CircleTest::solid3D() { { 0.0f, -1.0f, 0.0f}, { Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f, 0.0f}, { 1.0f, 0.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(circle.normals(0), (std::vector{ + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}, @@ -123,15 +123,15 @@ void CircleTest::solid3D() { { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(circle.textureCoords2DArrayCount(), 0); } void CircleTest::solid3DTextureCoords() { - Trade::MeshData3D circle = Primitives::circle3DSolid(8, Primitives::CircleTextureCoords::Generate); + Trade::MeshData circle = Primitives::circle3DSolid(8, Primitives::CircleTextureCoords::Generate); - CORRADE_VERIFY(!circle.isIndexed()); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::TriangleFan); - CORRADE_COMPARE_AS(circle.positions(0), (std::vector{ + CORRADE_VERIFY(!circle.isIndexed()); + CORRADE_COMPARE(circle.attributeCount(), 3); + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 0.0f, 0.0f, 0.0f}, { 1.0f, 0.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, { 0.0f, 1.0f, 0.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, @@ -139,7 +139,7 @@ void CircleTest::solid3DTextureCoords() { { 0.0f, -1.0f, 0.0f}, { Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f, 0.0f}, { 1.0f, 0.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(circle.normals(0), (std::vector{ + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f}, @@ -151,8 +151,7 @@ void CircleTest::solid3DTextureCoords() { { 0.0f, 0.0f, 1.0f}, { 0.0f, 0.0f, 1.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(circle.textureCoords2DArrayCount(), 1); - CORRADE_COMPARE_AS(circle.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.5f, 0.5f}, {1.0f, 0.5f}, {0.5f + Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, {0.5f, 1.0f}, {0.5f - Constants::sqrt2()/4.0f, 0.5f + Constants::sqrt2()/4.0f}, @@ -163,11 +162,12 @@ void CircleTest::solid3DTextureCoords() { } void CircleTest::wireframe2D() { - Trade::MeshData2D circle = Primitives::circle2DWireframe(8); + Trade::MeshData circle = Primitives::circle2DWireframe(8); - CORRADE_VERIFY(!circle.isIndexed()); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::LineLoop); - CORRADE_COMPARE_AS(circle.positions(0), (std::vector{ + CORRADE_VERIFY(!circle.isIndexed()); + CORRADE_COMPARE(circle.attributeCount(), 1); + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 1.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, { 0.0f, 1.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f}, {-1.0f, 0.0f}, {-Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f}, @@ -176,11 +176,12 @@ void CircleTest::wireframe2D() { } void CircleTest::wireframe3D() { - Trade::MeshData3D circle = Primitives::circle3DWireframe(8); + Trade::MeshData circle = Primitives::circle3DWireframe(8); - CORRADE_VERIFY(!circle.isIndexed()); CORRADE_COMPARE(circle.primitive(), MeshPrimitive::LineLoop); - CORRADE_COMPARE_AS(circle.positions(0), (std::vector{ + CORRADE_VERIFY(!circle.isIndexed()); + CORRADE_COMPARE(circle.attributeCount(), 1); + CORRADE_COMPARE_AS(circle.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 1.0f, 0.0f, 0.0f}, { Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, { 0.0f, 1.0f, 0.0f}, {-Constants::sqrt2()/2.0f, Constants::sqrt2()/2.0f, 0.0f}, {-1.0f, 0.0f, 0.0f}, {-Constants::sqrt2()/2.0f, -Constants::sqrt2()/2.0f, 0.0f}, diff --git a/src/Magnum/Primitives/Test/ConeTest.cpp b/src/Magnum/Primitives/Test/ConeTest.cpp index bc3e848065..f907f62020 100644 --- a/src/Magnum/Primitives/Test/ConeTest.cpp +++ b/src/Magnum/Primitives/Test/ConeTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Cone.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -51,9 +51,13 @@ ConeTest::ConeTest() { } void ConeTest::solidWithoutAnything() { - Trade::MeshData3D cone = coneSolid(2, 3, 1.0f); + Trade::MeshData cone = coneSolid(2, 3, 1.0f); - CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + CORRADE_COMPARE(cone.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cone.isIndexed()); + CORRADE_COMPARE(cone.attributeCount(), 2); + + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 1.0f}, /* 0 */ {0.866025f, -1.0f, -0.5f}, /* 1 */ {-0.866025f, -1.0f, -0.5f}, /* 2 */ @@ -67,7 +71,7 @@ void ConeTest::solidWithoutAnything() { {0.0f, 1.0f, 0.0f} /* 8 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, 0.447214f, 0.894427f}, /* 0 */ {0.774597f, 0.447214f, -0.447214f}, /* 1 */ {-0.774597f, 0.447214f, -0.447214f}, /* 2 */ @@ -81,20 +85,22 @@ void ConeTest::solidWithoutAnything() { {-0.774597f, 0.447214f, -0.447214f} /* 8 */ }), TestSuite::Compare::Container); - CORRADE_VERIFY(!cone.hasTextureCoords2D()); - - CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + CORRADE_COMPARE_AS(cone.indices(), Containers::arrayView({ 0, 1, 4, 0, 4, 3, 1, 2, 5, 1, 5, 4, 2, 0, 3, 2, 3, 5, 3, 4, 7, 3, 7, 6, 4, 5, 8, 4, 8, 7, 5, 3, 6, 5, 6, 8 }), TestSuite::Compare::Container); } void ConeTest::solidWithCaps() { - Trade::MeshData3D cone = coneSolid(2, 3, 1.0f, ConeFlag::CapEnd); + Trade::MeshData cone = coneSolid(2, 3, 1.0f, ConeFlag::CapEnd); + + CORRADE_COMPARE(cone.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cone.isIndexed()); + CORRADE_COMPARE(cone.attributeCount(), 2); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ - CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ {0.0f, -1.0f, 1.0f}, /* 1 */ @@ -114,7 +120,7 @@ void ConeTest::solidWithCaps() { {0.0f, 1.0f, 0.0f}, /* 12 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ {0.0f, -1.0f, 0.0f}, /* 1 */ @@ -134,11 +140,9 @@ void ConeTest::solidWithCaps() { {-0.774597f, 0.447214f, -0.447214f}, /* 12 */ }), TestSuite::Compare::Container); - CORRADE_VERIFY(!cone.hasTextureCoords2D()); - /* Faces of the caps and sides do not share any vertices due to different normals */ - CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + CORRADE_COMPARE_AS(cone.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 1, 3, 4, 5, 8, 4, 8, 7, 5, 6, 9, 5, 9, 8, 6, 4, 7, 6, 7, 9, 7, 8, 11, 7, 11, 10, 8, 9, 12, 8, 12, 11, 9, 7, 10, 9, 10, 12 @@ -146,11 +150,15 @@ void ConeTest::solidWithCaps() { } void ConeTest::solidWithTextureCoords() { - Trade::MeshData3D cone = coneSolid(2, 3, 1.0f, ConeFlag::GenerateTextureCoords); + Trade::MeshData cone = coneSolid(2, 3, 1.0f, ConeFlag::GenerateTextureCoords); + + CORRADE_COMPARE(cone.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cone.isIndexed()); + CORRADE_COMPARE(cone.attributeCount(), 3); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ - CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 1.0f}, /* 0 */ {0.866025f, -1.0f, -0.5f}, /* 1 */ {-0.866025f, -1.0f, -0.5f}, /* 2 */ @@ -167,7 +175,7 @@ void ConeTest::solidWithTextureCoords() { {0.0f, 1.0f, 0.0f} /* 11 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, 0.447214f, 0.894427f}, /* 0 */ {0.774597f, 0.447214f, -0.447214f}, /* 1 */ {-0.774597f, 0.447214f, -0.447214f}, /* 2 */ @@ -184,7 +192,7 @@ void ConeTest::solidWithTextureCoords() { {0.0f, 0.447214f, 0.894427f} /* 11 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.0f, 0.0f}, /* 0 */ {0.333333f, 0.0f}, /* 1 */ {0.666667f, 0.0f}, /* 2 */ @@ -202,18 +210,22 @@ void ConeTest::solidWithTextureCoords() { }), TestSuite::Compare::Container); /* Each ring has an extra vertex for texture coords */ - CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + CORRADE_COMPARE_AS(cone.indices(), Containers::arrayView({ 0, 1, 5, 0, 5, 4, 1, 2, 6, 1, 6, 5, 2, 3, 7, 2, 7, 6, 4, 5, 9, 4, 9, 8, 5, 6, 10, 5, 10, 9, 6, 7, 11, 6, 11, 10 }), TestSuite::Compare::Container); } void ConeTest::solidWithTextureCoordsAndCaps() { - Trade::MeshData3D cone = coneSolid(2, 3, 1.0f, ConeFlag::GenerateTextureCoords|ConeFlag::CapEnd); + Trade::MeshData cone = coneSolid(2, 3, 1.0f, ConeFlag::GenerateTextureCoords|ConeFlag::CapEnd); + + CORRADE_COMPARE(cone.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cone.isIndexed()); + CORRADE_COMPARE(cone.attributeCount(), 3); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ - CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ {0.0f, -1.0f, 1.0f}, /* 1 */ @@ -237,7 +249,7 @@ void ConeTest::solidWithTextureCoordsAndCaps() { {0.0f, 1.0f, 0.0f} /* 16 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ {0.0f, -1.0f, 0.0f}, /* 1 */ @@ -261,7 +273,7 @@ void ConeTest::solidWithTextureCoordsAndCaps() { {0.0f, 0.447214f, 0.894427f} /* 16 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cone.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.5f, 0.0f}, /* 0 */ {0.0f, 0.333333f}, /* 1 */ @@ -287,7 +299,7 @@ void ConeTest::solidWithTextureCoordsAndCaps() { /* Faces of the caps and sides do not share any vertices due to different normals, each ring has an extra vertex for texture coords */ - CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + CORRADE_COMPARE_AS(cone.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 4, 3, 5, 6, 10, 5, 10, 9, 6, 7, 11, 6, 11, 10, 7, 8, 12, 7, 12, 11, 9, 10, 14, 9, 14, 13, 10, 11, 15, 10, 15, 14, 11, 12, 16, 11, 16, 15 @@ -295,9 +307,13 @@ void ConeTest::solidWithTextureCoordsAndCaps() { } void ConeTest::wireframe() { - Trade::MeshData3D cone = coneWireframe(8, 1.5f); + Trade::MeshData cone = coneWireframe(8, 1.5f); - CORRADE_COMPARE_AS(cone.positions(0), (std::vector{ + CORRADE_COMPARE(cone.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(cone.isIndexed()); + CORRADE_COMPARE(cone.attributeCount(), 1); + + CORRADE_COMPARE_AS(cone.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 1.0f}, /* 0 */ {1.0f, -1.5f, 0.0f}, /* 1 */ {0.0f, -1.5f, -1.0f}, /* 2 */ @@ -310,10 +326,7 @@ void ConeTest::wireframe() { {0.0f, 1.5f, 0.0f} /* 8 */ }), TestSuite::Compare::Container); - CORRADE_VERIFY(!cone.hasNormals()); - CORRADE_VERIFY(!cone.hasTextureCoords2D()); - - CORRADE_COMPARE_AS(cone.indices(), (std::vector{ + CORRADE_COMPARE_AS(cone.indices(), Containers::arrayView({ 0, 4, 1, 5, 2, 6, 3, 7, 4, 1, 5, 2, 6, 3, 7, 0, diff --git a/src/Magnum/Primitives/Test/CrosshairTest.cpp b/src/Magnum/Primitives/Test/CrosshairTest.cpp index 8b2474c5a1..fe4ee73cc1 100644 --- a/src/Magnum/Primitives/Test/CrosshairTest.cpp +++ b/src/Magnum/Primitives/Test/CrosshairTest.cpp @@ -28,8 +28,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Crosshair.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -46,19 +45,25 @@ CrosshairTest::CrosshairTest() { } void CrosshairTest::twoDimensions() { - Trade::MeshData2D crosshair = Primitives::crosshair2D(); + Trade::MeshData crosshair = Primitives::crosshair2D(); - CORRADE_VERIFY(!crosshair.isIndexed()); CORRADE_COMPARE(crosshair.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(crosshair.positions(0).size(), 4); + CORRADE_VERIFY(!crosshair.isIndexed()); + CORRADE_COMPARE(crosshair.vertexCount(), 4); + CORRADE_COMPARE(crosshair.attributeCount(), 1); + CORRADE_COMPARE(crosshair.attribute(Trade::MeshAttribute::Position)[3], + (Vector2{0.0f, 1.0f})); } void CrosshairTest::threeDimensions() { - Trade::MeshData3D crosshair = Primitives::crosshair3D(); + Trade::MeshData crosshair = Primitives::crosshair3D(); - CORRADE_VERIFY(!crosshair.isIndexed()); CORRADE_COMPARE(crosshair.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(crosshair.positions(0).size(), 6); + CORRADE_VERIFY(!crosshair.isIndexed()); + CORRADE_COMPARE(crosshair.vertexCount(), 6); + CORRADE_COMPARE(crosshair.attributeCount(), 1); + CORRADE_COMPARE(crosshair.attribute(Trade::MeshAttribute::Position)[4], + (Vector3{ 0.0f, 0.0f, -1.0f})); } }}}} diff --git a/src/Magnum/Primitives/Test/CubeTest.cpp b/src/Magnum/Primitives/Test/CubeTest.cpp index 5e4398ea50..3c0c1ad140 100644 --- a/src/Magnum/Primitives/Test/CubeTest.cpp +++ b/src/Magnum/Primitives/Test/CubeTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Cube.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -47,29 +47,42 @@ CubeTest::CubeTest() { } void CubeTest::solid() { - Trade::MeshData3D cube = Primitives::cubeSolid(); + Trade::MeshData cube = Primitives::cubeSolid(); CORRADE_COMPARE(cube.primitive(), MeshPrimitive::Triangles); - CORRADE_COMPARE(cube.indices().size(), 36); - CORRADE_COMPARE(cube.positions(0).size(), 24); - CORRADE_COMPARE(cube.normals(0).size(), 24); + CORRADE_VERIFY(cube.isIndexed()); + CORRADE_COMPARE(cube.indexCount(), 36); + CORRADE_COMPARE(cube.vertexCount(), 24); + CORRADE_COMPARE(cube.attributeCount(), 2); + CORRADE_COMPARE(cube.indices()[17], 11); + CORRADE_COMPARE(cube.attribute(Trade::MeshAttribute::Position)[4], + (Vector3{1.0f, -1.0f, 1.0f})); + CORRADE_COMPARE(cube.attribute(Trade::MeshAttribute::Normal)[6], + (Vector3{1.0f, 0.0f, 0.0f})); } void CubeTest::solidStrip() { - Trade::MeshData3D cube = Primitives::cubeSolidStrip(); + Trade::MeshData cube = Primitives::cubeSolidStrip(); - CORRADE_VERIFY(!cube.isIndexed()); CORRADE_COMPARE(cube.primitive(), MeshPrimitive::TriangleStrip); - CORRADE_COMPARE(cube.positions(0).size(), 14); - CORRADE_COMPARE(cube.normalArrayCount(), 0); + CORRADE_VERIFY(!cube.isIndexed()); + CORRADE_COMPARE(cube.vertexCount(), 14); + CORRADE_COMPARE(cube.attributeCount(), 1); + CORRADE_COMPARE(cube.attribute(Trade::MeshAttribute::Position)[4], + (Vector3{-1.0f, -1.0f, -1.0f})); } void CubeTest::wireframe() { - Trade::MeshData3D cube = Primitives::cubeWireframe(); + Trade::MeshData cube = Primitives::cubeWireframe(); CORRADE_COMPARE(cube.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(cube.indices().size(), 24); - CORRADE_COMPARE(cube.positions(0).size(), 8); + CORRADE_VERIFY(cube.isIndexed()); + CORRADE_COMPARE(cube.indexCount(), 24); + CORRADE_COMPARE(cube.vertexCount(), 8); + CORRADE_COMPARE(cube.attributeCount(), 1); + CORRADE_COMPARE(cube.indices()[5], 3); + CORRADE_COMPARE(cube.attribute(Trade::MeshAttribute::Position)[5], + (Vector3{1.0f, -1.0f, -1.0f})); } }}}} diff --git a/src/Magnum/Primitives/Test/CylinderTest.cpp b/src/Magnum/Primitives/Test/CylinderTest.cpp index 93392a2ea9..f1a10e7fe5 100644 --- a/src/Magnum/Primitives/Test/CylinderTest.cpp +++ b/src/Magnum/Primitives/Test/CylinderTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Cylinder.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -51,9 +51,13 @@ CylinderTest::CylinderTest() { } void CylinderTest::solidWithoutAnything() { - Trade::MeshData3D cylinder = cylinderSolid(2, 3, 1.5f); + Trade::MeshData cylinder = cylinderSolid(2, 3, 1.5f); - CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ + CORRADE_COMPARE(cylinder.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cylinder.isIndexed()); + CORRADE_COMPARE(cylinder.attributeCount(), 2); + + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 1.0f}, /* 0 */ {0.866025f, -1.5f, -0.5f}, /* 1 */ {-0.866025f, -1.5f, -0.5f}, /* 2 */ @@ -67,7 +71,7 @@ void CylinderTest::solidWithoutAnything() { {-0.866025f, 1.5f, -0.5f} /* 8 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, 0.0f, 1.0f}, /* 0 */ {0.866025f, 0.0f, -0.5f}, /* 1 */ {-0.866025f, 0.0f, -0.5f}, /* 2 */ @@ -81,20 +85,22 @@ void CylinderTest::solidWithoutAnything() { {-0.866025f, 0.0f, -0.5f} /* 8 */ }), TestSuite::Compare::Container); - CORRADE_VERIFY(!cylinder.hasTextureCoords2D()); - - CORRADE_COMPARE_AS(cylinder.indices(), (std::vector{ + CORRADE_COMPARE_AS(cylinder.indices(), Containers::arrayView({ 0, 1, 4, 0, 4, 3, 1, 2, 5, 1, 5, 4, 2, 0, 3, 2, 3, 5, 3, 4, 7, 3, 7, 6, 4, 5, 8, 4, 8, 7, 5, 3, 6, 5, 6, 8 }), TestSuite::Compare::Container); } void CylinderTest::solidWithCaps() { - Trade::MeshData3D cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::CapEnds); + Trade::MeshData cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::CapEnds); + + CORRADE_COMPARE(cylinder.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cylinder.isIndexed()); + CORRADE_COMPARE(cylinder.attributeCount(), 2); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ - CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 0.0f}, /* 0 */ {0.0f, -1.5f, 1.0f}, /* 1 */ @@ -120,7 +126,7 @@ void CylinderTest::solidWithCaps() { {0.0f, 1.5f, 0.0f} /* 16 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ {0.0f, -1.0f, 0.0f}, /* 1 */ @@ -146,11 +152,9 @@ void CylinderTest::solidWithCaps() { {0.0f, 1.0f, 0.0f}, /* 16 */ }), TestSuite::Compare::Container); - CORRADE_VERIFY(!cylinder.hasTextureCoords2D()); - /* Faces of the caps and sides do not share any vertices due to different normals */ - CORRADE_COMPARE_AS(cylinder.indices(), (std::vector{ + CORRADE_COMPARE_AS(cylinder.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 1, 3, 4, 5, 8, 4, 8, 7, 5, 6, 9, 5, 9, 8, 6, 4, 7, 6, 7, 9, 7, 8, 11, 7, 11, 10, 8, 9, 12, 8, 12, 11, 9, 7, 10, 9, 10, 12, @@ -159,11 +163,15 @@ void CylinderTest::solidWithCaps() { } void CylinderTest::solidWithTextureCoords() { - Trade::MeshData3D cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::GenerateTextureCoords); + Trade::MeshData cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::GenerateTextureCoords); + + CORRADE_COMPARE(cylinder.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cylinder.isIndexed()); + CORRADE_COMPARE(cylinder.attributeCount(), 3); /* First vertex of each ring duplicated because it has different texture coordinates */ - CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 1.0f}, /* 0 */ {0.866025f, -1.5f, -0.5f}, /* 1 */ {-0.866025f, -1.5f, -0.5f}, /* 2 */ @@ -180,7 +188,7 @@ void CylinderTest::solidWithTextureCoords() { {0.0f, 1.5f, 1.0f}, /* 11 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, 0.0f, 1.0f}, /* 0 */ {0.866025f, 0.0f, -0.5f}, /* 1 */ {-0.866025f, 0.0f, -0.5f}, /* 2 */ @@ -197,7 +205,7 @@ void CylinderTest::solidWithTextureCoords() { {0.0f, 0.0f, 1.0f}, /* 11 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.0f, 0.0f}, /* 0 */ {0.333333f, 0.0f}, /* 1 */ {0.666667f, 0.0f}, /* 2 */ @@ -215,18 +223,22 @@ void CylinderTest::solidWithTextureCoords() { }), TestSuite::Compare::Container); /* Each ring has an extra vertex for texture coords */ - CORRADE_COMPARE_AS(cylinder.indices(), (std::vector{ + CORRADE_COMPARE_AS(cylinder.indices(), Containers::arrayView({ 0, 1, 5, 0, 5, 4, 1, 2, 6, 1, 6, 5, 2, 3, 7, 2, 7, 6, 4, 5, 9, 4, 9, 8, 5, 6, 10, 5, 10, 9, 6, 7, 11, 6, 11, 10 }), TestSuite::Compare::Container); } void CylinderTest::solidWithTextureCoordsAndCaps() { - Trade::MeshData3D cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::GenerateTextureCoords|CylinderFlag::CapEnds); + Trade::MeshData cylinder = cylinderSolid(2, 3, 1.5f, CylinderFlag::GenerateTextureCoords|CylinderFlag::CapEnds); + + CORRADE_COMPARE(cylinder.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(cylinder.isIndexed()); + CORRADE_COMPARE(cylinder.attributeCount(), 3); /* Bottom ring duplicated because it has different normals, first vertex of each ring duplicated because it has different texture coordinates */ - CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.5f, 0.0f}, /* 0 */ {0.0f, -1.5f, 1.0f}, /* 1 */ @@ -257,7 +269,7 @@ void CylinderTest::solidWithTextureCoordsAndCaps() { {0.0f, 1.5f, 0.0f} /* 21 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.normals(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, /* 0 */ {0.0f, -1.0f, 0.0f}, /* 1 */ @@ -288,7 +300,7 @@ void CylinderTest::solidWithTextureCoordsAndCaps() { {0.0f, 1.0f, 0.0f}, /* 21 */ }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(cylinder.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.5f, 0.0f}, /* 0 */ {0.0f, 0.2f}, /* 1 */ @@ -321,7 +333,7 @@ void CylinderTest::solidWithTextureCoordsAndCaps() { /* Faces of the caps and sides do not share any vertices due to different normals, each ring has an extra vertex for texture coords */ - CORRADE_COMPARE_AS(cylinder.indices(), (std::vector{ + CORRADE_COMPARE_AS(cylinder.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 4, 3, 5, 6, 10, 5, 10, 9, 6, 7, 11, 6, 11, 10, 7, 8, 12, 7, 12, 11, 9, 10, 14, 9, 14, 13, 10, 11, 15, 10, 15, 14, 11, 12, 16, 11, 16, 15, @@ -330,9 +342,13 @@ void CylinderTest::solidWithTextureCoordsAndCaps() { } void CylinderTest::wireframe() { - Trade::MeshData3D cylinder = cylinderWireframe(2, 8, 0.5f); + Trade::MeshData cylinder = cylinderWireframe(2, 8, 0.5f); - CORRADE_COMPARE_AS(cylinder.positions(0), (std::vector{ + CORRADE_COMPARE(cylinder.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(cylinder.isIndexed()); + CORRADE_COMPARE(cylinder.attributeCount(), 1); + + CORRADE_COMPARE_AS(cylinder.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -0.5f, 1.0f}, /* 0 */ {1.0f, -0.5f, 0.0f}, /* 1 */ {0.0f, -0.5f, -1.0f}, /* 2 */ @@ -361,10 +377,7 @@ void CylinderTest::wireframe() { {-0.707107f, 0.5f, 0.707107f} /* 23 */ }), TestSuite::Compare::Container); - CORRADE_VERIFY(!cylinder.hasNormals()); - CORRADE_VERIFY(!cylinder.hasTextureCoords2D()); - - CORRADE_COMPARE_AS(cylinder.indices(), (std::vector{ + CORRADE_COMPARE_AS(cylinder.indices(), Containers::arrayView({ 0, 4, 1, 5, 2, 6, 3, 7, 4, 1, 5, 2, 6, 3, 7, 0, diff --git a/src/Magnum/Primitives/Test/GradientTest.cpp b/src/Magnum/Primitives/Test/GradientTest.cpp index bd56babf3a..b036859d40 100644 --- a/src/Magnum/Primitives/Test/GradientTest.cpp +++ b/src/Magnum/Primitives/Test/GradientTest.cpp @@ -29,8 +29,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Color.h" #include "Magnum/Primitives/Gradient.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Primitives/Square.h" #include "Magnum/Primitives/Plane.h" @@ -62,23 +61,30 @@ using namespace Magnum::Math::Literals; void GradientTest::gradient2D() { /* The corners sould have 0.2, 0.4, 0.6, 0.8 blends */ - Trade::MeshData2D gradient = Primitives::gradient2D( + Trade::MeshData gradient = Primitives::gradient2D( {-1.0f, 2.0f}, {0.2f, 0.6f, 1.0f}, {1.0f, -2.0f}, {0.4f, 1.0f, 0.0f}); + CORRADE_COMPARE(gradient.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_VERIFY(!gradient.isIndexed()); + CORRADE_COMPARE(gradient.attributeCount(), 2); + /* Positions should be the same as for a square */ - Trade::MeshData2D square = Primitives::squareSolid(); + Trade::MeshData square = Primitives::squareSolid(); CORRADE_COMPARE(gradient.primitive(), square.primitive()); - CORRADE_COMPARE_AS(gradient.positions(0), square.positions(0), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(gradient.positions(0), (std::vector{ + CORRADE_COMPARE_AS( + gradient.attribute(Trade::MeshAttribute::Position), + square.attribute(Trade::MeshAttribute::Position), + TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 1.0f, -1.0f}, /* Bottom right */ { 1.0f, 1.0f}, /* Top right */ {-1.0f, -1.0f}, /* Bottom left */ {-1.0f, 1.0f} /* Top left */ }), TestSuite::Compare::Container); - CORRADE_COMPARE(gradient.colorArrayCount(), 1); - CORRADE_COMPARE_AS(gradient.colors(0), (std::vector{ + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ {0.36f, 0.92f, 0.2f}, /* 80% */ {0.28f, 0.76f, 0.6f}, /* 40% */ {0.32f, 0.84f, 0.4f}, /* 60% */ @@ -87,10 +93,9 @@ void GradientTest::gradient2D() { } void GradientTest::gradient2DHorizontal() { - Trade::MeshData2D gradient = Primitives::gradient2DHorizontal(0xfabcde_srgbf, 0xdeab09_srgbf); + Trade::MeshData gradient = Primitives::gradient2DHorizontal(0xfabcde_srgbf, 0xdeab09_srgbf); - CORRADE_COMPARE(gradient.colorArrayCount(), 1); - CORRADE_COMPARE_AS(gradient.colors(0), (std::vector{ + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ 0xdeab09_srgbf, 0xdeab09_srgbf, 0xfabcde_srgbf, @@ -99,10 +104,9 @@ void GradientTest::gradient2DHorizontal() { } void GradientTest::gradient2DVertical() { - Trade::MeshData2D gradient = Primitives::gradient2DVertical(0xfabcde_srgbf, 0xdeab09_srgbf); + Trade::MeshData gradient = Primitives::gradient2DVertical(0xfabcde_srgbf, 0xdeab09_srgbf); - CORRADE_COMPARE(gradient.colorArrayCount(), 1); - CORRADE_COMPARE_AS(gradient.colors(0), (std::vector{ + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ 0xfabcde_srgbf, 0xdeab09_srgbf, 0xfabcde_srgbf, @@ -112,25 +116,37 @@ void GradientTest::gradient2DVertical() { void GradientTest::gradient3D() { /* The corners sould have 0.2, 0.4, 0.6, 0.8 blends */ - Trade::MeshData3D gradient = Primitives::gradient3D( + Trade::MeshData gradient = Primitives::gradient3D( {-1.0f, 2.0f, -1.5f}, {0.2f, 0.6f, 1.0f}, {1.0f, -2.0f, -1.5f}, {0.4f, 1.0f, 0.0f}); + CORRADE_COMPARE(gradient.primitive(), MeshPrimitive::TriangleStrip); + CORRADE_VERIFY(!gradient.isIndexed()); + CORRADE_COMPARE(gradient.attributeCount(), 3); + /* Positions should be the same as for a plane */ - Trade::MeshData3D plane = Primitives::planeSolid(); + Trade::MeshData plane = Primitives::planeSolid(); CORRADE_COMPARE(gradient.primitive(), plane.primitive()); - CORRADE_COMPARE_AS(gradient.positions(0), plane.positions(0), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(gradient.positions(0), (std::vector{ + CORRADE_COMPARE_AS( + gradient.attribute(Trade::MeshAttribute::Position), + plane.attribute(Trade::MeshAttribute::Position), + TestSuite::Compare::Container); + + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ { 1.0f, -1.0f, 0.0f}, /* Bottom right */ { 1.0f, 1.0f, 0.0f}, /* Top right */ {-1.0f, -1.0f, 0.0f}, /* Bottom left */ {-1.0f, 1.0f, 0.0f} /* Top left */ }), TestSuite::Compare::Container); - CORRADE_COMPARE(gradient.normalArrayCount(), 1); + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f}, + {0.0f, 0.0f, 1.0f} + }), TestSuite::Compare::Container); - CORRADE_COMPARE(gradient.colorArrayCount(), 1); - CORRADE_COMPARE_AS(gradient.colors(0), (std::vector{ + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ {0.36f, 0.92f, 0.2f}, /* 80% */ {0.28f, 0.76f, 0.6f}, /* 40% */ {0.32f, 0.84f, 0.4f}, /* 60% */ @@ -139,10 +155,9 @@ void GradientTest::gradient3D() { } void GradientTest::gradient3DHorizontal() { - Trade::MeshData3D gradient = Primitives::gradient3DHorizontal(0xfabcde_srgbf, 0xdeab09_srgbf); + Trade::MeshData gradient = Primitives::gradient3DHorizontal(0xfabcde_srgbf, 0xdeab09_srgbf); - CORRADE_COMPARE(gradient.colorArrayCount(), 1); - CORRADE_COMPARE_AS(gradient.colors(0), (std::vector{ + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ 0xdeab09_srgbf, 0xdeab09_srgbf, 0xfabcde_srgbf, @@ -151,10 +166,9 @@ void GradientTest::gradient3DHorizontal() { } void GradientTest::gradient3DVertical() { - Trade::MeshData3D gradient = Primitives::gradient3DVertical(0xfabcde_srgbf, 0xdeab09_srgbf); + Trade::MeshData gradient = Primitives::gradient3DVertical(0xfabcde_srgbf, 0xdeab09_srgbf); - CORRADE_COMPARE(gradient.colorArrayCount(), 1); - CORRADE_COMPARE_AS(gradient.colors(0), (std::vector{ + CORRADE_COMPARE_AS(gradient.attribute(Trade::MeshAttribute::Color), Containers::arrayView({ 0xfabcde_srgbf, 0xdeab09_srgbf, 0xfabcde_srgbf, diff --git a/src/Magnum/Primitives/Test/GridTest.cpp b/src/Magnum/Primitives/Test/GridTest.cpp index f5114d8b8d..71a4a52d86 100644 --- a/src/Magnum/Primitives/Test/GridTest.cpp +++ b/src/Magnum/Primitives/Test/GridTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Grid.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -47,9 +47,13 @@ GridTest::GridTest() { } void GridTest::solid3DWithoutAnything() { - Trade::MeshData3D grid = grid3DSolid({5, 3}, {}); + Trade::MeshData grid = grid3DSolid({5, 3}, {}); - CORRADE_COMPARE_AS(grid.positions(0), (std::vector{ + CORRADE_COMPARE(grid.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(grid.isIndexed()); + CORRADE_COMPARE(grid.attributeCount(), 1); + + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {-1.0f, -1.0f, 0.0f}, {-0.666667f, -1.0f, 0.0f}, {-0.333333f, -1.0f, 0.0f}, @@ -91,10 +95,7 @@ void GridTest::solid3DWithoutAnything() { {1.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(grid.normalArrayCount(), 0); - CORRADE_COMPARE(grid.textureCoords2DArrayCount(), 0); - - CORRADE_COMPARE_AS(grid.indices(), (std::vector{ + CORRADE_COMPARE_AS(grid.indices(), Containers::arrayView({ 0, 8, 7, 0, 1, 8, 1, 9, 8, 1, 2, 9, 2, 10, 9, 2, 3, 10, @@ -126,9 +127,13 @@ void GridTest::solid3DWithoutAnything() { } void GridTest::solid3DWithNormalsAndTextureCoords() { - Trade::MeshData3D grid = grid3DSolid({5, 3}, GridFlag::GenerateNormals|GridFlag::GenerateTextureCoords); + Trade::MeshData grid = grid3DSolid({5, 3}, GridFlag::GenerateNormals|GridFlag::GenerateTextureCoords); - CORRADE_COMPARE_AS(grid.positions(0), (std::vector{ + CORRADE_COMPARE(grid.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(grid.isIndexed()); + CORRADE_COMPARE(grid.attributeCount(), 3); + + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {-1.0f, -1.0f, 0.0f}, {-0.666667f, -1.0f, 0.0f}, {-0.333333f, -1.0f, 0.0f}, @@ -170,7 +175,7 @@ void GridTest::solid3DWithNormalsAndTextureCoords() { {1.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(grid.normals(0), (std::vector{ + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 1.0f}, @@ -212,7 +217,7 @@ void GridTest::solid3DWithNormalsAndTextureCoords() { {0.0f, 0.0f, 1.0f}, }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(grid.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.0f, 0.0f}, {0.166667f, 0.0f}, {0.333333f, 0.0f}, @@ -254,7 +259,7 @@ void GridTest::solid3DWithNormalsAndTextureCoords() { {1.0f, 1.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(grid.indices(), (std::vector{ + CORRADE_COMPARE_AS(grid.indices(), Containers::arrayView({ 0, 8, 7, 0, 1, 8, 1, 9, 8, 1, 2, 9, 2, 10, 9, 2, 3, 10, @@ -286,9 +291,13 @@ void GridTest::solid3DWithNormalsAndTextureCoords() { } void GridTest::wireframe3D() { - Trade::MeshData3D grid = grid3DWireframe({5, 3}); + Trade::MeshData grid = grid3DWireframe({5, 3}); + + CORRADE_COMPARE(grid.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(grid.isIndexed()); + CORRADE_COMPARE(grid.attributeCount(), 1); - CORRADE_COMPARE_AS(grid.positions(0), (std::vector{ + CORRADE_COMPARE_AS(grid.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {-1.0f, -1.0f, 0.0f}, {-0.666667f, -1.0f, 0.0f}, {-0.333333f, -1.0f, 0.0f}, @@ -330,7 +339,7 @@ void GridTest::wireframe3D() { {1.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(grid.indices(), (std::vector{ + CORRADE_COMPARE_AS(grid.indices(), Containers::arrayView({ 0, 1, 0, 7, 1, 2, 1, 8, 2, 3, 2, 9, diff --git a/src/Magnum/Primitives/Test/IcosphereTest.cpp b/src/Magnum/Primitives/Test/IcosphereTest.cpp index 5f7c3cac5b..87f59ca969 100644 --- a/src/Magnum/Primitives/Test/IcosphereTest.cpp +++ b/src/Magnum/Primitives/Test/IcosphereTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Icosphere.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -47,25 +47,30 @@ IcosphereTest::IcosphereTest() { } void IcosphereTest::count0() { - Trade::MeshData3D data = Primitives::icosphereSolid(0); - - CORRADE_COMPARE(data.positionArrayCount(), 1); - CORRADE_COMPARE(data.normalArrayCount(), 1); - - CORRADE_COMPARE(data.indices().size(), 60); - CORRADE_COMPARE(data.positions(0).size(), 12); - CORRADE_COMPARE(data.normals(0).size(), 12); + Trade::MeshData icosphere = Primitives::icosphereSolid(0); + + CORRADE_COMPARE(icosphere.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(icosphere.isIndexed()); + CORRADE_COMPARE(icosphere.indexCount(), 60); + CORRADE_COMPARE(icosphere.vertexCount(), 12); + CORRADE_COMPARE(icosphere.attributeCount(), 2); + CORRADE_COMPARE(icosphere.indices()[18], 9); + CORRADE_COMPARE(icosphere.attribute(Trade::MeshAttribute::Position)[8], + (Vector3{-0.525731f, -0.850651f, 0.0f})); + CORRADE_COMPARE(icosphere.attribute(Trade::MeshAttribute::Normal)[8], + (Vector3{-0.525731f, -0.850651f, 0.0f})); } void IcosphereTest::data1() { /* This also tests the subdivide() and removeDuplicates() mesh tools */ - Trade::MeshData3D data = Primitives::icosphereSolid(1); + Trade::MeshData icosphere = Primitives::icosphereSolid(1); - CORRADE_COMPARE(data.positionArrayCount(), 1); - CORRADE_COMPARE(data.normalArrayCount(), 1); + CORRADE_COMPARE(icosphere.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(icosphere.isIndexed()); + CORRADE_COMPARE(icosphere.attributeCount(), 2); - CORRADE_COMPARE_AS(data.indices(), (std::vector{ + CORRADE_COMPARE_AS(icosphere.indices(), Containers::arrayView({ 12, 13, 14, 15, 16, 12, 17, 18, 19, 17, 20, 21, 22, 23, 24, 22, 25, 26, 27, 28, 29, 27, 30, 31, 32, 33, 34, 32, 35, 36, 37, 38, 39, 37, 40, 41, 13, 28, 25, 14, 24, 39, 19, 26, 31, 18, 40, 23, 16, 34, 29, 15, 38, 35, @@ -80,7 +85,8 @@ void IcosphereTest::data1() { 16, 29, 16, 7, 34, 29, 34, 9, 7, 15, 35, 15, 1, 38, 35, 38, 0, 3, 30, 20, 30, 9, 33, 20, 33, 8, 4, 21, 41, 21, 8, 36, 41, 36, 0}), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(data.positions(0), (std::vector{ + + CORRADE_COMPARE_AS(icosphere.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -0.525731f, 0.850651f}, {0.850651f, 0.0f, 0.525731f}, {0.850651f, 0.0f, -0.525731f}, @@ -124,19 +130,20 @@ void IcosphereTest::data1() { {-0.5f, 0.309017f, 0.809017f}, {-0.5f, -0.309017f, 0.809017f}}), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(data.normals(0), data.positions(0), + CORRADE_COMPARE_AS( + icosphere.attribute(Trade::MeshAttribute::Position), + icosphere.attribute(Trade::MeshAttribute::Normal), TestSuite::Compare::Container); } void IcosphereTest::count2() { - Trade::MeshData3D data = Primitives::icosphereSolid(2); - - CORRADE_COMPARE(data.positionArrayCount(), 1); - CORRADE_COMPARE(data.normalArrayCount(), 1); + Trade::MeshData icosphere = Primitives::icosphereSolid(2); - CORRADE_COMPARE(data.indices().size(), 960); - CORRADE_COMPARE(data.positions(0).size(), 162); - CORRADE_COMPARE(data.normals(0).size(), 162); + CORRADE_COMPARE(icosphere.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(icosphere.isIndexed()); + CORRADE_COMPARE(icosphere.indexCount(), 960); + CORRADE_COMPARE(icosphere.vertexCount(), 162); + CORRADE_COMPARE(icosphere.attributeCount(), 2); } }}}} diff --git a/src/Magnum/Primitives/Test/LineTest.cpp b/src/Magnum/Primitives/Test/LineTest.cpp index 02c0f7972b..d0c80a8a14 100644 --- a/src/Magnum/Primitives/Test/LineTest.cpp +++ b/src/Magnum/Primitives/Test/LineTest.cpp @@ -28,8 +28,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Line.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -46,19 +45,25 @@ LineTest::LineTest() { } void LineTest::twoDimensions() { - Trade::MeshData2D line = Primitives::line2D(); + Trade::MeshData line = Primitives::line2D(); - CORRADE_VERIFY(!line.isIndexed()); CORRADE_COMPARE(line.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(line.positions(0).size(), 2); + CORRADE_VERIFY(!line.isIndexed()); + CORRADE_COMPARE(line.vertexCount(), 2); + CORRADE_COMPARE(line.attributeCount(), 1); + CORRADE_COMPARE(line.attribute(Trade::MeshAttribute::Position)[1], + (Vector2{1.0f, 0.0f})); } void LineTest::threeDimensions() { - Trade::MeshData3D line = Primitives::line3D(); + Trade::MeshData line = Primitives::line3D(); - CORRADE_VERIFY(!line.isIndexed()); CORRADE_COMPARE(line.primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(line.positions(0).size(), 2); + CORRADE_VERIFY(!line.isIndexed()); + CORRADE_COMPARE(line.vertexCount(), 2); + CORRADE_COMPARE(line.attributeCount(), 1); + CORRADE_COMPARE(line.attribute(Trade::MeshAttribute::Position)[1], + (Vector3{1.0f, 0.0f, 0.0f})); } }}}} diff --git a/src/Magnum/Primitives/Test/PlaneTest.cpp b/src/Magnum/Primitives/Test/PlaneTest.cpp index 14ce8ba948..614b8f4a3d 100644 --- a/src/Magnum/Primitives/Test/PlaneTest.cpp +++ b/src/Magnum/Primitives/Test/PlaneTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/Plane.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -47,32 +47,42 @@ PlaneTest::PlaneTest() { } void PlaneTest::solid() { - Trade::MeshData3D plane = Primitives::planeSolid(); + Trade::MeshData plane = Primitives::planeSolid(); - CORRADE_VERIFY(!plane.isIndexed()); CORRADE_COMPARE(plane.primitive(), MeshPrimitive::TriangleStrip); - CORRADE_COMPARE(plane.positions(0).size(), 4); - CORRADE_COMPARE(plane.normals(0).size(), 4); - CORRADE_COMPARE(plane.textureCoords2DArrayCount(), 0); + CORRADE_VERIFY(!plane.isIndexed()); + CORRADE_COMPARE(plane.vertexCount(), 4); + CORRADE_COMPARE(plane.attributeCount(), 2); + CORRADE_COMPARE(plane.attribute(Trade::MeshAttribute::Position)[3], + (Vector3{-1.0f, 1.0f, 0.0f})); + CORRADE_COMPARE(plane.attribute(Trade::MeshAttribute::Normal)[2], + (Vector3{0.0f, 0.0f, 1.0f})); } void PlaneTest::solidTextured() { - Trade::MeshData3D plane = Primitives::planeSolid(Primitives::PlaneTextureCoords::Generate); + Trade::MeshData plane = Primitives::planeSolid(Primitives::PlaneTextureCoords::Generate); - CORRADE_VERIFY(!plane.isIndexed()); CORRADE_COMPARE(plane.primitive(), MeshPrimitive::TriangleStrip); - CORRADE_COMPARE(plane.positions(0).size(), 4); - CORRADE_COMPARE(plane.normals(0).size(), 4); - CORRADE_COMPARE(plane.textureCoords2DArrayCount(), 1); - CORRADE_COMPARE(plane.textureCoords2D(0).size(), 4); + CORRADE_VERIFY(!plane.isIndexed()); + CORRADE_COMPARE(plane.vertexCount(), 4); + CORRADE_COMPARE(plane.attributeCount(), 3); + CORRADE_COMPARE(plane.attribute(Trade::MeshAttribute::Position)[3], + (Vector3{-1.0f, 1.0f, 0.0f})); + CORRADE_COMPARE(plane.attribute(Trade::MeshAttribute::Normal)[2], + (Vector3{0.0f, 0.0f, 1.0f})); + CORRADE_COMPARE(plane.attribute(Trade::MeshAttribute::TextureCoordinates)[1], + (Vector2{1.0f, 1.0f})); } void PlaneTest::wireframe() { - Trade::MeshData3D plane = Primitives::planeWireframe(); + Trade::MeshData plane = Primitives::planeWireframe(); - CORRADE_VERIFY(!plane.isIndexed()); CORRADE_COMPARE(plane.primitive(), MeshPrimitive::LineLoop); - CORRADE_COMPARE(plane.positions(0).size(), 4); + CORRADE_VERIFY(!plane.isIndexed()); + CORRADE_COMPARE(plane.vertexCount(), 4); + CORRADE_COMPARE(plane.attributeCount(), 1); + CORRADE_COMPARE(plane.attribute(Trade::MeshAttribute::Position)[3], + (Vector3{-1.0f, 1.0f, 0.0f})); } }}}} diff --git a/src/Magnum/Primitives/Test/SquareTest.cpp b/src/Magnum/Primitives/Test/SquareTest.cpp index d4abf8bfce..f06966f5a3 100644 --- a/src/Magnum/Primitives/Test/SquareTest.cpp +++ b/src/Magnum/Primitives/Test/SquareTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Mesh.h" #include "Magnum/Math/Vector2.h" #include "Magnum/Primitives/Square.h" -#include "Magnum/Trade/MeshData2D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -47,30 +47,38 @@ SquareTest::SquareTest() { } void SquareTest::solid() { - Trade::MeshData2D square = Primitives::squareSolid(); + Trade::MeshData square = Primitives::squareSolid(); - CORRADE_VERIFY(!square.isIndexed()); CORRADE_COMPARE(square.primitive(), MeshPrimitive::TriangleStrip); - CORRADE_COMPARE(square.positions(0).size(), 4); - CORRADE_COMPARE(square.textureCoords2DArrayCount(), 0); + CORRADE_VERIFY(!square.isIndexed()); + CORRADE_COMPARE(square.vertexCount(), 4); + CORRADE_COMPARE(square.attributeCount(), 1); + CORRADE_COMPARE(square.attribute(Trade::MeshAttribute::Position)[3], + (Vector2{-1.0f, 1.0f})); } void SquareTest::solidTextured() { - Trade::MeshData2D square = Primitives::squareSolid(Primitives::SquareTextureCoords::Generate); + Trade::MeshData square = Primitives::squareSolid(Primitives::SquareTextureCoords::Generate); - CORRADE_VERIFY(!square.isIndexed()); CORRADE_COMPARE(square.primitive(), MeshPrimitive::TriangleStrip); - CORRADE_COMPARE(square.positions(0).size(), 4); - CORRADE_COMPARE(square.textureCoords2DArrayCount(), 1); - CORRADE_COMPARE(square.textureCoords2D(0).size(), 4); + CORRADE_VERIFY(!square.isIndexed()); + CORRADE_COMPARE(square.vertexCount(), 4); + CORRADE_COMPARE(square.attributeCount(), 2); + CORRADE_COMPARE(square.attribute(Trade::MeshAttribute::Position)[3], + (Vector2{-1.0f, 1.0f})); + CORRADE_COMPARE(square.attribute(Trade::MeshAttribute::TextureCoordinates)[1], + (Vector2{1.0f, 1.0f})); } void SquareTest::wireframe() { - Trade::MeshData2D square = Primitives::squareWireframe(); + Trade::MeshData square = Primitives::squareWireframe(); - CORRADE_VERIFY(!square.isIndexed()); CORRADE_COMPARE(square.primitive(), MeshPrimitive::LineLoop); - CORRADE_COMPARE(square.positions(0).size(), 4); + CORRADE_VERIFY(!square.isIndexed()); + CORRADE_COMPARE(square.vertexCount(), 4); + CORRADE_COMPARE(square.attributeCount(), 1); + CORRADE_COMPARE(square.attribute(Trade::MeshAttribute::Position)[3], + (Vector2{-1.0f, 1.0f})); } }}}} diff --git a/src/Magnum/Primitives/Test/UVSphereTest.cpp b/src/Magnum/Primitives/Test/UVSphereTest.cpp index 98efb97b01..33d9ebe3ad 100644 --- a/src/Magnum/Primitives/Test/UVSphereTest.cpp +++ b/src/Magnum/Primitives/Test/UVSphereTest.cpp @@ -28,7 +28,7 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Primitives/UVSphere.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Primitives { namespace Test { namespace { @@ -47,9 +47,13 @@ UVSphereTest::UVSphereTest() { } void UVSphereTest::solidWithoutTextureCoords() { - Trade::MeshData3D sphere = uvSphereSolid(3, 3); + Trade::MeshData sphere = uvSphereSolid(3, 3); - CORRADE_COMPARE_AS(sphere.positions(0), (std::vector{ + CORRADE_COMPARE(sphere.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(sphere.isIndexed()); + CORRADE_COMPARE(sphere.attributeCount(), 2); + + CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, {0.0f, -0.5f, 0.866025f}, @@ -63,7 +67,7 @@ void UVSphereTest::solidWithoutTextureCoords() { {0.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(sphere.normals(0), (std::vector{ + CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::Normal), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, {0.0f, -0.5f, 0.866025f}, @@ -77,7 +81,7 @@ void UVSphereTest::solidWithoutTextureCoords() { {0.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(sphere.indices(), (std::vector{ + CORRADE_COMPARE_AS(sphere.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 1, 3, 1, 2, 5, 1, 5, 4, 2, 3, 6, 2, 6, 5, 3, 1, 4, 3, 4, 6, 4, 5, 7, 5, 6, 7, 6, 4, 7 @@ -85,9 +89,13 @@ void UVSphereTest::solidWithoutTextureCoords() { } void UVSphereTest::solidWithTextureCoords() { - Trade::MeshData3D sphere = uvSphereSolid(3, 3, UVSphereTextureCoords::Generate); + Trade::MeshData sphere = uvSphereSolid(3, 3, UVSphereTextureCoords::Generate); + + CORRADE_COMPARE(sphere.primitive(), MeshPrimitive::Triangles); + CORRADE_VERIFY(sphere.isIndexed()); + CORRADE_COMPARE(sphere.attributeCount(), 3); - CORRADE_COMPARE_AS(sphere.positions(0), (std::vector{ + CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, {0.0f, -0.5f, 0.866025f}, @@ -103,7 +111,7 @@ void UVSphereTest::solidWithTextureCoords() { {0.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(sphere.textureCoords2D(0), (std::vector{ + CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::TextureCoordinates), Containers::arrayView({ {0.5f, 0.0f}, {0.0f, 0.333333f}, @@ -119,7 +127,7 @@ void UVSphereTest::solidWithTextureCoords() { {0.5f, 1.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(sphere.indices(), (std::vector{ + CORRADE_COMPARE_AS(sphere.indices(), Containers::arrayView({ 0, 2, 1, 0, 3, 2, 0, 4, 3, 1, 2, 6, 1, 6, 5, 2, 3, 7, 2, 7, 6, 3, 4, 8, 3, 8, 7, 5, 6, 9, 6, 7, 9, 7, 8, 9 @@ -127,9 +135,13 @@ void UVSphereTest::solidWithTextureCoords() { } void UVSphereTest::wireframe() { - Trade::MeshData3D sphere = uvSphereWireframe(6, 8); + Trade::MeshData sphere = uvSphereWireframe(6, 8); - CORRADE_COMPARE_AS(sphere.positions(0), (std::vector{ + CORRADE_COMPARE(sphere.primitive(), MeshPrimitive::Lines); + CORRADE_VERIFY(sphere.isIndexed()); + CORRADE_COMPARE(sphere.attributeCount(), 1); + + CORRADE_COMPARE_AS(sphere.attribute(Trade::MeshAttribute::Position), Containers::arrayView({ {0.0f, -1.0f, 0.0f}, {0.0f, -0.866025f, 0.5f}, @@ -167,9 +179,7 @@ void UVSphereTest::wireframe() { {0.0f, 1.0f, 0.0f} }), TestSuite::Compare::Container); - CORRADE_COMPARE(sphere.normalArrayCount(), 0); - - CORRADE_COMPARE_AS(sphere.indices(), (std::vector{ + CORRADE_COMPARE_AS(sphere.indices(), Containers::arrayView({ 0, 1, 0, 2, 0, 3, 0, 4, 1, 5, 2, 6, 3, 7, 4, 8, From fa3c9495b750acc025cdf4da0e0688320fd17d68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 4 Jan 2020 23:30:16 +0100 Subject: [PATCH 061/107] MeshTools: improve the subdivide benchmark with in-place subdiv. --- .../Test/SubdivideRemoveDuplicatesBenchmark.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp b/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp index f0452e4785..7e68de8814 100644 --- a/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp +++ b/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp @@ -39,12 +39,14 @@ struct SubdivideRemoveDuplicatesBenchmark: TestSuite::Tester { void subdivide(); void subdivideAndRemoveDuplicatesAfter(); + void subdivideAndRemoveDuplicatesAfterInPlace(); void subdivideAndRemoveDuplicatesInBetween(); }; SubdivideRemoveDuplicatesBenchmark::SubdivideRemoveDuplicatesBenchmark() { addBenchmarks({&SubdivideRemoveDuplicatesBenchmark::subdivide, &SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesAfter, + &SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesAfterInPlace, &SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesInBetween}, 4); } @@ -77,6 +79,13 @@ void SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesAfter() { } } +void SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesAfterInPlace() { + CORRADE_BENCHMARK(3) { + /* Because that's what this thing does */ + Trade::MeshData3D icosphere = Primitives::icosphereSolid(5); + } +} + void SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesInBetween() { CORRADE_BENCHMARK(3) { Trade::MeshData3D icosphere = Primitives::icosphereSolid(0); From 89d6d6de7cb114d3ef353318a40b50fe2c99669d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 18 Jan 2020 19:35:00 +0100 Subject: [PATCH 062/107] Shaders: port tests away from MeshDataXD. --- .../Test/DistanceFieldVectorGLTest.cpp | 3 +-- src/Magnum/Shaders/Test/FlatGLTest.cpp | 11 +++++------ .../Shaders/Test/MeshVisualizerGLTest.cpp | 14 ++++---------- src/Magnum/Shaders/Test/PhongGLTest.cpp | 12 ++++++------ src/Magnum/Shaders/Test/VectorGLTest.cpp | 3 +-- src/Magnum/Shaders/Test/VertexColorGLTest.cpp | 19 +++++++++---------- 6 files changed, 26 insertions(+), 36 deletions(-) diff --git a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp index 010ece013c..84e7681d7f 100644 --- a/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/DistanceFieldVectorGLTest.cpp @@ -48,8 +48,7 @@ #include "Magnum/Shaders/DistanceFieldVector.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" diff --git a/src/Magnum/Shaders/Test/FlatGLTest.cpp b/src/Magnum/Shaders/Test/FlatGLTest.cpp index 799026f36e..e70cf2ef70 100644 --- a/src/Magnum/Shaders/Test/FlatGLTest.cpp +++ b/src/Magnum/Shaders/Test/FlatGLTest.cpp @@ -52,8 +52,7 @@ #include "Magnum/Shaders/Flat.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" @@ -640,11 +639,11 @@ template void FlatGLTest::renderVertexColor2D() { !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); - Trade::MeshData2D circleData = Primitives::circle2DSolid(32, + Trade::MeshData circleData = Primitives::circle2DSolid(32, Primitives::CircleTextureCoords::Generate); /* Highlight a quarter */ - Containers::Array colorData{Containers::DirectInit, circleData.positions(0).size(), 0x999999_rgbf}; + Containers::Array colorData{Containers::DirectInit, circleData.vertexCount(), 0x999999_rgbf}; for(std::size_t i = 8; i != 16; ++i) colorData[i + 1] = 0xffff99_rgbf*1.5f; @@ -694,11 +693,11 @@ template void FlatGLTest::renderVertexColor3D() { !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); - Trade::MeshData3D sphereData = Primitives::uvSphereSolid(16, 32, + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, Primitives::UVSphereTextureCoords::Generate); /* Highlight the middle rings */ - Containers::Array colorData{Containers::DirectInit, sphereData.positions(0).size(), 0x999999_rgbf}; + Containers::Array colorData{Containers::DirectInit, sphereData.vertexCount(), 0x999999_rgbf}; for(std::size_t i = 6*33; i != 9*33; ++i) colorData[i + 1] = 0xffff99_rgbf*1.5f; diff --git a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp index a4326883de..1a574cdfcb 100644 --- a/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp +++ b/src/Magnum/Shaders/Test/MeshVisualizerGLTest.cpp @@ -51,8 +51,7 @@ #include "Magnum/Primitives/UVSphere.h" #include "Magnum/Shaders/MeshVisualizer.h" #include "Magnum/Trade/AbstractImporter.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" @@ -409,24 +408,19 @@ void MeshVisualizerGLTest::renderWireframe() { #endif #endif - const Trade::MeshData3D sphereData = Primitives::icosphereSolid(1); + const Trade::MeshData sphereData = Primitives::icosphereSolid(1); GL::Mesh sphere{NoCreate}; if(data.flags & MeshVisualizer::Flag::NoGeometryShader) { - sphere = GL::Mesh{}; - sphere.setCount(sphereData.indices().size()); - /* Duplicate the vertices */ - GL::Buffer positions; - positions.setData(MeshTools::duplicate(Containers::stridedArrayView(sphereData.indices()), Containers::stridedArrayView(sphereData.positions(0)))); - sphere.addVertexBuffer(std::move(positions), 0, MeshVisualizer::Position{}); + sphere = MeshTools::compile(MeshTools::duplicate(sphereData)); /* Supply also the vertex ID, if needed */ #ifndef MAGNUM_TARGET_GLES2 if(!GL::Context::current().isExtensionSupported()) #endif { - Containers::Array vertexIndex{sphereData.indices().size()}; + Containers::Array vertexIndex{sphereData.indexCount()}; std::iota(vertexIndex.begin(), vertexIndex.end(), 0.0f); GL::Buffer vertexId; diff --git a/src/Magnum/Shaders/Test/PhongGLTest.cpp b/src/Magnum/Shaders/Test/PhongGLTest.cpp index 424861056a..0e5b73d6d4 100644 --- a/src/Magnum/Shaders/Test/PhongGLTest.cpp +++ b/src/Magnum/Shaders/Test/PhongGLTest.cpp @@ -51,8 +51,8 @@ #include "Magnum/Primitives/UVSphere.h" #include "Magnum/Shaders/Phong.h" #include "Magnum/Trade/AbstractImporter.h" -#include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/ImageData.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" @@ -506,12 +506,12 @@ void PhongGLTest::renderDefaults() { /* The light is at the center by default, so we scale the sphere to half and move the vertices back a bit to avoid a fully-black render but still have the thing in the default [-1; 1] cube */ - Trade::MeshData3D meshData = Primitives::uvSphereSolid(16, 32); + Trade::MeshData meshData = Primitives::uvSphereSolid(16, 32); Matrix4 transformation = Matrix4::translation(Vector3::zAxis(-1.0f))*Matrix4::scaling(Vector3(1.0f, 1.0f, 0.25f)); - MeshTools::transformPointsInPlace(transformation, meshData.positions(0)); + MeshTools::transformPointsInPlace(transformation, meshData.mutableAttribute(Trade::MeshAttribute::Position)); /** @todo use Matrix4::normalMatrix() */ - MeshTools::transformVectorsInPlace(transformation.inverted().transposed(), meshData.normals(0)); + MeshTools::transformVectorsInPlace(transformation.inverted().transposed(), meshData.mutableAttribute(Trade::MeshAttribute::Normal)); GL::Mesh sphere = MeshTools::compile(meshData); Phong shader; @@ -854,11 +854,11 @@ template void PhongGLTest::renderVertexColor() { !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); - Trade::MeshData3D sphereData = Primitives::uvSphereSolid(16, 32, + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, Primitives::UVSphereTextureCoords::Generate); /* Highlight the middle rings */ - Containers::Array colorData{Containers::DirectInit, sphereData.positions(0).size(), 0x999999_rgbf}; + Containers::Array colorData{Containers::DirectInit, sphereData.vertexCount(), 0x999999_rgbf}; for(std::size_t i = 6*33; i != 9*33; ++i) colorData[i + 1] = 0xffff99_rgbf*1.5f; diff --git a/src/Magnum/Shaders/Test/VectorGLTest.cpp b/src/Magnum/Shaders/Test/VectorGLTest.cpp index 6cf2abccc5..4abe9c71f2 100644 --- a/src/Magnum/Shaders/Test/VectorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VectorGLTest.cpp @@ -48,8 +48,7 @@ #include "Magnum/Shaders/Vector.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ImageData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" diff --git a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp index 3c0dc7e201..85cf56ab5f 100644 --- a/src/Magnum/Shaders/Test/VertexColorGLTest.cpp +++ b/src/Magnum/Shaders/Test/VertexColorGLTest.cpp @@ -44,8 +44,7 @@ #include "Magnum/Primitives/UVSphere.h" #include "Magnum/Shaders/VertexColor.h" #include "Magnum/Trade/AbstractImporter.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" @@ -197,11 +196,11 @@ void VertexColorGLTest::renderTeardown() { template void VertexColorGLTest::renderDefaults2D() { setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); - Trade::MeshData2D circleData = Primitives::circle2DSolid(32, + Trade::MeshData circleData = Primitives::circle2DSolid(32, Primitives::CircleTextureCoords::Generate); /* All a single color */ - Containers::Array colorData{Containers::DirectInit, circleData.positions(0).size(), 0xffffff_rgbf}; + Containers::Array colorData{Containers::DirectInit, circleData.vertexCount(), 0xffffff_rgbf}; GL::Buffer colors; colors.setData(colorData); @@ -238,11 +237,11 @@ template void VertexColorGLTest::renderDefaults3D() { !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); - Trade::MeshData3D sphereData = Primitives::uvSphereSolid(16, 32, + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, Primitives::UVSphereTextureCoords::Generate); /* All a single color */ - Containers::Array colorData{Containers::DirectInit, sphereData.positions(0).size(), 0xffffff_rgbf}; + Containers::Array colorData{Containers::DirectInit, sphereData.vertexCount(), 0xffffff_rgbf}; GL::Buffer colors; colors.setData(colorData); @@ -271,11 +270,11 @@ template void VertexColorGLTest::renderDefaults3D() { template void VertexColorGLTest::render2D() { setTestCaseTemplateName(T::Size == 3 ? "Color3" : "Color4"); - Trade::MeshData2D circleData = Primitives::circle2DSolid(32, + Trade::MeshData circleData = Primitives::circle2DSolid(32, Primitives::CircleTextureCoords::Generate); /* Highlight a quarter */ - Containers::Array colorData{Containers::DirectInit, circleData.positions(0).size(), 0x9999ff_rgbf}; + Containers::Array colorData{Containers::DirectInit, circleData.vertexCount(), 0x9999ff_rgbf}; for(std::size_t i = 8; i != 16; ++i) colorData[i + 1] = 0xffff99_rgbf; @@ -316,11 +315,11 @@ template void VertexColorGLTest::render3D() { !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) CORRADE_SKIP("AnyImageImporter / TgaImageImporter plugins not found."); - Trade::MeshData3D sphereData = Primitives::uvSphereSolid(16, 32, + Trade::MeshData sphereData = Primitives::uvSphereSolid(16, 32, Primitives::UVSphereTextureCoords::Generate); /* Highlight the middle rings */ - Containers::Array colorData{Containers::DirectInit, sphereData.positions(0).size(), 0x9999ff_rgbf}; + Containers::Array colorData{Containers::DirectInit, sphereData.vertexCount(), 0x9999ff_rgbf}; for(std::size_t i = 6*33; i != 9*33; ++i) colorData[i + 1] = 0xffff99_rgbf; From c07af0340f873abfe4288e213d37c0ef23745577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 18 Jan 2020 19:40:16 +0100 Subject: [PATCH 063/107] MeshTools: port tests and benchmarks away from MeshData3D. --- .../MeshTools/Test/GenerateNormalsTest.cpp | 11 +++-- .../SubdivideRemoveDuplicatesBenchmark.cpp | 48 +++++++++++++++---- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp index bc80b787a9..38bbd3892e 100644 --- a/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp +++ b/src/Magnum/MeshTools/Test/GenerateNormalsTest.cpp @@ -37,7 +37,7 @@ #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/GenerateNormals.h" #include "Magnum/Primitives/Cylinder.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { @@ -331,13 +331,14 @@ void GenerateNormalsTest::smoothBeveledCube() { } void GenerateNormalsTest::smoothCylinder() { - const Trade::MeshData3D data = Primitives::cylinderSolid(1, 5, 1.0f); + const Trade::MeshData data = Primitives::cylinderSolid(1, 5, 1.0f); /* Output should be exactly the same as the cylinder normals */ CORRADE_COMPARE_AS(Containers::arrayView(generateSmoothNormals( - Containers::stridedArrayView(data.indices()), - Containers::stridedArrayView(data.positions(0)))), - Containers::arrayView(data.normals(0)), TestSuite::Compare::Container); + data.indices(), + data.attribute(Trade::MeshAttribute::Position))), + data.attribute(Trade::MeshAttribute::Normal), + TestSuite::Compare::Container); } void GenerateNormalsTest::smoothZeroAreaTriangle() { diff --git a/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp b/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp index 7e68de8814..4d6f5ab508 100644 --- a/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp +++ b/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp @@ -23,14 +23,16 @@ DEALINGS IN THE SOFTWARE. */ +#include #include +#include #include "Magnum/Math/Vector4.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Subdivide.h" #include "Magnum/Primitives/Icosphere.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { namespace Test { namespace { @@ -57,43 +59,69 @@ namespace { } void SubdivideRemoveDuplicatesBenchmark::subdivide() { + Trade::MeshData icosphere = Primitives::icosphereSolid(0); + CORRADE_BENCHMARK(3) { - Trade::MeshData3D icosphere = Primitives::icosphereSolid(0); + Containers::Array indices; + arrayResize(indices, Containers::NoInit, icosphere.indexCount()); + Utility::copy(icosphere.indices(), indices); + + Containers::Array positions; + arrayResize(positions, Containers::NoInit, icosphere.vertexCount()); + Utility::copy(icosphere.attribute(Trade::MeshAttribute::Position), positions); /* Subdivide 5 times */ for(std::size_t i = 0; i != 5; ++i) - MeshTools::subdivide(icosphere.indices(), icosphere.positions(0), interpolator); + MeshTools::subdivide(indices, positions, interpolator); } } void SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesAfter() { + Trade::MeshData icosphere = Primitives::icosphereSolid(0); + CORRADE_BENCHMARK(3) { - Trade::MeshData3D icosphere = Primitives::icosphereSolid(0); + Containers::Array indices; + arrayResize(indices, Containers::NoInit, icosphere.indexCount()); + Utility::copy(icosphere.indices(), indices); + + Containers::Array positions; + arrayResize(positions, Containers::NoInit, icosphere.vertexCount()); + Utility::copy(icosphere.attribute(Trade::MeshAttribute::Position), positions); /* Subdivide 5 times */ for(std::size_t i = 0; i != 5; ++i) - MeshTools::subdivide(icosphere.indices(), icosphere.positions(0), interpolator); + MeshTools::subdivide(indices, positions, interpolator); /* Remove duplicates after */ - icosphere.indices() = MeshTools::duplicate(icosphere.indices(), MeshTools::removeDuplicates(icosphere.positions(0))); + arrayResize(positions, MeshTools::removeDuplicatesIndexedInPlace( + stridedArrayView(indices), stridedArrayView(positions))); } } void SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesAfterInPlace() { CORRADE_BENCHMARK(3) { /* Because that's what this thing does */ - Trade::MeshData3D icosphere = Primitives::icosphereSolid(5); + Trade::MeshData icosphere = Primitives::icosphereSolid(5); } } void SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesInBetween() { + Trade::MeshData icosphere = Primitives::icosphereSolid(0); + CORRADE_BENCHMARK(3) { - Trade::MeshData3D icosphere = Primitives::icosphereSolid(0); + Containers::Array indices; + arrayResize(indices, Containers::NoInit, icosphere.indexCount()); + Utility::copy(icosphere.indices(), indices); + + Containers::Array positions; + arrayResize(positions, Containers::NoInit, icosphere.vertexCount()); + Utility::copy(icosphere.attribute(Trade::MeshAttribute::Position), positions); /* Subdivide 5 times and remove duplicates during the operation */ for(std::size_t i = 0; i != 5; ++i) { - MeshTools::subdivide(icosphere.indices(), icosphere.positions(0), interpolator); - icosphere.indices() = MeshTools::duplicate(icosphere.indices(), MeshTools::removeDuplicates(icosphere.positions(0))); + MeshTools::subdivide(indices, positions, interpolator); + arrayResize(positions, MeshTools::removeDuplicatesIndexedInPlace( + stridedArrayView(indices), stridedArrayView(positions))); } } } From 35659a6d15ef0bc49092e16d4453f8ce6d5199a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 18 Jan 2020 19:40:44 +0100 Subject: [PATCH 064/107] DebugTools: port away from MeshDataXD. --- src/Magnum/DebugTools/ObjectRenderer.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/Magnum/DebugTools/ObjectRenderer.cpp b/src/Magnum/DebugTools/ObjectRenderer.cpp index 018ff20556..276ffd2680 100644 --- a/src/Magnum/DebugTools/ObjectRenderer.cpp +++ b/src/Magnum/DebugTools/ObjectRenderer.cpp @@ -31,8 +31,7 @@ #include "Magnum/Primitives/Axis.h" #include "Magnum/SceneGraph/Camera.h" #include "Magnum/Shaders/VertexColor.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace DebugTools { @@ -43,13 +42,13 @@ template struct Renderer; template<> struct Renderer<2> { static ResourceKey shader() { return {"VertexColorShader2D"}; } static ResourceKey mesh() { return {"object2d"}; } - static Trade::MeshData2D meshData() { return Primitives::axis2D(); } + static Trade::MeshData meshData() { return Primitives::axis2D(); } }; template<> struct Renderer<3> { static ResourceKey shader() { return {"VertexColorShader3D"}; } static ResourceKey mesh() { return {"object3d"}; } - static Trade::MeshData3D meshData() { return Primitives::axis3D(); } + static Trade::MeshData meshData() { return Primitives::axis3D(); } }; } From 53ef991c44adfd990dadfb4d79cd34e9ff746765 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 18 Jan 2020 20:36:04 +0100 Subject: [PATCH 065/107] SceneGraph: port away from MeshDataXD. --- doc/snippets/MagnumSceneGraph-gl.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/snippets/MagnumSceneGraph-gl.cpp b/doc/snippets/MagnumSceneGraph-gl.cpp index 2f1a429bb5..bf5f4a5914 100644 --- a/doc/snippets/MagnumSceneGraph-gl.cpp +++ b/doc/snippets/MagnumSceneGraph-gl.cpp @@ -41,7 +41,7 @@ #include "Magnum/SceneGraph/Scene.h" #include "Magnum/Shaders/Flat.h" #include "Magnum/Shaders/Phong.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" using namespace Magnum; using namespace Magnum::Math::Literals; From 274fdc3813334754a7d6e4b2993ed1d37e66b391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 19 Jan 2020 21:42:13 +0100 Subject: [PATCH 066/107] GL: port away from MeshDataXD. --- doc/snippets/MagnumGL.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/doc/snippets/MagnumGL.cpp b/doc/snippets/MagnumGL.cpp index b6533726b0..3dfb6f426b 100644 --- a/doc/snippets/MagnumGL.cpp +++ b/doc/snippets/MagnumGL.cpp @@ -23,6 +23,7 @@ DEALINGS IN THE SOFTWARE. */ +#include /* for std::tie() :( */ #include #include #include @@ -52,7 +53,7 @@ #include "Magnum/Primitives/Cube.h" #include "Magnum/Primitives/Plane.h" #include "Magnum/Shaders/Phong.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #if !(defined(MAGNUM_TARGET_GLES2) && defined(MAGNUM_TARGET_WEBGL)) #include "Magnum/GL/SampleQuery.h" @@ -1019,16 +1020,17 @@ mesh.setPrimitive(MeshPrimitive::Triangles) { /* [Mesh-interleaved] */ /* Non-indexed primitive with positions and normals */ -Trade::MeshData3D plane = Primitives::planeSolid(); +Trade::MeshData plane = Primitives::planeSolid(); /* Fill a vertex buffer with interleaved position and normal data */ GL::Buffer buffer; -buffer.setData(MeshTools::interleave(plane.positions(0), plane.normals(0)), GL::BufferUsage::StaticDraw); +buffer.setData(MeshTools::interleave(plane.positions3DAsArray(), + plane.normalsAsArray())); /* Configure the mesh, add the vertex buffer */ GL::Mesh mesh; mesh.setPrimitive(plane.primitive()) - .setCount(plane.positions(0).size()) + .setCount(plane.vertexCount()) .addVertexBuffer(buffer, 0, Shaders::Phong::Position{}, Shaders::Phong::Normal{}); /* [Mesh-interleaved] */ } @@ -1048,14 +1050,14 @@ Vector3 positions[240]{ // ... }; GL::Buffer vertexBuffer; -vertexBuffer.setData(positions, GL::BufferUsage::StaticDraw); +vertexBuffer.setData(positions); /* Fill index buffer with index data */ UnsignedByte indices[75]{ // ... }; GL::Buffer indexBuffer; -indexBuffer.setData(indices, GL::BufferUsage::StaticDraw); +indexBuffer.setData(indices); /* Configure the mesh, add both vertex and index buffer */ GL::Mesh mesh; @@ -1069,28 +1071,28 @@ mesh.setPrimitive(MeshPrimitive::Triangles) { /* [Mesh-indexed-tools] */ // Indexed primitive -Trade::MeshData3D cube = Primitives::cubeSolid(); +Trade::MeshData cube = Primitives::cubeSolid(); // Fill vertex buffer with interleaved position and normal data GL::Buffer vertexBuffer; -vertexBuffer.setData(MeshTools::interleave(cube.positions(0), cube.normals(0)), GL::BufferUsage::StaticDraw); +vertexBuffer.setData(MeshTools::interleave(cube.positions3DAsArray(), + cube.normalsAsArray())); // Compress index data Containers::Array indexData; MeshIndexType indexType; -UnsignedInt indexStart, indexEnd; -std::tie(indexData, indexType, indexStart, indexEnd) = MeshTools::compressIndices(cube.indices()); +std::tie(indexData, indexType) = MeshTools::compressIndices(cube.indices()); // Fill index buffer GL::Buffer indexBuffer; -indexBuffer.setData(indexData, GL::BufferUsage::StaticDraw); +indexBuffer.setData(indexData); // Configure the mesh, add both vertex and index buffer GL::Mesh mesh; mesh.setPrimitive(cube.primitive()) - .setCount(cube.indices().size()) + .setCount(cube.indexCount()) .addVertexBuffer(vertexBuffer, 0, Shaders::Phong::Position{}, Shaders::Phong::Normal{}) - .setIndexBuffer(indexBuffer, 0, indexType, indexStart, indexEnd); + .setIndexBuffer(indexBuffer, 0, indexType); /* [Mesh-indexed-tools] */ } From 84ee4f3cc77bef8934f5552ac385ce8fc40c65ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 19 Feb 2020 13:08:03 +0100 Subject: [PATCH 067/107] Add packed, half and double types to VertexFormat. Intentionally not enabling / documenting the double types for use with positions / normals / ... yet. Might come later (or never). --- .../Implementation/vertexFormatMapping.hpp | 42 +++ src/Magnum/Test/VertexFormatTest.cpp | 157 +++++++++ src/Magnum/VertexFormat.cpp | 301 ++++++++++++++++- src/Magnum/VertexFormat.h | 309 +++++++++++++++++- 4 files changed, 797 insertions(+), 12 deletions(-) diff --git a/src/Magnum/Implementation/vertexFormatMapping.hpp b/src/Magnum/Implementation/vertexFormatMapping.hpp index 405eaa1bed..2b2dc2b453 100644 --- a/src/Magnum/Implementation/vertexFormatMapping.hpp +++ b/src/Magnum/Implementation/vertexFormatMapping.hpp @@ -26,13 +26,55 @@ /* Each entry is just the name, for debug output and configuration to string */ #ifdef _c _c(Float) +_c(Half) +_c(Double) _c(UnsignedByte) +_c(UnsignedByteNormalized) _c(Byte) +_c(ByteNormalized) _c(UnsignedShort) +_c(UnsignedShortNormalized) _c(Short) +_c(ShortNormalized) _c(UnsignedInt) _c(Int) _c(Vector2) +_c(Vector2h) +_c(Vector2d) +_c(Vector2ub) +_c(Vector2ubNormalized) +_c(Vector2b) +_c(Vector2bNormalized) +_c(Vector2us) +_c(Vector2usNormalized) +_c(Vector2s) +_c(Vector2sNormalized) +_c(Vector2ui) +_c(Vector2i) _c(Vector3) +_c(Vector3h) +_c(Vector3d) +_c(Vector3ub) +_c(Vector3ubNormalized) +_c(Vector3b) +_c(Vector3bNormalized) +_c(Vector3us) +_c(Vector3usNormalized) +_c(Vector3s) +_c(Vector3sNormalized) +_c(Vector3ui) +_c(Vector3i) _c(Vector4) +_c(Vector4h) +_c(Vector4d) +_c(Vector4ub) +_c(Vector4ubNormalized) +_c(Vector4b) +_c(Vector4bNormalized) +_c(Vector4us) +_c(Vector4usNormalized) +_c(Vector4s) +_c(Vector4sNormalized) +_c(Vector4ui) +_c(Vector4i) #endif diff --git a/src/Magnum/Test/VertexFormatTest.cpp b/src/Magnum/Test/VertexFormatTest.cpp index a95fef885b..635225c92a 100644 --- a/src/Magnum/Test/VertexFormatTest.cpp +++ b/src/Magnum/Test/VertexFormatTest.cpp @@ -40,16 +40,59 @@ struct VertexFormatTest: TestSuite::Tester { void size(); void sizeInvalid(); + void componentCount(); + void componentCountInvalid(); + void componentFormat(); + void componentFormatInvalid(); + void isNormalized(); + void isNormalizedInvalid(); + + void assemble(); + void assembleRoundtrip(); + void assembleCantNormalize(); + void assembleInvalidComponentCount(); void debug(); void configuration(); }; +constexpr struct { + VertexFormat componentType; + bool normalized; +} CombineRoundtripData[] { + {VertexFormat::Float, false}, + {VertexFormat::Double, false}, + {VertexFormat::UnsignedByte, false}, + {VertexFormat::UnsignedByte, true}, + {VertexFormat::Byte, false}, + {VertexFormat::Byte, true}, + {VertexFormat::UnsignedShort, false}, + {VertexFormat::UnsignedShort, true}, + {VertexFormat::Short, false}, + {VertexFormat::Short, true}, + {VertexFormat::UnsignedInt, false}, + {VertexFormat::Int, false} +}; + VertexFormatTest::VertexFormatTest() { addTests({&VertexFormatTest::mapping, &VertexFormatTest::size, &VertexFormatTest::sizeInvalid, + &VertexFormatTest::componentCount, + &VertexFormatTest::componentCountInvalid, + &VertexFormatTest::componentFormat, + &VertexFormatTest::componentFormatInvalid, + &VertexFormatTest::isNormalized, + &VertexFormatTest::isNormalizedInvalid, + + &VertexFormatTest::assemble}); + + addRepeatedInstancedTests({&VertexFormatTest::assembleRoundtrip}, 4, + Containers::arraySize(CombineRoundtripData)); + + addTests({&VertexFormatTest::assembleCantNormalize, + &VertexFormatTest::assembleInvalidComponentCount, &VertexFormatTest::debug, &VertexFormatTest::configuration}); @@ -112,6 +155,120 @@ void VertexFormatTest::sizeInvalid() { "vertexFormatSize(): invalid format VertexFormat(0xdead)\n"); } +void VertexFormatTest::componentCount() { + CORRADE_COMPARE(Magnum::vertexFormatComponentCount(VertexFormat::UnsignedByteNormalized), 1); + CORRADE_COMPARE(Magnum::vertexFormatComponentCount(VertexFormat::Vector2us), 2); + CORRADE_COMPARE(Magnum::vertexFormatComponentCount(VertexFormat::Vector3bNormalized), 3); + CORRADE_COMPARE(Magnum::vertexFormatComponentCount(VertexFormat::Vector4), 4); +} + +void VertexFormatTest::componentCountInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + Magnum::vertexFormatComponentCount(VertexFormat{}); + Magnum::vertexFormatComponentCount(VertexFormat(0xdead)); + + CORRADE_COMPARE(out.str(), + "vertexFormatComponentCount(): invalid format VertexFormat(0x0)\n" + "vertexFormatComponentCount(): invalid format VertexFormat(0xdead)\n"); +} + +void VertexFormatTest::componentFormat() { + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector4), VertexFormat::Float); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector3h), VertexFormat::Half); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector2d), VertexFormat::Double); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::UnsignedByte), VertexFormat::UnsignedByte); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::UnsignedByteNormalized), VertexFormat::UnsignedByte); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector3bNormalized), VertexFormat::Byte); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector2us), VertexFormat::UnsignedShort); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector2sNormalized), VertexFormat::Short); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector2ui), VertexFormat::UnsignedInt); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector3i), VertexFormat::Int); +} + +void VertexFormatTest::componentFormatInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + Magnum::vertexFormatComponentFormat(VertexFormat{}); + Magnum::vertexFormatComponentFormat(VertexFormat(0xdead)); + + CORRADE_COMPARE(out.str(), + "vertexFormatComponentType(): invalid format VertexFormat(0x0)\n" + "vertexFormatComponentType(): invalid format VertexFormat(0xdead)\n"); +} + +void VertexFormatTest::isNormalized() { + CORRADE_VERIFY(isVertexFormatNormalized(VertexFormat::UnsignedByteNormalized)); + CORRADE_VERIFY(!isVertexFormatNormalized(VertexFormat::Vector2us)); + CORRADE_VERIFY(isVertexFormatNormalized(VertexFormat::Vector3bNormalized)); + CORRADE_VERIFY(!isVertexFormatNormalized(VertexFormat::Vector4)); +} + +void VertexFormatTest::isNormalizedInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + isVertexFormatNormalized(VertexFormat{}); + isVertexFormatNormalized(VertexFormat(0xdead)); + + CORRADE_COMPARE(out.str(), + "isVertexFormatNormalized(): invalid format VertexFormat(0x0)\n" + "isVertexFormatNormalized(): invalid format VertexFormat(0xdead)\n"); +} + +void VertexFormatTest::assemble() { + CORRADE_COMPARE(vertexFormat(VertexFormat::UnsignedShort, 3, true), + VertexFormat::Vector3usNormalized); + CORRADE_COMPARE(vertexFormat(VertexFormat::Int, 4, false), + VertexFormat::Vector4i); + CORRADE_COMPARE(vertexFormat(VertexFormat::Double, 1, false), + VertexFormat::Double); + CORRADE_COMPARE(vertexFormat(VertexFormat::Byte, 1, true), + VertexFormat::ByteNormalized); + + /* Non-scalar types allowed too, as that makes the internal checking + much simpler than when requiring the type to be scalar non-normalized */ + CORRADE_COMPARE(vertexFormat(VertexFormat::Vector4bNormalized, 2, false), + VertexFormat::Vector2b); + CORRADE_COMPARE(vertexFormat(VertexFormat::Vector3h, 2, false), + VertexFormat::Vector2h); +} + +void VertexFormatTest::assembleRoundtrip() { + auto&& data = CombineRoundtripData[testCaseInstanceId()]; + + std::ostringstream out; + { + Debug d{&out, Debug::Flag::NoNewlineAtTheEnd}; + d << data.componentType; + if(data.normalized) d << Debug::nospace << ", normalized"; + } + setTestCaseDescription(out.str()); + + VertexFormat result = vertexFormat(data.componentType, testCaseRepeatId() + 1, data.normalized); + CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(result), data.componentType); + CORRADE_COMPARE(Magnum::vertexFormatComponentCount(result), testCaseRepeatId() + 1); + CORRADE_COMPARE(isVertexFormatNormalized(result), data.normalized); +} + +void VertexFormatTest::assembleCantNormalize() { + std::ostringstream out; + Error redirectError{&out}; + vertexFormat(VertexFormat::Vector2, 1, true); + CORRADE_COMPARE(out.str(), + "vertexFormat(): VertexFormat::Vector2 can't be made normalized\n"); +} + +void VertexFormatTest::assembleInvalidComponentCount() { + std::ostringstream out; + Error redirectError{&out}; + vertexFormat(VertexFormat::Vector3, 5, false); + CORRADE_COMPARE(out.str(), + "vertexFormat(): invalid component count 5\n"); +} + void VertexFormatTest::debug() { std::ostringstream o; Debug(&o) << VertexFormat::Vector4 << VertexFormat(0xdead); diff --git a/src/Magnum/VertexFormat.cpp b/src/Magnum/VertexFormat.cpp index 344c35c2ac..cfe0ac031a 100644 --- a/src/Magnum/VertexFormat.cpp +++ b/src/Magnum/VertexFormat.cpp @@ -35,23 +35,318 @@ namespace Magnum { UnsignedInt vertexFormatSize(const VertexFormat format) { switch(format) { case VertexFormat::UnsignedByte: + case VertexFormat::UnsignedByteNormalized: case VertexFormat::Byte: + case VertexFormat::ByteNormalized: return 1; + case VertexFormat::Half: case VertexFormat::UnsignedShort: + case VertexFormat::UnsignedShortNormalized: case VertexFormat::Short: + case VertexFormat::ShortNormalized: + case VertexFormat::Vector2ub: + case VertexFormat::Vector2ubNormalized: + case VertexFormat::Vector2b: + case VertexFormat::Vector2bNormalized: return 2; + case VertexFormat::Vector3ub: + case VertexFormat::Vector3ubNormalized: + case VertexFormat::Vector3b: + case VertexFormat::Vector3bNormalized: + return 3; case VertexFormat::Float: case VertexFormat::UnsignedInt: case VertexFormat::Int: + case VertexFormat::Vector2h: + case VertexFormat::Vector2us: + case VertexFormat::Vector2usNormalized: + case VertexFormat::Vector2s: + case VertexFormat::Vector2sNormalized: + case VertexFormat::Vector4ub: + case VertexFormat::Vector4ubNormalized: + case VertexFormat::Vector4b: + case VertexFormat::Vector4bNormalized: return 4; - case VertexFormat::Vector2: return 8; - case VertexFormat::Vector3: return 12; - case VertexFormat::Vector4: return 16; + case VertexFormat::Vector3h: + case VertexFormat::Vector3us: + case VertexFormat::Vector3usNormalized: + case VertexFormat::Vector3s: + case VertexFormat::Vector3sNormalized: + return 6; + case VertexFormat::Double: + case VertexFormat::Vector2: + case VertexFormat::Vector2ui: + case VertexFormat::Vector2i: + case VertexFormat::Vector4h: + case VertexFormat::Vector4us: + case VertexFormat::Vector4usNormalized: + case VertexFormat::Vector4s: + case VertexFormat::Vector4sNormalized: + return 8; + case VertexFormat::Vector3: + case VertexFormat::Vector3ui: + case VertexFormat::Vector3i: + return 12; + case VertexFormat::Vector2d: + case VertexFormat::Vector4: + case VertexFormat::Vector4ui: + case VertexFormat::Vector4i: + return 16; + case VertexFormat::Vector3d: + return 24; + case VertexFormat::Vector4d: + return 32; } CORRADE_ASSERT(false, "vertexFormatSize(): invalid format" << format, {}); } +UnsignedInt vertexFormatComponentCount(const VertexFormat format) { + switch(format) { + case VertexFormat::Float: + case VertexFormat::Half: + case VertexFormat::Double: + case VertexFormat::UnsignedByte: + case VertexFormat::UnsignedByteNormalized: + case VertexFormat::Byte: + case VertexFormat::ByteNormalized: + case VertexFormat::UnsignedShort: + case VertexFormat::UnsignedShortNormalized: + case VertexFormat::Short: + case VertexFormat::ShortNormalized: + case VertexFormat::UnsignedInt: + case VertexFormat::Int: + return 1; + + case VertexFormat::Vector2: + case VertexFormat::Vector2h: + case VertexFormat::Vector2d: + case VertexFormat::Vector2ub: + case VertexFormat::Vector2ubNormalized: + case VertexFormat::Vector2b: + case VertexFormat::Vector2bNormalized: + case VertexFormat::Vector2us: + case VertexFormat::Vector2usNormalized: + case VertexFormat::Vector2s: + case VertexFormat::Vector2sNormalized: + case VertexFormat::Vector2ui: + case VertexFormat::Vector2i: + return 2; + + case VertexFormat::Vector3: + case VertexFormat::Vector3h: + case VertexFormat::Vector3d: + case VertexFormat::Vector3ub: + case VertexFormat::Vector3ubNormalized: + case VertexFormat::Vector3b: + case VertexFormat::Vector3bNormalized: + case VertexFormat::Vector3us: + case VertexFormat::Vector3usNormalized: + case VertexFormat::Vector3s: + case VertexFormat::Vector3sNormalized: + case VertexFormat::Vector3ui: + case VertexFormat::Vector3i: + return 3; + + case VertexFormat::Vector4: + case VertexFormat::Vector4h: + case VertexFormat::Vector4d: + case VertexFormat::Vector4ub: + case VertexFormat::Vector4ubNormalized: + case VertexFormat::Vector4b: + case VertexFormat::Vector4bNormalized: + case VertexFormat::Vector4us: + case VertexFormat::Vector4usNormalized: + case VertexFormat::Vector4s: + case VertexFormat::Vector4sNormalized: + case VertexFormat::Vector4ui: + case VertexFormat::Vector4i: + return 4; + } + + CORRADE_ASSERT(false, "vertexFormatComponentCount(): invalid format" << format, {}); +} + +VertexFormat vertexFormatComponentFormat(const VertexFormat format) { + switch(format) { + case VertexFormat::Float: + case VertexFormat::Vector2: + case VertexFormat::Vector3: + case VertexFormat::Vector4: + return VertexFormat::Float; + + case VertexFormat::Half: + case VertexFormat::Vector2h: + case VertexFormat::Vector3h: + case VertexFormat::Vector4h: + return VertexFormat::Half; + + case VertexFormat::Double: + case VertexFormat::Vector2d: + case VertexFormat::Vector3d: + case VertexFormat::Vector4d: + return VertexFormat::Double; + + case VertexFormat::UnsignedByte: + case VertexFormat::UnsignedByteNormalized: + case VertexFormat::Vector2ub: + case VertexFormat::Vector2ubNormalized: + case VertexFormat::Vector3ub: + case VertexFormat::Vector3ubNormalized: + case VertexFormat::Vector4ub: + case VertexFormat::Vector4ubNormalized: + return VertexFormat::UnsignedByte; + + case VertexFormat::Byte: + case VertexFormat::ByteNormalized: + case VertexFormat::Vector2b: + case VertexFormat::Vector2bNormalized: + case VertexFormat::Vector3b: + case VertexFormat::Vector3bNormalized: + case VertexFormat::Vector4b: + case VertexFormat::Vector4bNormalized: + return VertexFormat::Byte; + + case VertexFormat::UnsignedShort: + case VertexFormat::UnsignedShortNormalized: + case VertexFormat::Vector2us: + case VertexFormat::Vector2usNormalized: + case VertexFormat::Vector3us: + case VertexFormat::Vector3usNormalized: + case VertexFormat::Vector4us: + case VertexFormat::Vector4usNormalized: + return VertexFormat::UnsignedShort; + + case VertexFormat::Short: + case VertexFormat::ShortNormalized: + case VertexFormat::Vector2s: + case VertexFormat::Vector2sNormalized: + case VertexFormat::Vector3s: + case VertexFormat::Vector3sNormalized: + case VertexFormat::Vector4s: + case VertexFormat::Vector4sNormalized: + return VertexFormat::Short; + + case VertexFormat::UnsignedInt: + case VertexFormat::Vector2ui: + case VertexFormat::Vector3ui: + case VertexFormat::Vector4ui: + return VertexFormat::UnsignedInt; + + case VertexFormat::Int: + case VertexFormat::Vector2i: + case VertexFormat::Vector3i: + case VertexFormat::Vector4i: + return VertexFormat::Int; + } + + CORRADE_ASSERT(false, "vertexFormatComponentType(): invalid format" << format, {}); +} + +bool isVertexFormatNormalized(const VertexFormat format) { + switch(format) { + case VertexFormat::Float: + case VertexFormat::Half: + case VertexFormat::Double: + case VertexFormat::UnsignedByte: + case VertexFormat::Byte: + case VertexFormat::UnsignedShort: + case VertexFormat::Short: + case VertexFormat::UnsignedInt: + case VertexFormat::Int: + case VertexFormat::Vector2: + case VertexFormat::Vector2h: + case VertexFormat::Vector2d: + case VertexFormat::Vector2ub: + case VertexFormat::Vector2b: + case VertexFormat::Vector2us: + case VertexFormat::Vector2s: + case VertexFormat::Vector2ui: + case VertexFormat::Vector2i: + case VertexFormat::Vector3: + case VertexFormat::Vector3h: + case VertexFormat::Vector3d: + case VertexFormat::Vector3ub: + case VertexFormat::Vector3b: + case VertexFormat::Vector3us: + case VertexFormat::Vector3s: + case VertexFormat::Vector3ui: + case VertexFormat::Vector3i: + case VertexFormat::Vector4: + case VertexFormat::Vector4h: + case VertexFormat::Vector4d: + case VertexFormat::Vector4ub: + case VertexFormat::Vector4b: + case VertexFormat::Vector4us: + case VertexFormat::Vector4s: + case VertexFormat::Vector4ui: + case VertexFormat::Vector4i: + return false; + + case VertexFormat::UnsignedByteNormalized: + case VertexFormat::ByteNormalized: + case VertexFormat::UnsignedShortNormalized: + case VertexFormat::ShortNormalized: + case VertexFormat::Vector2ubNormalized: + case VertexFormat::Vector2bNormalized: + case VertexFormat::Vector2usNormalized: + case VertexFormat::Vector2sNormalized: + case VertexFormat::Vector3ubNormalized: + case VertexFormat::Vector3bNormalized: + case VertexFormat::Vector3usNormalized: + case VertexFormat::Vector3sNormalized: + case VertexFormat::Vector4ubNormalized: + case VertexFormat::Vector4bNormalized: + case VertexFormat::Vector4usNormalized: + case VertexFormat::Vector4sNormalized: + return true; + } + + CORRADE_ASSERT(false, "isVertexFormatNormalized(): invalid format" << format, {}); +} + +VertexFormat vertexFormat(const VertexFormat format, UnsignedInt componentCount, bool normalized) { + VertexFormat componentFormat = vertexFormatComponentFormat(format); + + /* First turn the format into a normalized one, if requested */ + if(normalized) { + switch(componentFormat) { + case VertexFormat::UnsignedByte: + componentFormat = VertexFormat::UnsignedByteNormalized; + break; + case VertexFormat::Byte: + componentFormat = VertexFormat::ByteNormalized; + break; + case VertexFormat::UnsignedShort: + componentFormat = VertexFormat::UnsignedShortNormalized; + break; + case VertexFormat::Short: + componentFormat = VertexFormat::ShortNormalized; + break; + default: CORRADE_ASSERT(false, + "vertexFormat():" << format << "can't be made normalized", {}); + } + } + + /* Then turn them into desired component count, assuming the initial order + is the same in all cases */ + if(componentCount == 1) + return componentFormat; + else if(componentCount == 2) + return VertexFormat(UnsignedInt(VertexFormat::Vector2) + + UnsignedInt(componentFormat) - UnsignedInt(VertexFormat::Float)); + else if(componentCount == 3) + return VertexFormat(UnsignedInt(VertexFormat::Vector3) + + UnsignedInt(componentFormat) - UnsignedInt(VertexFormat::Float)); + else if(componentCount == 4) + return VertexFormat(UnsignedInt(VertexFormat::Vector4) + + UnsignedInt(componentFormat) - UnsignedInt(VertexFormat::Float)); + else CORRADE_ASSERT(false, + "vertexFormat(): invalid component count" << componentCount, {}); + + CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + namespace { constexpr const char* VertexFormatNames[] { diff --git a/src/Magnum/VertexFormat.h b/src/Magnum/VertexFormat.h index ce7d3120a0..6b66aa88e9 100644 --- a/src/Magnum/VertexFormat.h +++ b/src/Magnum/VertexFormat.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Enum @ref Magnum::VertexFormat, function @ref Magnum::vertexFormatSize() + * @brief Enum @ref Magnum::VertexFormat, @ref Magnum::vertexFormatSize(), @ref Magnum::vertexFormatComponentCount(), @ref Magnum::vertexFormatComponentFormat(), @ref Magnum::isVertexFormatNormalized() */ #include @@ -48,37 +48,328 @@ types and matrices. enum class VertexFormat: UnsignedInt { /* Zero reserved for an invalid type (but not being a named value) */ - Float = 1, /**< @ref Float */ - UnsignedByte, /**< @ref UnsignedByte */ - Byte, /**< @ref Byte */ - UnsignedShort, /**< @ref UnsignedShort */ - Short, /**< @ref Short */ - UnsignedInt, /**< @ref UnsignedInt */ - Int, /**< @ref Int */ + /** @ref Float */ + Float = 1, + + /** @ref Half */ + Half, + + /** @ref Double */ + Double, + + /** @ref UnsignedByte */ + UnsignedByte, + + /** + * @ref UnsignedByte, with range @f$ [0, 255] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. + */ + UnsignedByteNormalized, + + /** @ref Byte */ + Byte, + + /** + * @ref Byte, with range @f$ [-127, 127] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. + */ + ByteNormalized, + + /** @ref UnsignedShort */ + UnsignedShort, + + /** + * @ref UnsignedShort, with range @f$ [0, 65535] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. + */ + UnsignedShortNormalized, + + /** @ref Short */ + Short, + + /** + * @ref Short, with range @f$ [-32767, 32767] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. + */ + ShortNormalized, + + /** @ref UnsignedInt */ + UnsignedInt, + + /** @ref Int */ + Int, /** * @ref Vector2. Usually used for 2D positions and 2D texture coordinates. */ Vector2, + /** + * @ref Vector2h. Can be used instead of @ref VertexFormat::Vector2 for 2D + * positions and 2D texture coordinates. + */ + Vector2h, + + /** @ref Vector2d */ + Vector2d, + + /** + * @ref Vector2ub. Can be used instead of @ref VertexFormat::Vector2 for + * packed 2D positions and 2D texture coordinates, in which case the range + * @f$ [0, 255] @f$ is interpreted as @f$ [0.0, 255.0] @f$. + */ + Vector2ub, + + /** + * @ref Vector2ub, with range @f$ [0, 255] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 + * for packed 2D positions and 2D texture coordinates. + */ + Vector2ubNormalized, + + /** + * @ref Vector2b. Can be used instead of @ref VertexFormat::Vector2 for + * packed 2D positions and 2D texture coordinates, in which case the range + * @f$ [-128, 127] @f$ is interpreted as @f$ [-128.0, 127.0] @f$. + */ + Vector2b, + + /** + * @ref Vector2b, with range @f$ [-127, 127] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 + * for packed 2D positions and 2D texture coordinates. + */ + Vector2bNormalized, + + /** + * @ref Vector2us. Can be used instead of @ref VertexFormat::Vector2 for + * packed 2D positions and 2D texture coordinates, in which case the range + * @f$ [0, 65535] @f$ is interpreted as @f$ [0.0, 65535.0] @f$. + */ + Vector2us, + + /** + * @ref Vector2us, with range @f$ [0, 65535] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 + * for packed 2D positions and 2D texture coordinates. + */ + Vector2usNormalized, + + /** + * @ref Vector2s. Can be used instead of @ref VertexFormat::Vector2 for + * packed 2D positions and 2D texture coordinates, in which case the range + * @f$ [-32768, 32767] @f$ is interpreted as @f$ [-32768.0, 32767.0] @f$. + */ + Vector2s, + + /** + * @ref Vector2s, with range @f$ [-32767, 32767] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 + * for packed 2D positions and 2D texture coordinates. + */ + Vector2sNormalized, + + /** @ref Vector2ui */ + Vector2ui, + + /** @ref Vector2i */ + Vector2i, + /** * @ref Vector3 or @ref Color3. Usually used for 3D positions, normals and * three-component colors. */ Vector3, + /** + * @ref Vector3h. Can be used instead of @ref VertexFormat::Vector3 for + * packed 3D positions and three-component colors. + */ + Vector3h, + + /** @ref Vector3d */ + Vector3d, + + /** + * @ref Vector3ub. Can be used instead of @ref VertexFormat::Vector3 for + * packed 3D positions, in which case the range @f$ [0, 255] @f$ is + * interpreted as @f$ [0.0, 255.0] @f$. + */ + Vector3ub, + + /** + * @ref Vector3ub, with range @f$ [0, 255] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector3 + * for packed 3D positions and three-component colors. + */ + Vector3ubNormalized, + + /** + * @ref Vector3b. Can be used instead of @ref VertexFormat::Vector3 for + * packed 3D positions, in which case the range @f$ [-128, 127] @f$ is + * interpreted as @f$ [-128.0, 127.0] @f$. + */ + Vector3b, + + /** + * @ref Vector3b, with range @f$ [-127, 127] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. Can be used instead of + * @ref VertexFormat::Vector3 for packed 3D positions and normals. + */ + Vector3bNormalized, + + /** + * @ref Vector3us. Can be used instead of @ref VertexFormat::Vector3 for + * packed 2D positions, in which case the range @f$ [0, 65535] @f$ is + * interpreted as @f$ [0.0, 65535.0] @f$. + */ + Vector3us, + + /** + * @ref Vector3us, with range @f$ [0, 65535] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 + * for packed 3D positions and three-component colors. + */ + Vector3usNormalized, + + /** + * @ref Vector3s. Can be used instead of @ref VertexFormat::Vector3 for + * packed 3D positions, in which case the range @f$ [-32768, 32767] @f$ is + * interpreted as @f$ [-32768.0, 32767.0] @f$. + */ + Vector3s, + + /** + * @ref Vector3s, with range @f$ [-32767, 32767] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector3 + * for packed 3D positions and normals. + */ + Vector3sNormalized, + + /** @ref Vector3ui */ + Vector3ui, + + /** @ref Vector3i */ + Vector3i, + /** * @ref Vector4 or @ref Color4. Usually used for four-component colors. */ - Vector4 + Vector4, + + /** + * @ref Vector4h. Can be used instead of @ref VertexFormat::Vector4 for + * four-component colors. + */ + Vector4h, + + /** @ref Vector4d */ + Vector4d, + + /** @ref Vector4ub */ + Vector4ub, + + /** + * @ref Vector4ub, with range @f$ [0, 255] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector4 + * for packed linear four-component colors. + */ + Vector4ubNormalized, + + /** @ref Vector4b */ + Vector4b, + + /** + * @ref Vector4b, with range @f$ [-127, 127] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. + */ + Vector4bNormalized, + + /** @ref Vector4us */ + Vector4us, + + /** + * @ref Vector4us, with range @f$ [0, 65535] @f$ interpreted as + * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector4 + * for packed linear four-component colors. + */ + Vector4usNormalized, + + /** @ref Vector4s */ + Vector4s, + + /** + * @ref Vector4s, with range @f$ [-32767, 32767] @f$ interpreted as + * @f$ [-1.0, 1.0] @f$. + */ + Vector4sNormalized, + + /** @ref Vector4ui */ + Vector4ui, + + /** @ref Vector4i */ + Vector4i }; /** @brief Size of given vertex format @m_since_latest + +To get size of a single component, call this function on a result of +@ref vertexFormatComponentFormat(). */ MAGNUM_EXPORT UnsignedInt vertexFormatSize(VertexFormat format); +/** +@brief Component format of given vertex format +@m_since_latest + +The function also removes the normalization aspect from the type --- use +@ref isVertexFormatNormalized() to query that. Returns for example +@ref VertexFormat::Short for @ref VertexFormat::ShortNormalized or +@ref VertexFormat::UnsignedByte for @ref VertexFormat::Vector3ub. +Calling @ref vertexFormatComponentCount() on the return value will always +give @cpp 1 @ce; calling @ref isVertexFormatNormalized() on the return +value will always give @cpp false @ce. +@see @ref vertexFormat(VertexFormat, UnsignedInt, bool) +*/ +MAGNUM_EXPORT VertexFormat vertexFormatComponentFormat(VertexFormat format); + +/** +@brief Component count of given vertex format +@m_since_latest + +Returns @cpp 1 @ce for scalar types and e.g. @cpp 3 @ce for +@ref VertexFormat::Vector3ub. +@see @ref vertexFormat(VertexFormat, UnsignedInt, bool) +*/ +MAGNUM_EXPORT UnsignedInt vertexFormatComponentCount(VertexFormat format); + +/** +@brief Component count of given vertex format +@m_since_latest + +Returns @cpp true @ce for `*Normalized` types, @cpp false @ce otherwise. In +particular, floating-point types are *not* treated as normalized, even though +for example colors might commonly have values only in the @f$ [0.0, 1.0] @f$ +range (or normals in the @f$ [-1.0, 1.0] @f$ range). +@see @ref vertexFormat(VertexFormat, UnsignedInt, bool) +*/ +MAGNUM_EXPORT bool isVertexFormatNormalized(VertexFormat format); + +/** +@brief Assemble a vertex format from parts +@m_since_latest + +Converts @p format to a new format of desired component count and +normalization. Expects that @p componentCount is not larger than @cpp 4 @ce and +@p normalized is @cpp true @ce only for 8- and 16-byte integer types. +@see @ref vertexFormatComponentFormat(), + @ref vertexFormatComponentCount(), + @ref isVertexFormatNormalized() +*/ +MAGNUM_EXPORT VertexFormat vertexFormat(VertexFormat format, UnsignedInt componentCount, bool normalized); + /** @debugoperatorenum{VertexFormat} @m_since_latest From ed88b35ec81d81928b68f3e0ef35fb2bf3459840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 20 Feb 2020 15:08:34 +0100 Subject: [PATCH 068/107] Trade: support packed attributes in MeshData. This was a LONG unexpected detour... I mean, I expected it, but not so soon. --- src/Magnum/Trade/MeshData.cpp | 165 +++++++++-- src/Magnum/Trade/MeshData.h | 193 +++++++++++-- src/Magnum/Trade/Test/MeshDataTest.cpp | 366 +++++++++++++++++++++++-- 3 files changed, 662 insertions(+), 62 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index f1e458bb9b..cb930b30d6 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -28,6 +28,7 @@ #include #include "Magnum/Math/Color.h" +#include "Magnum/Math/PackingBatch.h" #include "Magnum/Trade/Implementation/arrayUtilities.h" namespace Magnum { namespace Trade { @@ -365,10 +366,39 @@ void MeshData::positions2DInto(const Containers::StridedArrayView1D des CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; + const auto destination2f = Containers::arrayCast<2, Float>(destination); + /* Copy 2D positions as-is, for 3D positions ignore Z */ if(attribute._format == VertexFormat::Vector2 || attribute._format == VertexFormat::Vector3) Utility::copy(Containers::arrayCast(attribute._data), destination); + else if(attribute._format == VertexFormat::Vector2h || + attribute._format == VertexFormat::Vector3h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2ub || + attribute._format == VertexFormat::Vector3ub) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2b || + attribute._format == VertexFormat::Vector3b) + Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2us || + attribute._format == VertexFormat::Vector3us) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2s || + attribute._format == VertexFormat::Vector3s) + Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2ubNormalized || + attribute._format == VertexFormat::Vector3ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2bNormalized || + attribute._format == VertexFormat::Vector3bNormalized) + Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2usNormalized || + attribute._format == VertexFormat::Vector3usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2sNormalized || + attribute._format == VertexFormat::Vector3sNormalized) + Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -384,19 +414,71 @@ void MeshData::positions3DInto(const Containers::StridedArrayView1D des CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions3DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; - /* For 2D positions copy the XY part to the first two components and then - fill the Z with a single value */ - if(attribute._format == VertexFormat::Vector2) { + const Containers::StridedArrayView2D destination2f = Containers::arrayCast<2, Float>(Containers::arrayCast(destination)); + const Containers::StridedArrayView2D destination3f = Containers::arrayCast<2, Float>(destination); + + /* For 2D positions copy the XY part to the first two components */ + if(attribute._format == VertexFormat::Vector2) Utility::copy(Containers::arrayCast(attribute._data), Containers::arrayCast(destination)); + else if(attribute._format == VertexFormat::Vector2h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2ub) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2b) + Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2us) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2s) + Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2bNormalized) + Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2sNormalized) + Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + + /* Copy 3D positions as-is */ + else if(attribute._format == VertexFormat::Vector3) + Utility::copy(Containers::arrayCast(attribute._data), destination); + else if(attribute._format == VertexFormat::Vector3h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3ub) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3b) + Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3us) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3s) + Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3bNormalized) + Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3sNormalized) + Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 3), destination3f); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + /* For 2D positions finally fill the Z with a single value */ + if(attribute._format == VertexFormat::Vector2 || + attribute._format == VertexFormat::Vector2h || + attribute._format == VertexFormat::Vector2ub || + attribute._format == VertexFormat::Vector2b || + attribute._format == VertexFormat::Vector2us || + attribute._format == VertexFormat::Vector2s || + attribute._format == VertexFormat::Vector2ubNormalized || + attribute._format == VertexFormat::Vector2bNormalized || + attribute._format == VertexFormat::Vector2usNormalized || + attribute._format == VertexFormat::Vector2sNormalized) { constexpr Float z[1]{0.0f}; Utility::copy( Containers::stridedArrayView(z).broadcasted<0>(_vertexCount), - Containers::arrayCast<2, Float>(destination).transposed<0, 1>()[2]); - /* Copy 3D positions as-is */ - } else if(attribute._format == VertexFormat::Vector3) { - Utility::copy(Containers::arrayCast(attribute._data), destination); - } else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + destination3f.transposed<0, 1>()[2]); + } } Containers::Array MeshData::positions3DAsArray(const UnsignedInt id) const { @@ -411,8 +493,16 @@ void MeshData::normalsInto(const Containers::StridedArrayView1D destina CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::normalsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; + const auto destination3f = Containers::arrayCast<2, Float>(destination); + if(attribute._format == VertexFormat::Vector3) Utility::copy(Containers::arrayCast(attribute._data), destination); + else if(attribute._format == VertexFormat::Vector3h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3bNormalized) + Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3sNormalized) + Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 3), destination3f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -428,8 +518,28 @@ void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D(destination); + if(attribute._format == VertexFormat::Vector2) Utility::copy(Containers::arrayCast(attribute._data), destination); + else if(attribute._format == VertexFormat::Vector2h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2ub) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2b) + Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2us) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2s) + Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2bNormalized) + Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + else if(attribute._format == VertexFormat::Vector2sNormalized) + Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -445,20 +555,43 @@ void MeshData::colorsInto(const Containers::StridedArrayView1D destinati CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::colorsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; + const Containers::StridedArrayView2D destination3f = Containers::arrayCast<2, Float>(Containers::arrayCast(destination)); + const Containers::StridedArrayView2D destination4f = Containers::arrayCast<2, Float>(destination); + /* For three-component colors copy the RGB part to the first three - components and then fill the alpha with a single value */ - if(attribute._format == VertexFormat::Vector3) { + components */ + if(attribute._format == VertexFormat::Vector3) Utility::copy(Containers::arrayCast(attribute._data), Containers::arrayCast(destination)); - constexpr Float alpha[1]{1.0f}; - Utility::copy( - Containers::stridedArrayView(alpha).broadcasted<0>(_vertexCount), - Containers::arrayCast<2, Float>(destination).transposed<0, 1>()[3]); + else if(attribute._format == VertexFormat::Vector3h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 3), destination3f); + else if(attribute._format == VertexFormat::Vector3usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + /* Copy four-component colors as-is */ - } else if(attribute._format == VertexFormat::Vector4) { + else if(attribute._format == VertexFormat::Vector4) Utility::copy(Containers::arrayCast(attribute._data), Containers::arrayCast(destination)); - } else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + else if(attribute._format == VertexFormat::Vector4h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 4), destination4f); + else if(attribute._format == VertexFormat::Vector4ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 4), destination4f); + else if(attribute._format == VertexFormat::Vector4usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 4), destination4f); + else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + /* For three-component colors finally fill the alpha with a single value */ + if(attribute._format == VertexFormat::Vector3 || + attribute._format == VertexFormat::Vector3h || + attribute._format == VertexFormat::Vector3ubNormalized || + attribute._format == VertexFormat::Vector3usNormalized) { + constexpr Float alpha[1]{1.0f}; + Utility::copy( + Containers::stridedArrayView(alpha).broadcasted<0>(_vertexCount), + destination4f.transposed<0, 1>()[3]); + } } Containers::Array MeshData::colorsAsArray(const UnsignedInt id) const { diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 3409e5ddcc..4684584831 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -55,37 +55,54 @@ enum class MeshAttribute: UnsignedShort { AbstractImporter::meshAttributeForName()) */ /** - * Position. Type is usually @ref Magnum::Vector2 "Vector2" for 2D and - * @ref Magnum::Vector3 "Vector3" for 3D. Corresponds to - * @ref Shaders::Generic::Position. - * @see @ref VertexFormat::Vector2, @ref VertexFormat::Vector3, - * @ref MeshData::positions2DAsArray(), + * Position. Type is usually @ref VertexFormat::Vector2 for 2D and + * @ref VertexFormat::Vector3 for 3D, but can be also any of + * @ref VertexFormat::Vector2h, @ref VertexFormat::Vector3h, + * @ref VertexFormat::Vector2ub, @ref VertexFormat::Vector2ubNormalized, + * @ref VertexFormat::Vector2b, @ref VertexFormat::Vector2bNormalized, + * @ref VertexFormat::Vector2us, @ref VertexFormat::Vector2usNormalized, + * @ref VertexFormat::Vector2s, @ref VertexFormat::Vector2sNormalized, + * @ref VertexFormat::Vector3ub, @ref VertexFormat::Vector3ubNormalized, + * @ref VertexFormat::Vector3b, @ref VertexFormat::Vector3bNormalized, + * @ref VertexFormat::Vector3us, @ref VertexFormat::Vector3usNormalized, + * @ref VertexFormat::Vector3s or @ref VertexFormat::Vector3sNormalized. + * Corresponds to @ref Shaders::Generic::Position. + * @see @ref MeshData::positions2DAsArray(), * @ref MeshData::positions3DAsArray() */ Position = 1, /** - * Normal. Type is usually @ref Magnum::Vector3 "Vector3". Corresponds to + * Normal. Type is usually @ref VertexFormat::Vector3, but can be also + * @ref VertexFormat::Vector3h. @ref VertexFormat::Vector3bNormalized or + * @ref VertexFormat::Vector3sNormalized. Corresponds to * @ref Shaders::Generic::Normal. - * @see @ref VertexFormat::Vector3, @ref MeshData::normalsAsArray() + * @see @ref MeshData::normalsAsArray() */ Normal, /** - * Texture coordinates. Type is usually @ref Magnum::Vector2 "Vector2" for - * 2D coordinates. Corresponds to @ref Shaders::Generic::TextureCoordinates. - * @see @ref VertexFormat::Vector2, - * @ref MeshData::textureCoordinates2DAsArray() + * Texture coordinates. Type is usually @ref VertexFormat::Vector2 for + * 2D coordinates, but can be also any of @ref VertexFormat::Vector2h, + * @ref VertexFormat::Vector2ub, @ref VertexFormat::Vector2ubNormalized, + * @ref VertexFormat::Vector2b, @ref VertexFormat::Vector2bNormalized, + * @ref VertexFormat::Vector2us, @ref VertexFormat::Vector2usNormalized, + * @ref VertexFormat::Vector2s or @ref VertexFormat::Vector2sNormalized. + * Corresponds to @ref Shaders::Generic::TextureCoordinates. + * @see @ref MeshData::textureCoordinates2DAsArray() */ TextureCoordinates, /** - * Vertex color. Type is usually @ref Magnum::Vector3 "Vector3" or - * @ref Magnum::Vector4 "Vector4" (or @ref Color3 / @ref Color4). - * Corresponds to @ref Shaders::Generic::Color3 or - * @ref Shaders::Generic::Color4. - * @see @ref VertexFormat::Vector3, @ref VertexFormat::Vector4, - * @ref MeshData::colorsAsArray() + * Vertex color. Type is usually @ref VertexFormat::Vector3 or + * @ref VertexFormat::Vector4, but can be also any of + * @ref VertexFormat::Vector3h, @ref VertexFormat::Vector4h, + * @ref VertexFormat::Vector3ubNormalized, + * @ref VertexFormat::Vector3usNormalized, + * @ref VertexFormat::Vector4ubNormalized or + * @ref VertexFormat::Vector4usNormalized. Corresponds to + * @ref Shaders::Generic::Color3 or @ref Shaders::Generic::Color4. + * @see @ref MeshData::colorsAsArray() */ Color, @@ -261,6 +278,27 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * * Detects @ref VertexFormat based on @p T and calls * @ref MeshAttributeData(MeshAttribute, VertexFormat, const Containers::StridedArrayView1D&). + * For most types known by Magnum, the detected @ref VertexFormat is of + * the same name as the type (so e.g. @ref Magnum::Vector3ui "Vector3ui" + * gets recognized as @ref VertexFormat::Vector3ui), with the + * following exceptions: + * + * - @ref Color3ub is recognized as + * @ref VertexFormat::Vector3ubNormalized + * - @ref Color3us is recognized as + * @ref VertexFormat::Vector3usNormalized + * - @ref Color4ub is recognized as + * @ref VertexFormat::Vector4ubNormalized + * - @ref Color4us is recognized as + * @ref VertexFormat::Vector4usNormalized + * + * This also means that if you have a @ref Magnum::Vector2s "Vector2s", + * for example, and want to pick a + * @ref VertexFormat::Vector2sNormalized instead of the + * (autodetected) @ref VertexFormat::Vector2s, you need to specify + * it explicitly --- there's no way the library can infer this from the + * type alone, except for the color types above (which are generally + * always normalized). */ template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept; @@ -1164,6 +1202,7 @@ namespace Implementation { template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedShort; } template<> constexpr MeshIndexType meshIndexTypeFor() { return MeshIndexType::UnsignedInt; } + /* Implicit mapping from a format to enum (1:1) */ template constexpr VertexFormat vertexFormatFor() { /* C++ why there isn't an obvious way to do such a thing?! */ static_assert(sizeof(T) == 0, "unsupported attribute type"); @@ -1173,6 +1212,8 @@ namespace Implementation { #define _c(format) \ template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::format; } _c(Float) + _c(Half) + _c(Double) _c(UnsignedByte) _c(Byte) _c(UnsignedShort) @@ -1180,28 +1221,128 @@ namespace Implementation { _c(UnsignedInt) _c(Int) _c(Vector2) + _c(Vector2h) + _c(Vector2d) + _c(Vector2ub) + _c(Vector2b) + _c(Vector2us) + _c(Vector2s) + _c(Vector2ui) + _c(Vector2i) _c(Vector3) + _c(Vector3h) + _c(Vector3d) + _c(Vector3ub) + _c(Vector3b) + _c(Vector3us) + _c(Vector3s) + _c(Vector3ui) + _c(Vector3i) _c(Vector4) + _c(Vector4h) + _c(Vector4d) + _c(Vector4ub) + _c(Vector4b) + _c(Vector4us) + _c(Vector4s) + _c(Vector4ui) + _c(Vector4i) #undef _c #endif template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector3; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector3h; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector3ubNormalized; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector3usNormalized; } template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector4; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector4h; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector4ubNormalized; } + template<> constexpr VertexFormat vertexFormatFor() { return VertexFormat::Vector4usNormalized; } + + /* Check if enum is compatible with a format (1:n). Mostly just 1:1 mapping + tho, so reusing vertexFormatFor(), with a few exceptions. */ + template constexpr bool isVertexFormatCompatible(VertexFormat type) { + return vertexFormatFor() == type; + } + #ifndef DOXYGEN_GENERATING_OUTPUT + #define _c(format_) \ + template<> constexpr bool isVertexFormatCompatible(VertexFormat format) { \ + return format == VertexFormat::format_ || \ + format == VertexFormat::format_ ## Normalized; \ + } + _c(UnsignedByte) + _c(Byte) + _c(UnsignedShort) + _c(Short) + _c(Vector2ub) + _c(Vector2b) + _c(Vector2us) + _c(Vector2s) + _c(Vector3ub) + _c(Vector3b) + _c(Vector3us) + _c(Vector3s) + _c(Vector4ub) + _c(Vector4b) + _c(Vector4us) + _c(Vector4s) + /* For Color[34]u[sb] we expect the format to be normalized, which is + handled by vertexFormatFor() properly already */ + #undef _c + #endif /* LCOV_EXCL_STOP */ } #endif constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept: _name{name}, _format{format}, _data{(CORRADE_CONSTEXPR_ASSERT( + /* Double formats intentionally not supported for any builtin attributes + right now -- only for custom formats */ (name == MeshAttribute::Position && (format == VertexFormat::Vector2 || - format == VertexFormat::Vector3)) || + format == VertexFormat::Vector2h || + format == VertexFormat::Vector2ub || + format == VertexFormat::Vector2ubNormalized || + format == VertexFormat::Vector2b || + format == VertexFormat::Vector2bNormalized || + format == VertexFormat::Vector2us || + format == VertexFormat::Vector2usNormalized || + format == VertexFormat::Vector2s || + format == VertexFormat::Vector2sNormalized || + format == VertexFormat::Vector3 || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3ub || + format == VertexFormat::Vector3ubNormalized || + format == VertexFormat::Vector3b || + format == VertexFormat::Vector3bNormalized || + format == VertexFormat::Vector3us || + format == VertexFormat::Vector3usNormalized || + format == VertexFormat::Vector3s || + format == VertexFormat::Vector3sNormalized)) || (name == MeshAttribute::Normal && - (format == VertexFormat::Vector3)) || + (format == VertexFormat::Vector3 || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3bNormalized || + format == VertexFormat::Vector3sNormalized)) || (name == MeshAttribute::Color && (format == VertexFormat::Vector3 || - format == VertexFormat::Vector4)) || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3ubNormalized || + format == VertexFormat::Vector3usNormalized || + format == VertexFormat::Vector4 || + format == VertexFormat::Vector4h || + format == VertexFormat::Vector4ubNormalized || + format == VertexFormat::Vector4usNormalized)) || (name == MeshAttribute::TextureCoordinates && - (format == VertexFormat::Vector2)) || + (format == VertexFormat::Vector2 || + format == VertexFormat::Vector2h || + format == VertexFormat::Vector2ub || + format == VertexFormat::Vector2ubNormalized || + format == VertexFormat::Vector2b || + format == VertexFormat::Vector2bNormalized || + format == VertexFormat::Vector2us || + format == VertexFormat::Vector2usNormalized || + format == VertexFormat::Vector2s || + format == VertexFormat::Vector2sNormalized)) || isMeshAttributeCustom(name) /* can be any format */, "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), data)} {} @@ -1233,7 +1374,7 @@ template Containers::StridedArrayView1D MeshData::attribute(Un #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif - CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[id]._format, + CORRADE_ASSERT(Implementation::isVertexFormatCompatible(_attributes[id]._format), "Trade::MeshData::attribute(): improper type requested for" << _attributes[id]._name << "of format" << _attributes[id]._format, nullptr); return Containers::arrayCast<1, const T>(data); } @@ -1243,7 +1384,7 @@ template Containers::StridedArrayView1D MeshData::mutableAttribute(U #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif - CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[id]._format, + CORRADE_ASSERT(Implementation::isVertexFormatCompatible(_attributes[id]._format), "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[id]._name << "of format" << _attributes[id]._format, nullptr); return Containers::arrayCast<1, T>(data); } @@ -1256,7 +1397,7 @@ template Containers::StridedArrayView1D MeshData::attribute(Me #ifndef CORRADE_NO_ASSERT const UnsignedInt attributeId = attributeFor(name, id); #endif - CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[attributeId]._format, + CORRADE_ASSERT(Implementation::isVertexFormatCompatible(_attributes[attributeId]._format), "Trade::MeshData::attribute(): improper type requested for" << _attributes[attributeId]._name << "of format" << _attributes[attributeId]._format, nullptr); return Containers::arrayCast<1, const T>(data); } @@ -1269,8 +1410,8 @@ template Containers::StridedArrayView1D MeshData::mutableAttribute(M #ifndef CORRADE_NO_ASSERT const UnsignedInt attributeId = attributeFor(name, id); #endif - CORRADE_ASSERT(Implementation::vertexFormatFor() == _attributes[attributeId]._format, - "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[attributeId]._name << "of type" << _attributes[attributeId]._format, nullptr); + CORRADE_ASSERT(Implementation::isVertexFormatCompatible(_attributes[attributeId]._format), + "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[attributeId]._name << "of format" << _attributes[attributeId]._format, nullptr); return Containers::arrayCast<1, T>(data); } diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 8d42f8bb67..cde87476e9 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -29,6 +29,7 @@ #include #include "Magnum/Math/Color.h" +#include "Magnum/Math/Half.h" #include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Trade { namespace Test { namespace { @@ -96,14 +97,28 @@ struct MeshDataTest: TestSuite::Tester { template void indicesAsArray(); void indicesIntoArrayInvalidSize(); template void positions2DAsArray(); + template void positions2DAsArrayPackedUnsigned(); + template void positions2DAsArrayPackedSigned(); + template void positions2DAsArrayPackedUnsignedNormalized(); + template void positions2DAsArrayPackedSignedNormalized(); void positions2DIntoArrayInvalidSize(); template void positions3DAsArray(); + template void positions3DAsArrayPackedUnsigned(); + template void positions3DAsArrayPackedSigned(); + template void positions3DAsArrayPackedUnsignedNormalized(); + template void positions3DAsArrayPackedSignedNormalized(); void positions3DIntoArrayInvalidSize(); template void normalsAsArray(); + template void normalsAsArrayPackedSignedNormalized(); void normalsIntoArrayInvalidSize(); template void textureCoordinates2DAsArray(); + template void textureCoordinates2DAsArrayPackedUnsigned(); + template void textureCoordinates2DAsArrayPackedSigned(); + template void textureCoordinates2DAsArrayPackedUnsignedNormalized(); + template void textureCoordinates2DAsArrayPackedSignedNormalized(); void textureCoordinates2DIntoArrayInvalidSize(); template void colorsAsArray(); + template void colorsAsArrayPackedUnsignedNormalized(); void colorsIntoArrayInvalidSize(); void mutableAccessNotAllowed(); @@ -202,17 +217,71 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::indicesAsArray, &MeshDataTest::indicesIntoArrayInvalidSize, &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DAsArray, &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DAsArrayPackedUnsigned, + &MeshDataTest::positions2DAsArrayPackedUnsigned, + &MeshDataTest::positions2DAsArrayPackedUnsigned, + &MeshDataTest::positions2DAsArrayPackedUnsigned, + &MeshDataTest::positions2DAsArrayPackedSigned, + &MeshDataTest::positions2DAsArrayPackedSigned, + &MeshDataTest::positions2DAsArrayPackedSigned, + &MeshDataTest::positions2DAsArrayPackedSigned, + &MeshDataTest::positions2DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions2DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions2DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions2DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions2DAsArrayPackedSignedNormalized, + &MeshDataTest::positions2DAsArrayPackedSignedNormalized, + &MeshDataTest::positions2DAsArrayPackedSignedNormalized, + &MeshDataTest::positions2DAsArrayPackedSignedNormalized, &MeshDataTest::positions2DIntoArrayInvalidSize, &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DAsArray, &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DAsArrayPackedUnsigned, + &MeshDataTest::positions3DAsArrayPackedUnsigned, + &MeshDataTest::positions3DAsArrayPackedUnsigned, + &MeshDataTest::positions3DAsArrayPackedUnsigned, + &MeshDataTest::positions3DAsArrayPackedSigned, + &MeshDataTest::positions3DAsArrayPackedSigned, + &MeshDataTest::positions3DAsArrayPackedSigned, + &MeshDataTest::positions3DAsArrayPackedSigned, + &MeshDataTest::positions3DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions3DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions3DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions3DAsArrayPackedUnsignedNormalized, + &MeshDataTest::positions3DAsArrayPackedSignedNormalized, + &MeshDataTest::positions3DAsArrayPackedSignedNormalized, + &MeshDataTest::positions3DAsArrayPackedSignedNormalized, + &MeshDataTest::positions3DAsArrayPackedSignedNormalized, &MeshDataTest::positions3DIntoArrayInvalidSize, &MeshDataTest::normalsAsArray, + &MeshDataTest::normalsAsArray, + &MeshDataTest::normalsAsArrayPackedSignedNormalized, + &MeshDataTest::normalsAsArrayPackedSignedNormalized, &MeshDataTest::normalsIntoArrayInvalidSize, &MeshDataTest::textureCoordinates2DAsArray, + &MeshDataTest::textureCoordinates2DAsArray, + &MeshDataTest::textureCoordinates2DAsArrayPackedUnsigned, + &MeshDataTest::textureCoordinates2DAsArrayPackedUnsigned, + &MeshDataTest::textureCoordinates2DAsArrayPackedSigned, + &MeshDataTest::textureCoordinates2DAsArrayPackedSigned, + &MeshDataTest::textureCoordinates2DAsArrayPackedUnsignedNormalized, + &MeshDataTest::textureCoordinates2DAsArrayPackedUnsignedNormalized, + &MeshDataTest::textureCoordinates2DAsArrayPackedSignedNormalized, + &MeshDataTest::textureCoordinates2DAsArrayPackedSignedNormalized, &MeshDataTest::textureCoordinates2DIntoArrayInvalidSize, &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsAsArray, &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, + &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, + &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, + &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, &MeshDataTest::colorsIntoArrayInvalidSize, &MeshDataTest::mutableAccessNotAllowed, @@ -1290,9 +1359,25 @@ template struct NameTraits; static const char* name() { return #format; } \ }; _c(Vector2) +_c(Vector2h) +_c(Vector2ub) +_c(Vector2b) +_c(Vector2us) +_c(Vector2s) _c(Vector3) +_c(Vector3h) +_c(Vector3ub) +_c(Vector3b) +_c(Vector3us) +_c(Vector3s) _c(Color3) +_c(Color3h) +_c(Color3ub) +_c(Color3us) _c(Color4) +_c(Color4h) +_c(Color4ub) +_c(Color4us) #undef _c template void MeshDataTest::indicesAsArray() { @@ -1324,12 +1409,13 @@ void MeshDataTest::indicesIntoArrayInvalidSize() { template void MeshDataTest::positions2DAsArray() { setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; Containers::Array vertexData{3*sizeof(T)}; auto positionsView = Containers::arrayCast(vertexData); - positionsView[0] = T::pad(Vector2{2.0f, 1.0f}); - positionsView[1] = T::pad(Vector2{0.0f, -1.0f}); - positionsView[2] = T::pad(Vector2{-2.0f, 3.0f}); + positionsView[0] = T::pad(Math::Vector2{U(2.0f), U(1.0f)}); + positionsView[1] = T::pad(Math::Vector2{U(0.0f), U(-1.0f)}); + positionsView[2] = T::pad(Math::Vector2{U(-2.0f), U(3.0f)}); MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; CORRADE_COMPARE_AS(data.positions2DAsArray(), @@ -1337,6 +1423,70 @@ template void MeshDataTest::positions2DAsArray() { TestSuite::Compare::Container); } +template void MeshDataTest::positions2DAsArrayPackedUnsigned() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector2{2, 1}); + positionsView[1] = T::pad(Math::Vector2{0, 15}); + positionsView[2] = T::pad(Math::Vector2{22, 3}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; + CORRADE_COMPARE_AS(data.positions2DAsArray(), + Containers::arrayView({{2.0f, 1.0f}, {0.0f, 15.0f}, {22.0f, 3.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::positions2DAsArrayPackedSigned() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector2{2, 1}); + positionsView[1] = T::pad(Math::Vector2{0, -15}); + positionsView[2] = T::pad(Math::Vector2{-22, 3}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; + CORRADE_COMPARE_AS(data.positions2DAsArray(), + Containers::arrayView({{2.0f, 1.0f}, {0.0f, -15.0f}, {-22.0f, 3.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::positions2DAsArrayPackedUnsignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector2{Math::pack(1.0f), 0}); + positionsView[1] = T::pad(Math::Vector2{0, Math::pack(1.0f)}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + positionsView}}}; + CORRADE_COMPARE_AS(data.positions2DAsArray(), + Containers::arrayView({{1.0f, 0.0f}, {0.0f, 1.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::positions2DAsArrayPackedSignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector2{Math::pack(1.0f), 0}); + positionsView[1] = T::pad(Math::Vector2{0, Math::pack(-1.0f)}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + positionsView}}}; + CORRADE_COMPARE_AS(data.positions2DAsArray(), + Containers::arrayView({{1.0f, 0.0f}, {0.0f, -1.0f}}), + TestSuite::Compare::Container); +} + void MeshDataTest::positions2DIntoArrayInvalidSize() { Containers::Array vertexData{3*sizeof(Vector2)}; MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, Containers::arrayCast(vertexData)}}}; @@ -1351,18 +1501,91 @@ void MeshDataTest::positions2DIntoArrayInvalidSize() { template void MeshDataTest::positions3DAsArray() { setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + /* Needs to be sufficiently representable to have the test work also for + half floats */ + positionsView[0] = T::pad(Math::Vector3{U(2.0f), U(1.0f), U(0.75f)}); + positionsView[1] = T::pad(Math::Vector3{U(0.0f), U(-1.0f), U(1.25f)}); + positionsView[2] = T::pad(Math::Vector3{U(-2.0f), U(3.0f), U(2.5f)}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; + CORRADE_COMPARE_AS(data.positions3DAsArray(), Containers::arrayView({ + Vector3::pad(Math::Vector::pad(Vector3{2.0f, 1.0f, 0.75f})), + Vector3::pad(Math::Vector::pad(Vector3{0.0f, -1.0f, 1.25f})), + Vector3::pad(Math::Vector::pad(Vector3{-2.0f, 3.0f, 2.5f})) + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::positions3DAsArrayPackedUnsigned() { + setTestCaseTemplateName(NameTraits::name()); Containers::Array vertexData{3*sizeof(T)}; auto positionsView = Containers::arrayCast(vertexData); - positionsView[0] = T::pad(Vector3{2.0f, 1.0f, 0.3f}); - positionsView[1] = T::pad(Vector3{0.0f, -1.0f, 1.1f}); - positionsView[2] = T::pad(Vector3{-2.0f, 3.0f, 2.2f}); + positionsView[0] = T::pad(Math::Vector3{2, 1, 135}); + positionsView[1] = T::pad(Math::Vector3{0, 15, 2}); + positionsView[2] = T::pad(Math::Vector3{22, 3, 192}); MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; CORRADE_COMPARE_AS(data.positions3DAsArray(), Containers::arrayView({ - Vector3::pad(T::pad(Vector3{2.0f, 1.0f, 0.3f})), - Vector3::pad(T::pad(Vector3{0.0f, -1.0f, 1.1f})), - Vector3::pad(T::pad(Vector3{-2.0f, 3.0f, 2.2f})) + Vector3::pad(Math::Vector::pad(Vector3{2.0f, 1.0f, 135.0f})), + Vector3::pad(Math::Vector::pad(Vector3{0.0f, 15.0f, 2.0f})), + Vector3::pad(Math::Vector::pad(Vector3{22.0f, 3.0f, 192.0f})) + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::positions3DAsArrayPackedSigned() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector3{2, 1, -117}); + positionsView[1] = T::pad(Math::Vector3{0, -15, 2}); + positionsView[2] = T::pad(Math::Vector3{-22, 3, 86}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, positionsView}}}; + CORRADE_COMPARE_AS(data.positions3DAsArray(), Containers::arrayView({ + Vector3::pad(Math::Vector::pad(Vector3{2.0f, 1.0f, -117.0f})), + Vector3::pad(Math::Vector::pad(Vector3{0.0f, -15.0f, 2.0f})), + Vector3::pad(Math::Vector::pad(Vector3{-22.0f, 3.0f, 86.0f})) + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::positions3DAsArrayPackedUnsignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector3{Math::pack(1.0f), 0, Math::pack(1.0f)}); + positionsView[1] = T::pad(Math::Vector3{0, Math::pack(1.0f), 0}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + positionsView}}}; + CORRADE_COMPARE_AS(data.positions3DAsArray(), Containers::arrayView({ + Vector3::pad(Math::Vector::pad(Vector3{1.0f, 0.0f, 1.0f})), + Vector3::pad(Math::Vector::pad(Vector3{0.0f, 1.0f, 0.0f})) + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::positions3DAsArrayPackedSignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto positionsView = Containers::arrayCast(vertexData); + positionsView[0] = T::pad(Math::Vector3{Math::pack(1.0f), 0, Math::pack(1.0f)}); + positionsView[1] = T::pad(Math::Vector3{0, Math::pack(-1.0f), 0}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Position, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + positionsView}}}; + CORRADE_COMPARE_AS(data.positions3DAsArray(), Containers::arrayView({ + Vector3::pad(Math::Vector::pad(Vector3{1.0f, 0.0f, 1.0f})), + Vector3::pad(Math::Vector::pad(Vector3{0.0f, -1.0f, 0.0f})) }), TestSuite::Compare::Container); } @@ -1380,16 +1603,36 @@ void MeshDataTest::positions3DIntoArrayInvalidSize() { template void MeshDataTest::normalsAsArray() { setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; Containers::Array vertexData{3*sizeof(T)}; auto normalsView = Containers::arrayCast(vertexData); - normalsView[0] = {2.0f, 1.0f, 0.3f}; - normalsView[1] = {0.0f, -1.0f, 1.1f}; - normalsView[2] = {-2.0f, 3.0f, 2.2f}; + /* Needs to be sufficiently representable to have the test work also for + half floats */ + normalsView[0] = {U(2.0f), U(1.0f), U(0.75f)}; + normalsView[1] = {U(0.0f), U(-1.0f), U(1.25f)}; + normalsView[2] = {U(-2.0f), U(3.0f), U(2.5f)}; MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Normal, normalsView}}}; CORRADE_COMPARE_AS(data.normalsAsArray(), Containers::arrayView({ - {2.0f, 1.0f, 0.3f}, {0.0f, -1.0f, 1.1f}, {-2.0f, 3.0f, 2.2f}, + {2.0f, 1.0f, 0.75f}, {0.0f, -1.0f, 1.25f}, {-2.0f, 3.0f, 2.5f}, + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::normalsAsArrayPackedSignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto normalsView = Containers::arrayCast(vertexData); + normalsView[0] = {Math::pack(1.0f), 0, Math::pack(1.0f)}; + normalsView[1] = {0, Math::pack(-1.0f), 0}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Normal, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + normalsView}}}; + CORRADE_COMPARE_AS(data.normalsAsArray(), Containers::arrayView({ + {1.0f, 0.0f, 1.0f}, {0.0f, -1.0f, 0.0f} }), TestSuite::Compare::Container); } @@ -1407,12 +1650,13 @@ void MeshDataTest::normalsIntoArrayInvalidSize() { template void MeshDataTest::textureCoordinates2DAsArray() { setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; Containers::Array vertexData{3*sizeof(T)}; auto textureCoordinatesView = Containers::arrayCast(vertexData); - textureCoordinatesView[0] = {2.0f, 1.0f}; - textureCoordinatesView[1] = {0.0f, -1.0f}; - textureCoordinatesView[2] = {-2.0f, 3.0f}; + textureCoordinatesView[0] = {U(2.0f), U(1.0f)}; + textureCoordinatesView[1] = {U(0.0f), U(-1.0f)}; + textureCoordinatesView[2] = {U(-2.0f), U(3.0f)}; MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, textureCoordinatesView}}}; CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(), Containers::arrayView({ @@ -1420,6 +1664,70 @@ template void MeshDataTest::textureCoordinates2DAsArray() { }), TestSuite::Compare::Container); } +template void MeshDataTest::textureCoordinates2DAsArrayPackedUnsigned() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto textureCoordinatesView = Containers::arrayCast(vertexData); + textureCoordinatesView[0] = {2, 1}; + textureCoordinatesView[1] = {0, 15}; + textureCoordinatesView[2] = {22, 3}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, textureCoordinatesView}}}; + CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(), + Containers::arrayView({{2.0f, 1.0f}, {0.0f, 15.0f}, {22.0f, 3.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::textureCoordinates2DAsArrayPackedSigned() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto textureCoordinatesView = Containers::arrayCast(vertexData); + textureCoordinatesView[0] = {2, 1}; + textureCoordinatesView[1] = {0, -15}; + textureCoordinatesView[2] = {-22, 3}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, textureCoordinatesView}}}; + CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(), + Containers::arrayView({{2.0f, 1.0f}, {0.0f, -15.0f}, {-22.0f, 3.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::textureCoordinates2DAsArrayPackedUnsignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto textureCoordinatesView = Containers::arrayCast(vertexData); + textureCoordinatesView[0] = {Math::pack(1.0f), 0}; + textureCoordinatesView[1] = {0, Math::pack(1.0f)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + textureCoordinatesView}}}; + CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(), + Containers::arrayView({{1.0f, 0.0f}, {0.0f, 1.0f}}), + TestSuite::Compare::Container); +} + +template void MeshDataTest::textureCoordinates2DAsArrayPackedSignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto textureCoordinatesView = Containers::arrayCast(vertexData); + textureCoordinatesView[0] = {Math::pack(1.0f), 0}; + textureCoordinatesView[1] = {0, Math::pack(-1.0f)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + textureCoordinatesView}}}; + CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(), + Containers::arrayView({{1.0f, 0.0f}, {0.0f, -1.0f}}), + TestSuite::Compare::Container); +} + void MeshDataTest::textureCoordinates2DIntoArrayInvalidSize() { Containers::Array vertexData{3*sizeof(Vector2)}; MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::TextureCoordinates, Containers::arrayCast(vertexData)}}}; @@ -1434,16 +1742,34 @@ void MeshDataTest::textureCoordinates2DIntoArrayInvalidSize() { template void MeshDataTest::colorsAsArray() { setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; Containers::Array vertexData{3*sizeof(T)}; auto colorsView = Containers::arrayCast(vertexData); - colorsView[0] = 0xff3366_rgbf; - colorsView[1] = 0x99aacc_rgbf; - colorsView[2] = 0x3377ff_rgbf; + /* Can't use e.g. 0xff3366_rgbf because that's not representable in + half-floats */ + colorsView[0] = {U(2.0f), U(1.0f), U(0.75f)}; + colorsView[1] = {U(0.0f), U(-1.0f), U(1.25f)}; + colorsView[2] = {U(-2.0f), U(3.0f), U(2.5f)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Color, colorsView}}}; + CORRADE_COMPARE_AS(data.colorsAsArray(), Containers::arrayView({ + {2.0f, 1.0f, 0.75f}, {0.0f, -1.0f, 1.25f}, {-2.0f, 3.0f, 2.5f}, + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::colorsAsArrayPackedUnsignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto colorsView = Containers::arrayCast(vertexData); + colorsView[0] = T::pad(Math::Color4{Math::pack(1.0f), 0, Math::pack(1.0f), 0}); + colorsView[1] = T::pad(Math::Color4{0, Math::pack(1.0f), 0, Math::pack(1.0f)}); MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Color, colorsView}}}; CORRADE_COMPARE_AS(data.colorsAsArray(), Containers::arrayView({ - 0xff3366_rgbf, 0x99aacc_rgbf, 0x3377ff_rgbf + Color4::pad(Math::Vector::pad(Vector4{1.0f, 0.0f, 1.0f, 0.0f}), 1.0f), + Color4::pad(Math::Vector::pad(Vector4{0.0f, 1.0f, 0.0f, 1.0f}), 1.0f) }), TestSuite::Compare::Container); } From 13c071a1aaa814e947d5c694544713658366f5db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 2 Feb 2020 17:45:18 +0100 Subject: [PATCH 069/107] GL: allow to construct DynamicAttribute from VertexFormat. --- doc/snippets/MagnumTrade.cpp | 18 ++-- src/Magnum/GL/Attribute.cpp | 58 +++++++++++ src/Magnum/GL/Attribute.h | 33 +++++- src/Magnum/GL/CMakeLists.txt | 2 +- src/Magnum/GL/Test/AttributeTest.cpp | 149 +++++++++++++++++++++++++++ src/Magnum/GL/Test/CMakeLists.txt | 2 +- src/Magnum/Trade/MeshData.h | 6 +- 7 files changed, 255 insertions(+), 13 deletions(-) diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index c8e681417f..b9d68c3d49 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -277,17 +277,17 @@ GL::Mesh mesh{data.primitive()}; GL::Buffer vertices; vertices.setData(data.vertexData()); -/* Set up the position attribute */ -Shaders::Phong::Position position; -auto positionFormat = data.attributeFormat(Trade::MeshAttribute::Position); -if(positionFormat == VertexFormat::Vector2) - position = {Shaders::Phong::Position::Components::Two}; -else if(positionFormat == VertexFormat::Vector3) - position = {Shaders::Phong::Position::Components::Three}; -else Fatal{} << "Huh?"; +/* Set up the position and normal attributes */ mesh.addVertexBuffer(vertices, data.attributeOffset(Trade::MeshAttribute::Position), - data.attributeStride(Trade::MeshAttribute::Position), position); + data.attributeStride(Trade::MeshAttribute::Position), + GL::DynamicAttribute{Shaders::Phong::Position{}, + data.attributeFormat(Trade::MeshAttribute::Position)}); +mesh.addVertexBuffer(vertices, + data.attributeOffset(Trade::MeshAttribute::Normal), + data.attributeStride(Trade::MeshAttribute::Normal), + GL::DynamicAttribute{Shaders::Phong::Normal{}, + data.attributeFormat(Trade::MeshAttribute::Normal)}); // Set up other attributes ... diff --git a/src/Magnum/GL/Attribute.cpp b/src/Magnum/GL/Attribute.cpp index 8fc5b39b29..9233df4fa7 100644 --- a/src/Magnum/GL/Attribute.cpp +++ b/src/Magnum/GL/Attribute.cpp @@ -28,6 +28,8 @@ #include #include +#include "Magnum/VertexFormat.h" + namespace Magnum { namespace GL { Debug& operator<<(Debug& debug, const DynamicAttribute::Kind value) { @@ -467,4 +469,60 @@ Debug& operator<<(Debug& debug, const Attribute>::DataTyp } +DynamicAttribute::DynamicAttribute(const Kind kind, UnsignedInt location, const VertexFormat format, GLint maxComponents): _kind{kind}, _location{location}, _components{Components(vertexFormatComponentCount(format))} { + /* Translate component type to a GL-specific value */ + switch(vertexFormatComponentFormat(format)) { + #define _c(format) \ + case VertexFormat::format: \ + _dataType = DataType::format; \ + break; + _c(UnsignedByte) + _c(Byte) + _c(UnsignedShort) + _c(Short) + _c(UnsignedInt) + _c(Int) + _c(Float) + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + _c(Half) + #endif + #ifndef MAGNUM_TARGET_GLES + _c(Double) + #endif + #undef _c + + /* Nothing else expected to be returned from + vertexFormatComponentFormat() */ + default: CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* If the type is normalized, switch the type to GenericNormalized (if not + already), and check that the attribute isn't expected to be integral or + long */ + if(isVertexFormatNormalized(format)) { + CORRADE_ASSERT(kind == Kind::Generic || kind == Kind::GenericNormalized, + "GL::DynamicAttribute: can't use" << format << "for a" << kind << "attribute", ); + _kind = Kind::GenericNormalized; + /* Otherwise check that non-normalized types aren't used for attributes + that are expected to be normalized. Float is an exception. */ + } else if(_dataType != DataType::Float) { + CORRADE_ASSERT(kind != Kind::GenericNormalized, + "GL::DynamicAttribute: can't use" << format << "for a normalized attribute", ); + /* Finally, float data types can't be used for integer attributes */ + } else { + #ifndef MAGNUM_TARGET_GLES2 + CORRADE_ASSERT(kind != Kind::Integral, + "GL::DynamicAttribute: can't use" << format << "for an integral attribute", ); + #endif + } + + #ifndef CORRADE_NO_DEBUG + /* Should pass also if maxComponents is GL_BGRA */ + CORRADE_ASSERT(GLint(_components) <= maxComponents, + "GL::DynamicAttribute: can't use" << format << "for a" << maxComponents << Debug::nospace << "-component attribute", ); + #else + static_cast(maxComponents); + #endif +} + }} diff --git a/src/Magnum/GL/Attribute.h b/src/Magnum/GL/Attribute.h index ae497691da..4dd3114c0b 100644 --- a/src/Magnum/GL/Attribute.h +++ b/src/Magnum/GL/Attribute.h @@ -346,7 +346,7 @@ location and base type. Note that unlike the compile-time specification, this class doesn't do any sanity verification and leaves most of the responsibility on the user. */ -class DynamicAttribute { +class MAGNUM_GL_EXPORT DynamicAttribute { public: /** * @brief Attribute kind @@ -543,6 +543,31 @@ class DynamicAttribute { */ template constexpr /*implicit*/ DynamicAttribute(const Attribute& attribute); + /** + * @brief Construct from a generic mesh attribute type + * @m_since_latest + * + * The @p type is expected to be compatible with @p kind --- i.e., + * normalized or floating-point for @ref Kind::GenericNormalized, + * non-normalized for @ref Kind::Integral / @ref Kind::Long and + * integral for @ref Kind::Integral. + */ + explicit DynamicAttribute(Kind kind, UnsignedInt location, VertexFormat format): DynamicAttribute{kind, location, format, 4} {} + + /** + * @brief Construct from a compile-time attribute with a generic mesh attribute type override + * @m_since_latest + * + * Extracts kind and location from passed @ref Attribute type and calls + * @ref DynamicAttribute(Kind, UnsignedInt, VertexFormat). Expects that + * @p type's component count is not larger than the component count + * defined in the @p Attribute type. Note that only the + * compile-time-defined properties of the @p Attribute type are used, + * the instance-specific data type, options and component count is + * ignored. + */ + template explicit DynamicAttribute(const Attribute&, VertexFormat format); + /** @brief Attribute kind */ constexpr Kind kind() const { return _kind; } @@ -556,6 +581,10 @@ class DynamicAttribute { constexpr DataType dataType() const { return _dataType; } private: + /* Used by the constructor taking Attribute, defined in cpp to avoid + a dependency on for the assertion */ + explicit DynamicAttribute(Kind kind, UnsignedInt location, VertexFormat format, GLint maxComponents); + Kind _kind; UnsignedInt _location; Components _components; @@ -892,6 +921,8 @@ template struct Attribute>: Attribute constexpr DynamicAttribute::DynamicAttribute(const Attribute& attribute): _kind{Implementation::kindFor(attribute.dataOptions())}, _location{location_}, _components{Components(GLint(attribute.components()))}, _dataType{DataType(GLenum(attribute.dataType()))} {} +template DynamicAttribute::DynamicAttribute(const Attribute& attribute, const VertexFormat format): DynamicAttribute{Implementation::kindFor(attribute.dataOptions()), location_, format, GLint(Implementation::Attribute::DefaultComponents)} {} + }} #endif diff --git a/src/Magnum/GL/CMakeLists.txt b/src/Magnum/GL/CMakeLists.txt index 1e61c5cda1..fdaec99fc1 100644 --- a/src/Magnum/GL/CMakeLists.txt +++ b/src/Magnum/GL/CMakeLists.txt @@ -27,7 +27,6 @@ set(MagnumGL_SRCS AbstractObject.cpp AbstractQuery.cpp AbstractShaderProgram.cpp - Attribute.cpp Buffer.cpp Context.cpp DefaultFramebuffer.cpp @@ -56,6 +55,7 @@ set(MagnumGL_SRCS set(MagnumGL_GracefulAssert_SRCS AbstractFramebuffer.cpp AbstractTexture.cpp + Attribute.cpp CubeMapTexture.cpp Mesh.cpp MeshView.cpp diff --git a/src/Magnum/GL/Test/AttributeTest.cpp b/src/Magnum/GL/Test/AttributeTest.cpp index 3cc14a5659..a745c2a13c 100644 --- a/src/Magnum/GL/Test/AttributeTest.cpp +++ b/src/Magnum/GL/Test/AttributeTest.cpp @@ -27,6 +27,7 @@ #include #include +#include "Magnum/VertexFormat.h" #include "Magnum/GL/Attribute.h" namespace Magnum { namespace GL { namespace Test { namespace { @@ -53,6 +54,23 @@ struct AttributeTest: TestSuite::Tester { void attributeMatrixNxNd(); void attributeMatrixMxNd(); + void attributeFromGenericFormat(); + #ifndef MAGNUM_TARGET_GLES2 + void attributeFromGenericFormatIntegral(); + #endif + #ifndef MAGNUM_TARGET_GLES + void attributeFromGenericFormatLong(); + #endif + void attributeFromGenericFormatEnableNormalized(); + void attributeFromGenericFormatUnexpectedForNormalizedKind(); + #ifndef MAGNUM_TARGET_GLES2 + void attributeFromGenericFormatUnexpectedForIntegralKind(); + #endif + #ifndef MAGNUM_TARGET_GLES + void attributeFromGenericFormatUnexpectedForLongKind(); + #endif + void attributeFromGenericFormatTooManyComponents(); + void debugComponents1(); void debugComponents2(); void debugComponents3(); @@ -98,6 +116,23 @@ AttributeTest::AttributeTest() { &AttributeTest::attributeMatrixNxNd, &AttributeTest::attributeMatrixMxNd, + &AttributeTest::attributeFromGenericFormat, + #ifndef MAGNUM_TARGET_GLES2 + &AttributeTest::attributeFromGenericFormatIntegral, + #endif + #ifndef MAGNUM_TARGET_GLES + &AttributeTest::attributeFromGenericFormatLong, + #endif + &AttributeTest::attributeFromGenericFormatEnableNormalized, + &AttributeTest::attributeFromGenericFormatUnexpectedForNormalizedKind, + #ifndef MAGNUM_TARGET_GLES2 + &AttributeTest::attributeFromGenericFormatUnexpectedForIntegralKind, + #endif + #ifndef MAGNUM_TARGET_GLES + &AttributeTest::attributeFromGenericFormatUnexpectedForLongKind, + #endif + &AttributeTest::attributeFromGenericFormatTooManyComponents, + &AttributeTest::debugComponents1, &AttributeTest::debugComponents2, &AttributeTest::debugComponents3, @@ -491,6 +526,120 @@ void AttributeTest::attributeMatrixMxNd() { #endif } +void AttributeTest::attributeFromGenericFormat() { + DynamicAttribute a{DynamicAttribute::Kind::Generic, 3, + VertexFormat::UnsignedShort}; + CORRADE_COMPARE(a.kind(), DynamicAttribute::Kind::Generic); + CORRADE_COMPARE(a.location(), 3); + CORRADE_COMPARE(a.components(), DynamicAttribute::Components::One); + CORRADE_COMPARE(a.dataType(), DynamicAttribute::DataType::UnsignedShort); + + /* Check that compile-time attribs work too */ + DynamicAttribute a2{Attribute<7, Vector3>{}, + VertexFormat::UnsignedShort}; + CORRADE_COMPARE(a2.kind(), DynamicAttribute::Kind::Generic); + CORRADE_COMPARE(a2.location(), 7); + CORRADE_COMPARE(a2.components(), DynamicAttribute::Components::One); + CORRADE_COMPARE(a2.dataType(), DynamicAttribute::DataType::UnsignedShort); + + DynamicAttribute b{DynamicAttribute::Kind::GenericNormalized, 3, + VertexFormat::Vector2bNormalized}; + CORRADE_COMPARE(b.kind(), DynamicAttribute::Kind::GenericNormalized); + CORRADE_COMPARE(b.location(), 3); + CORRADE_COMPARE(b.components(), DynamicAttribute::Components::Two); + CORRADE_COMPARE(b.dataType(), DynamicAttribute::DataType::Byte); + + DynamicAttribute c{DynamicAttribute::Kind::Generic, 3, + VertexFormat::Vector4ui}; + CORRADE_COMPARE(c.kind(), DynamicAttribute::Kind::Generic); + CORRADE_COMPARE(c.location(), 3); + CORRADE_COMPARE(c.components(), DynamicAttribute::Components::Four); + CORRADE_COMPARE(c.dataType(), DynamicAttribute::DataType::UnsignedInt); + + /* This one shouldn't fail even though the normalization is (probably?) + ignored. Not exactly sure. */ + DynamicAttribute d{DynamicAttribute::Kind::GenericNormalized, 3, + VertexFormat::Float}; + CORRADE_COMPARE(d.kind(), DynamicAttribute::Kind::GenericNormalized); + CORRADE_COMPARE(d.location(), 3); + CORRADE_COMPARE(d.components(), DynamicAttribute::Components::One); + CORRADE_COMPARE(d.dataType(), DynamicAttribute::DataType::Float); +} + +#ifndef MAGNUM_TARGET_GLES2 +void AttributeTest::attributeFromGenericFormatIntegral() { + DynamicAttribute a{DynamicAttribute::Kind::Integral, 3, + VertexFormat::Vector3s}; + CORRADE_COMPARE(a.kind(), DynamicAttribute::Kind::Integral); + CORRADE_COMPARE(a.location(), 3); + CORRADE_COMPARE(a.components(), DynamicAttribute::Components::Three); + CORRADE_COMPARE(a.dataType(), DynamicAttribute::DataType::Short); +} +#endif + +#ifndef MAGNUM_TARGET_GLES +void AttributeTest::attributeFromGenericFormatLong() { + DynamicAttribute a{DynamicAttribute::Kind::Long, 15, + VertexFormat::Vector2d}; + CORRADE_COMPARE(a.kind(), DynamicAttribute::Kind::Long); + CORRADE_COMPARE(a.location(), 15); + CORRADE_COMPARE(a.components(), DynamicAttribute::Components::Two); + CORRADE_COMPARE(a.dataType(), DynamicAttribute::DataType::Double); +} +#endif + +void AttributeTest::attributeFromGenericFormatEnableNormalized() { + DynamicAttribute a{DynamicAttribute::Kind::Generic, 3, + VertexFormat::Vector3ubNormalized}; + /* Generic is automatically switched to GenericNormalized */ + CORRADE_COMPARE(a.kind(), DynamicAttribute::Kind::GenericNormalized); + CORRADE_COMPARE(a.location(), 3); + CORRADE_COMPARE(a.components(), DynamicAttribute::Components::Three); + CORRADE_COMPARE(a.dataType(), DynamicAttribute::DataType::UnsignedByte); +} + +void AttributeTest::attributeFromGenericFormatUnexpectedForNormalizedKind() { + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{DynamicAttribute::Kind::GenericNormalized, 3, + VertexFormat::Int}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: can't use VertexFormat::Int for a normalized attribute\n"); +} + +#ifndef MAGNUM_TARGET_GLES2 +void AttributeTest::attributeFromGenericFormatUnexpectedForIntegralKind() { + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{DynamicAttribute::Kind::Integral, 3, + VertexFormat::Vector2bNormalized}; + DynamicAttribute{DynamicAttribute::Kind::Integral, 3, + VertexFormat::Vector3}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: can't use VertexFormat::Vector2bNormalized for a GL::DynamicAttribute::Kind::Integral attribute\n" + "GL::DynamicAttribute: can't use VertexFormat::Vector3 for an integral attribute\n"); +} +#endif + +#ifndef MAGNUM_TARGET_GLES +void AttributeTest::attributeFromGenericFormatUnexpectedForLongKind() { + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{DynamicAttribute::Kind::Long, 3, + VertexFormat::UnsignedShortNormalized}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: can't use VertexFormat::UnsignedShortNormalized for a GL::DynamicAttribute::Kind::Long attribute\n"); +} +#endif + +void AttributeTest::attributeFromGenericFormatTooManyComponents() { + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{Attribute<7, Vector2>{}, VertexFormat::Vector3}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: can't use VertexFormat::Vector3 for a 2-component attribute\n"); +} + void AttributeTest::debugComponents1() { typedef Attribute<3, Float> Attribute; diff --git a/src/Magnum/GL/Test/CMakeLists.txt b/src/Magnum/GL/Test/CMakeLists.txt index 2233b07117..1dface78d0 100644 --- a/src/Magnum/GL/Test/CMakeLists.txt +++ b/src/Magnum/GL/Test/CMakeLists.txt @@ -23,7 +23,7 @@ # DEALINGS IN THE SOFTWARE. # -corrade_add_test(GLAttributeTest AttributeTest.cpp LIBRARIES MagnumGL) +corrade_add_test(GLAttributeTest AttributeTest.cpp LIBRARIES MagnumGLTestLib) corrade_add_test(GLAbstractShaderProgramTest AbstractShaderProgramTest.cpp LIBRARIES MagnumGL) corrade_add_test(GLBufferTest BufferTest.cpp LIBRARIES MagnumGL) corrade_add_test(GLContextTest ContextTest.cpp LIBRARIES MagnumGL) diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 4684584831..825deed275 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -373,7 +373,11 @@ cases, sometimes you may want to minimize the import time of a large model or the imported data may be already in a well-optimized layout and format that you want to preserve. The @ref MeshData class internally stores a contiguous blob of data, which you can directly upload, and then use provided metadata to let -the GPU know of the format and layout: +the GPU know of the format and layout. Because there's a lot of possible types +of each attribute (floats, packed integers, ...), the @ref GL::DynamicAttribute +can accept a pair of @ref GL::Attribute defined by the shader and the actual +@ref VertexFormat, figuring out all properties such as component count and +element data type without having to explicitly handle all relevant types: @snippet MagnumTrade.cpp MeshData-usage-advanced From ee06eb2093c1dd7ef2c854577725d10ca38effaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 20 Feb 2020 16:22:58 +0100 Subject: [PATCH 070/107] MeshTools: support the 40 new attribute formats by deleting some code. Hur hur. --- src/Magnum/MeshTools/Compile.cpp | 29 ++-- src/Magnum/MeshTools/Test/CompileGLTest.cpp | 154 +++++++++++++++++++- 2 files changed, 163 insertions(+), 20 deletions(-) diff --git a/src/Magnum/MeshTools/Compile.cpp b/src/Magnum/MeshTools/Compile.cpp index 104819b963..74b434449d 100644 --- a/src/Magnum/MeshTools/Compile.cpp +++ b/src/Magnum/MeshTools/Compile.cpp @@ -58,8 +58,7 @@ GL::Mesh compile(const Trade::MeshData& meshData, CompileFlags flags) { if(meshData.primitive() == MeshPrimitive::Triangles && (flags & (CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals))) { CORRADE_ASSERT(meshData.attributeCount(Trade::MeshAttribute::Position), "MeshTools::compile(): the mesh has no positions, can't generate normals", GL::Mesh{}); - /* Right now this could fire only if we have 2D positions, which is - unlikely; in the future it might fire once packed formats are added */ + /* This could fire if we have 2D positions or for packed formats */ CORRADE_ASSERT(meshData.attributeFormat(Trade::MeshAttribute::Position) == VertexFormat::Vector3, "MeshTools::compile(): can't generate normals for" << meshData.attributeFormat(Trade::MeshAttribute::Position) << "positions", GL::Mesh{}); @@ -72,8 +71,7 @@ GL::Mesh compile(const Trade::MeshData& meshData, CompileFlags flags) { Trade::MeshAttribute::Normal, VertexFormat::Vector3, nullptr}; extra = {&normalAttribute, 1}; - /* If we reuse a normal location, expect correct type. Again this won't - fire now, but might in the future once packed formats are added */ + /* If we reuse a normal location, expect correct type */ } else CORRADE_ASSERT(meshData.attributeFormat(Trade::MeshAttribute::Normal) == VertexFormat::Vector3, "MeshTools::compile(): can't generate normals into" << meshData.attributeFormat(Trade::MeshAttribute::Normal), GL::Mesh{}); @@ -142,32 +140,27 @@ GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buff GL::Buffer verticesRef = GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array); for(UnsignedInt i = 0; i != meshData.attributeCount(); ++i) { Containers::Optional attribute; + const VertexFormat format = meshData.attributeFormat(i); switch(meshData.attributeName(i)) { case Trade::MeshAttribute::Position: - if(meshData.attributeFormat(i) == VertexFormat::Vector2) - attribute.emplace(Shaders::Generic2D::Position{}); - else if(meshData.attributeFormat(i) == VertexFormat::Vector3) - attribute.emplace(Shaders::Generic3D::Position{}); - else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + /* Pick 3D position always, the type will properly reduce it to + a 2-component version if needed */ + attribute.emplace(Shaders::Generic3D::Position{}, format); break; case Trade::MeshAttribute::Normal: - CORRADE_INTERNAL_ASSERT(meshData.attributeFormat(i) == VertexFormat::Vector3); - attribute.emplace(Shaders::Generic3D::Normal{}); + attribute.emplace(Shaders::Generic3D::Normal{}, format); break; case Trade::MeshAttribute::TextureCoordinates: - CORRADE_INTERNAL_ASSERT(meshData.attributeFormat(i) == VertexFormat::Vector2); /** @todo have Generic2D derived from Generic that has all attribute definitions common for 2D and 3D */ - attribute.emplace(Shaders::Generic2D::TextureCoordinates{}); + attribute.emplace(Shaders::Generic2D::TextureCoordinates{}, format); break; case Trade::MeshAttribute::Color: /** @todo have Generic2D derived from Generic that has all attribute definitions common for 2D and 3D */ - if(meshData.attributeFormat(i) == VertexFormat::Vector3) - attribute.emplace(Shaders::Generic2D::Color3{}); - else if(meshData.attributeFormat(i) == VertexFormat::Vector4) - attribute.emplace(Shaders::Generic2D::Color4{}); - else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + /* Pick Color4 always, the type will properly reduce it to a + 3-component version if needed */ + attribute.emplace(Shaders::Generic2D::Color4{}, format); break; /* So it doesn't yell that we didn't handle a known attribute */ diff --git a/src/Magnum/MeshTools/Test/CompileGLTest.cpp b/src/Magnum/MeshTools/Test/CompileGLTest.cpp index 4257c215e8..fa80cb067d 100644 --- a/src/Magnum/MeshTools/Test/CompileGLTest.cpp +++ b/src/Magnum/MeshTools/Test/CompileGLTest.cpp @@ -90,9 +90,13 @@ struct CompileGLTest: GL::OpenGLTester { /** @todo remove the template once MeshDataXD is gone */ template void twoDimensions(); template void threeDimensions(); + + void packedAttributes(); + void unknownAttribute(); void generateNormalsNoPosition(); void generateNormals2DPosition(); + void generateNormalsNoFloats(); void externalBuffers(); void externalBuffersInvalid(); @@ -198,9 +202,12 @@ CompileGLTest::CompileGLTest() { CORRADE_IGNORE_DEPRECATED_POP #endif - addTests({&CompileGLTest::unknownAttribute, + addTests({&CompileGLTest::packedAttributes, + + &CompileGLTest::unknownAttribute, &CompileGLTest::generateNormalsNoPosition, - &CompileGLTest::generateNormals2DPosition}); + &CompileGLTest::generateNormals2DPosition, + &CompileGLTest::generateNormalsNoFloats}); addInstancedTests({&CompileGLTest::externalBuffers}, Containers::arraySize(DataExternal)); @@ -578,6 +585,133 @@ template void CompileGLTest::threeDimensions() { } } +void CompileGLTest::packedAttributes() { + /* Same as above, just packed */ + const struct Vertex { + Vector3s position; + Vector3s normal; + Vector2us textureCoordinates; + Color4ub color; + } vertexData[]{ + {Math::pack(Vector3{-0.75f, -0.75f, -0.35f}), + Math::pack(Vector3{-0.5f, -0.5f, 1.0f}.normalized()), + Math::pack(Vector2{0.0f, 0.0f}), 0x00ff00_rgb}, + {Math::pack(Vector3{ 0.00f, -0.75f, -0.25f}), + Math::pack(Vector3{ 0.0f, -0.5f, 1.0f}.normalized()), + Math::pack(Vector2{0.5f, 0.0f}), 0x808000_rgb}, + {Math::pack(Vector3{ 0.75f, -0.75f, -0.35f}), + Math::pack(Vector3{ 0.5f, -0.5f, 1.0f}.normalized()), + Math::pack(Vector2{1.0f, 0.0f}), 0xff0000_rgb}, + + {Math::pack(Vector3{-0.75f, 0.00f, -0.25f}), + Math::pack(Vector3{-0.5f, 0.0f, 1.0f}.normalized()), + Math::pack(Vector2{0.0f, 0.5f}), 0x00ff80_rgb}, + {Math::pack(Vector3{ 0.00f, 0.00f, 0.00f}), + Math::pack(Vector3{ 0.0f, 0.0f, 1.0f}.normalized()), + Math::pack(Vector2{0.5f, 0.5f}), 0x808080_rgb}, + {Math::pack(Vector3{ 0.75f, 0.00f, -0.25f}), + Math::pack(Vector3{ 0.5f, 0.0f, 1.0f}.normalized()), + Math::pack(Vector2{1.0f, 0.5f}), 0xff0080_rgb}, + + {Math::pack(Vector3{-0.75f, 0.75f, -0.35f}), + Math::pack(Vector3{-0.5f, 0.5f, 1.0f}.normalized()), + Math::pack(Vector2{0.0f, 1.0f}), 0x00ffff_rgb}, + {Math::pack(Vector3{ 0.0f, 0.75f, -0.25f}), + Math::pack(Vector3{ 0.0f, 0.5f, 1.0f}.normalized()), + Math::pack(Vector2{0.5f, 1.0f}), 0x8080ff_rgb}, + {Math::pack(Vector3{ 0.75f, 0.75f, -0.35f}), + Math::pack(Vector3{ 0.5f, 0.5f, 1.0f}.normalized()), + Math::pack(Vector2{1.0f, 1.0f}), 0xff00ff_rgb} + }; + static_assert(sizeof(Vertex) % 4 == 0, + "the vertex is not 4-byte aligned and that's bad"); + + const UnsignedByte indexData[]{ + 0, 1, 4, 0, 4, 3, + 1, 2, 5, 1, 5, 4, + 3, 4, 7, 3, 7, 6, + 4, 5, 8, 4, 8, 7 + }; + + Trade::MeshData meshData{MeshPrimitive::Triangles, {}, indexData, + Trade::MeshIndexData{indexData}, {}, vertexData, { + Trade::MeshAttributeData{ + Trade::MeshAttribute::Position, + VertexFormat::Vector3sNormalized, + Containers::stridedArrayView(vertexData, &vertexData[0].position, + Containers::arraySize(vertexData), sizeof(Vertex))}, + Trade::MeshAttributeData{ + Trade::MeshAttribute::Normal, + VertexFormat::Vector3sNormalized, + Containers::stridedArrayView(vertexData, &vertexData[0].normal, + Containers::arraySize(vertexData), sizeof(Vertex))}, + Trade::MeshAttributeData{ + Trade::MeshAttribute::TextureCoordinates, + VertexFormat::Vector2usNormalized, + Containers::stridedArrayView(vertexData, &vertexData[0].textureCoordinates, + Containers::arraySize(vertexData), sizeof(Vertex))}, + Trade::MeshAttributeData{ + Trade::MeshAttribute::Color, + /* It should figure out the type itself here */ + Containers::stridedArrayView(vertexData, &vertexData[0].color, + Containers::arraySize(vertexData), sizeof(Vertex))} + }}; + + GL::Mesh mesh = compile(meshData); + + MAGNUM_VERIFY_NO_GL_ERROR(); + + if(!(_manager.loadState("AnyImageImporter") & PluginManager::LoadState::Loaded) || + !(_manager.loadState("TgaImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnyImageImporter / TgaImporter plugins not found."); + + Matrix4 projection = Matrix4::perspectiveProjection(45.0_degf, 1.0f, 0.1f, 10.0f); + Matrix4 transformation = Matrix4::translation(Vector3::zAxis(-2.0f)); + + /* In all checks below, the rendering should be practically 1:1 as above + with full-blown attribute types */ + + /* Check positions and normals */ + _framebuffer.clear(GL::FramebufferClear::Color); + _phong + .setDiffuseColor(0x33ff66_rgbf) + .setTransformationMatrix(transformation) + .setNormalMatrix(transformation.normalMatrix()) + .setProjectionMatrix(projection); + mesh.draw(_phong); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(COMPILEGLTEST_TEST_DIR, "phong.tga"), + /* SwiftShader has some minor off-by-one precision differences */ + (DebugTools::CompareImageToFile{_manager, 0.5f, 0.0113f})); + + /* Check colors */ + _framebuffer.clear(GL::FramebufferClear::Color); + _color3D + .setTransformationProjectionMatrix(projection*transformation); + mesh.draw(_color3D); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(COMPILEGLTEST_TEST_DIR, "color3D.tga"), + /* SwiftShader has some minor off-by-one precision differences */ + (DebugTools::CompareImageToFile{_manager, 0.5f, 0.0162f})); + + /* Check texture coordinates */ + _framebuffer.clear(GL::FramebufferClear::Color); + _flatTextured3D + .setTransformationProjectionMatrix(projection*transformation) + .bindTexture(_texture); + mesh.draw(_flatTextured3D); + MAGNUM_VERIFY_NO_GL_ERROR(); + CORRADE_COMPARE_WITH( + _framebuffer.read({{}, {32, 32}}, {PixelFormat::RGBA8Unorm}), + Utility::Directory::join(COMPILEGLTEST_TEST_DIR, "textured3D.tga"), + /* SwiftShader has some minor off-by-one precision differences */ + (DebugTools::CompareImageToFile{_manager, 1.0f, 0.0948f})); +} + void CompileGLTest::unknownAttribute() { Trade::MeshData data{MeshPrimitive::Triangles, nullptr, {Trade::MeshAttributeData{Trade::meshAttributeCustom(115), @@ -612,6 +746,22 @@ void CompileGLTest::generateNormals2DPosition() { "MeshTools::compile(): can't generate normals for VertexFormat::Vector2 positions\n"); } +void CompileGLTest::generateNormalsNoFloats() { + Trade::MeshData data{MeshPrimitive::Triangles, + nullptr, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector3, nullptr}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3h, nullptr}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::compile(data, CompileFlag::GenerateFlatNormals); + CORRADE_COMPARE(out.str(), + "MeshTools::compile(): can't generate normals into VertexFormat::Vector3h\n"); +} + void CompileGLTest::externalBuffers() { auto&& data = DataExternal[testCaseInstanceId()]; setTestCaseDescription(data.name); From b203924355d63604f71d0cb907cff51cf4629c5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 21 Nov 2019 18:37:36 +0100 Subject: [PATCH 071/107] GCC 4.8, happy to see you're still around, causing problems. --- src/Magnum/Primitives/Implementation/Spheroid.cpp | 15 +++++++++------ src/Magnum/Trade/Test/MeshData2DTest.cpp | 12 ++++++++---- src/Magnum/Trade/Test/MeshData3DTest.cpp | 12 ++++++++---- 3 files changed, 25 insertions(+), 14 deletions(-) diff --git a/src/Magnum/Primitives/Implementation/Spheroid.cpp b/src/Magnum/Primitives/Implementation/Spheroid.cpp index a1c89a8695..8c8d31e39b 100644 --- a/src/Magnum/Primitives/Implementation/Spheroid.cpp +++ b/src/Magnum/Primitives/Implementation/Spheroid.cpp @@ -229,17 +229,20 @@ Trade::MeshData Spheroid::finalize() { auto typedVertices = reinterpret_cast(_vertexData.data()); Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, - Containers::stridedArrayView(_vertexData, &typedVertices[0].position, - size, stride)}; + /* GCC 4.8 needs the arrayView() */ + Containers::stridedArrayView(Containers::arrayView(_vertexData), + &typedVertices[0].position, size, stride)}; Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, - Containers::stridedArrayView(_vertexData, &typedVertices[0].normal, - size, stride)}; + /* GCC 4.8 needs the arrayView() */ + Containers::stridedArrayView(Containers::arrayView(_vertexData), + &typedVertices[0].normal, size, stride)}; Containers::Array attributes; if(_textureCoords == TextureCoords::Generate) { Trade::MeshAttributeData textureCoords{Trade::MeshAttribute::TextureCoordinates, - Containers::stridedArrayView(_vertexData, &typedVertices[0].textureCoords, - size, stride)}; + /* GCC 4.8 needs the arrayView() */ + Containers::stridedArrayView(Containers::arrayView(_vertexData), + &typedVertices[0].textureCoords, size, stride)}; attributes = Containers::Array{Containers::InPlaceInit, {positions, normals, textureCoords}}; } else { attributes = Containers::Array{Containers::InPlaceInit, {positions, normals}}; diff --git a/src/Magnum/Trade/Test/MeshData2DTest.cpp b/src/Magnum/Trade/Test/MeshData2DTest.cpp index dc1def1c7e..5cf12e91de 100644 --- a/src/Magnum/Trade/Test/MeshData2DTest.cpp +++ b/src/Magnum/Trade/Test/MeshData2DTest.cpp @@ -83,7 +83,9 @@ struct { {{0xff98ab_rgbf, 0xff3366_rgbf}}, &State}}, {"from MeshData", - MeshData{MeshPrimitive::Lines, {}, Indices, MeshIndexData{Indices}, {}, Vertices, { + /* GCC 4.8 needs the explicit MeshData3D conversion otherwise it tries + to use a deleted copy constructor */ + MeshData2D{MeshData{MeshPrimitive::Lines, {}, Indices, MeshIndexData{Indices}, {}, Vertices, { MeshAttributeData{MeshAttribute::Position, Containers::StridedArrayView1D{Vertices, &Vertices[0].position1, 2, sizeof(Vertex)}}, MeshAttributeData{MeshAttribute::Position, @@ -96,15 +98,17 @@ struct { Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords3, 2, sizeof(Vertex)}}, MeshAttributeData{MeshAttribute::Color, Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, - }, &State}, - MeshData{MeshPrimitive::Lines, {}, Vertices, { + }, &State}}, + /* GCC 4.8 needs the explicit MeshData3D conversion otherwise it tries + to use a deleted copy constructor */ + MeshData2D{MeshData{MeshPrimitive::Lines, {}, Vertices, { MeshAttributeData{MeshAttribute::Position, Containers::StridedArrayView1D{Vertices, &Vertices[0].position1, 2, sizeof(Vertex)}}, MeshAttributeData{MeshAttribute::TextureCoordinates, Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords1, 2, sizeof(Vertex)}}, MeshAttributeData{MeshAttribute::Color, Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, - }, &State} + }, &State}} } }; diff --git a/src/Magnum/Trade/Test/MeshData3DTest.cpp b/src/Magnum/Trade/Test/MeshData3DTest.cpp index e7ab2dd114..0965f94c33 100644 --- a/src/Magnum/Trade/Test/MeshData3DTest.cpp +++ b/src/Magnum/Trade/Test/MeshData3DTest.cpp @@ -89,7 +89,9 @@ struct { {{0xff98ab_rgbf, 0xff3366_rgbf}}, &State}}, {"from MeshData", - MeshData{MeshPrimitive::Lines, {}, Indices, MeshIndexData{Indices}, {}, Vertices, { + /* GCC 4.8 needs the explicit MeshData3D conversion otherwise it tries + to use a deleted copy constructor */ + MeshData3D{MeshData{MeshPrimitive::Lines, {}, Indices, MeshIndexData{Indices}, {}, Vertices, { MeshAttributeData{MeshAttribute::Position, Containers::StridedArrayView1D{Vertices, &Vertices[0].position1, 2, sizeof(Vertex)}}, MeshAttributeData{MeshAttribute::Position, @@ -104,8 +106,10 @@ struct { Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords3, 2, sizeof(Vertex)}}, MeshAttributeData{MeshAttribute::Color, Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, - }, &State}, - MeshData{MeshPrimitive::Lines, {}, Vertices, { + }, &State}}, + /* GCC 4.8 needs the explicit MeshData3D conversion otherwise it tries + to use a deleted copy constructor */ + MeshData3D{MeshData{MeshPrimitive::Lines, {}, Vertices, { MeshAttributeData{MeshAttribute::Position, Containers::StridedArrayView1D{Vertices, &Vertices[0].position1, 2, sizeof(Vertex)}}, MeshAttributeData{MeshAttribute::Normal, @@ -114,7 +118,7 @@ struct { Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords1, 2, sizeof(Vertex)}}, MeshAttributeData{MeshAttribute::Color, Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, - }, &State} + }, &State}} } }; From 0657d6073d3a577512cdfb33008f880eead656ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 15 Feb 2020 21:29:55 +0100 Subject: [PATCH 072/107] Primitives: MSVC 2015 is this what makes you throw up? --- src/Magnum/Primitives/Crosshair.cpp | 40 +++++++++++++----- src/Magnum/Primitives/Cube.cpp | 64 +++++++++++++++++------------ src/Magnum/Primitives/Plane.cpp | 17 +++++--- src/Magnum/Primitives/Square.cpp | 34 +++++++++------ 4 files changed, 99 insertions(+), 56 deletions(-) diff --git a/src/Magnum/Primitives/Crosshair.cpp b/src/Magnum/Primitives/Crosshair.cpp index 0b1b307515..1795f5117a 100644 --- a/src/Magnum/Primitives/Crosshair.cpp +++ b/src/Magnum/Primitives/Crosshair.cpp @@ -33,32 +33,50 @@ namespace Magnum { namespace Primitives { namespace { -constexpr Vector2 Positions2D[]{ - {-1.0f, 0.0f}, {1.0f, 0.0f}, - { 0.0f, -1.0f}, {0.0f, 1.0f} +/* Can't be just an array of Vector2 but has to be a struct, because then MSVC + 2015 fails with an assertion like + + Trade::MeshData: attribute 0 [0x7ffd6c4fb290:0x7ffd6c4fb338] is not contained in passed vertexData array [0x7ffd6c4fb170:0x7ffd6c4fb218] + + which leads me to believe that it will make two copies of the data, one for + the MeshAttributeData constructor and one for the MeshData constructor. Same + for the Cube, Square and Plane primitives. Interestingly enough, this isn't + a problem for index arrays (maybe because those are integers and not + constexpr classes?). Also not a problem for MSVC 2017 and up. */ +constexpr struct Vertex2D { + Vector2 position; +} Vertices2D[]{ + {{-1.0f, 0.0f}}, {{1.0f, 0.0f}}, + {{ 0.0f, -1.0f}}, {{0.0f, 1.0f}} }; -constexpr Vector3 Positions3D[]{ - {-1.0f, 0.0f, 0.0f}, {1.0f, 0.0f, 0.0f}, - { 0.0f, -1.0f, 0.0f}, {0.0f, 1.0f, 0.0f}, - { 0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f} +constexpr struct Vertex3D { + Vector3 position; +} Vertices3D[]{ + {{-1.0f, 0.0f, 0.0f}}, {{1.0f, 0.0f, 0.0f}}, + {{ 0.0f, -1.0f, 0.0f}}, {{0.0f, 1.0f, 0.0f}}, + {{ 0.0f, 0.0f, -1.0f}}, {{0.0f, 0.0f, 1.0f}} }; constexpr Trade::MeshAttributeData Attributes2D[]{ - Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(Positions2D)} + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(Vertices2D, &Vertices2D[0].position, + Containers::arraySize(Vertices2D), sizeof(Vertex2D))} }; constexpr Trade::MeshAttributeData Attributes3D[]{ - Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(Positions3D)} + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(Vertices3D, &Vertices3D[0].position, + Containers::arraySize(Vertices3D), sizeof(Vertex3D))} }; } Trade::MeshData crosshair2D() { - return Trade::MeshData{MeshPrimitive::Lines, {}, Positions2D, + return Trade::MeshData{MeshPrimitive::Lines, {}, Vertices2D, Trade::meshAttributeDataNonOwningArray(Attributes2D)}; } Trade::MeshData crosshair3D() { - return Trade::MeshData{MeshPrimitive::Lines, {}, Positions3D, + return Trade::MeshData{MeshPrimitive::Lines, {}, Vertices3D, Trade::meshAttributeDataNonOwningArray(Attributes3D)}; } diff --git a/src/Magnum/Primitives/Cube.cpp b/src/Magnum/Primitives/Cube.cpp index 1929ee70c4..d22f0e08c1 100644 --- a/src/Magnum/Primitives/Cube.cpp +++ b/src/Magnum/Primitives/Cube.cpp @@ -95,7 +95,11 @@ Trade::MeshData cubeSolid() { namespace { -constexpr Vector3 VerticesSolidStrip[]{ +/* Can't be just an array of Vector3 because MSVC 2015 is special. See + Crosshair.cpp for details. */ +constexpr struct VertexSolidStrip { + Vector3 position; +} VerticesSolidStrip[]{ /* Sources: https://twitter.com/Donzanoid/status/436843034966507520 http://www.asmcommunity.net/forums/topic/?id=6284#post-45209 @@ -119,24 +123,25 @@ constexpr Vector3 VerticesSolidStrip[]{ |F \| 2---3 */ - { 1.0f, 1.0f, 1.0f}, /* 3 */ - {-1.0f, 1.0f, 1.0f}, /* 2 */ - { 1.0f, -1.0f, 1.0f}, /* 6 */ - {-1.0f, -1.0f, 1.0f}, /* 7 */ - {-1.0f, -1.0f, -1.0f}, /* 4 */ - {-1.0f, 1.0f, 1.0f}, /* 2 */ - {-1.0f, 1.0f, -1.0f}, /* 0 */ - { 1.0f, 1.0f, 1.0f}, /* 3 */ - { 1.0f, 1.0f, -1.0f}, /* 1 */ - { 1.0f, -1.0f, 1.0f}, /* 6 */ - { 1.0f, -1.0f, -1.0f}, /* 5 */ - {-1.0f, -1.0f, -1.0f}, /* 4 */ - { 1.0f, 1.0f, -1.0f}, /* 1 */ - {-1.0f, 1.0f, -1.0f} /* 0 */ + {{ 1.0f, 1.0f, 1.0f}}, /* 3 */ + {{-1.0f, 1.0f, 1.0f}}, /* 2 */ + {{ 1.0f, -1.0f, 1.0f}}, /* 6 */ + {{-1.0f, -1.0f, 1.0f}}, /* 7 */ + {{-1.0f, -1.0f, -1.0f}}, /* 4 */ + {{-1.0f, 1.0f, 1.0f}}, /* 2 */ + {{-1.0f, 1.0f, -1.0f}}, /* 0 */ + {{ 1.0f, 1.0f, 1.0f}}, /* 3 */ + {{ 1.0f, 1.0f, -1.0f}}, /* 1 */ + {{ 1.0f, -1.0f, 1.0f}}, /* 6 */ + {{ 1.0f, -1.0f, -1.0f}}, /* 5 */ + {{-1.0f, -1.0f, -1.0f}}, /* 4 */ + {{ 1.0f, 1.0f, -1.0f}}, /* 1 */ + {{-1.0f, 1.0f, -1.0f}} /* 0 */ }; constexpr Trade::MeshAttributeData AttributesSolidStrip[]{ Trade::MeshAttributeData{Trade::MeshAttribute::Position, - Containers::stridedArrayView(VerticesSolidStrip)} + Containers::stridedArrayView(VerticesSolidStrip, &VerticesSolidStrip[0].position, + Containers::arraySize(VerticesSolidStrip), sizeof(VertexSolidStrip))} }; } @@ -155,20 +160,25 @@ constexpr UnsignedShort IndicesWireframe[]{ 1, 5, 2, 6, /* +X */ 0, 4, 3, 7 /* -X */ }; -constexpr Vector3 VerticesWireframe[]{ - {-1.0f, -1.0f, 1.0f}, - { 1.0f, -1.0f, 1.0f}, - { 1.0f, 1.0f, 1.0f}, - {-1.0f, 1.0f, 1.0f}, - - {-1.0f, -1.0f, -1.0f}, - { 1.0f, -1.0f, -1.0f}, - { 1.0f, 1.0f, -1.0f}, - {-1.0f, 1.0f, -1.0f} +/* Can't be just an array of Vector3 because MSVC 2015 is special. See + Crosshair.cpp for details. */ +constexpr struct VertexWireframe { + Vector3 position; +} VerticesWireframe[]{ + {{-1.0f, -1.0f, 1.0f}}, + {{ 1.0f, -1.0f, 1.0f}}, + {{ 1.0f, 1.0f, 1.0f}}, + {{-1.0f, 1.0f, 1.0f}}, + + {{-1.0f, -1.0f, -1.0f}}, + {{ 1.0f, -1.0f, -1.0f}}, + {{ 1.0f, 1.0f, -1.0f}}, + {{-1.0f, 1.0f, -1.0f}} }; constexpr Trade::MeshAttributeData AttributesWireframe[]{ Trade::MeshAttributeData{Trade::MeshAttribute::Position, - Containers::stridedArrayView(VerticesWireframe)} + Containers::stridedArrayView(VerticesWireframe, &VerticesWireframe[0].position, + Containers::arraySize(VerticesWireframe), sizeof(VertexWireframe))} }; } diff --git a/src/Magnum/Primitives/Plane.cpp b/src/Magnum/Primitives/Plane.cpp index a6cd27a9b5..ae41ba9c65 100644 --- a/src/Magnum/Primitives/Plane.cpp +++ b/src/Magnum/Primitives/Plane.cpp @@ -90,15 +90,20 @@ Trade::MeshData planeSolid(const PlaneTextureCoords textureCoords) { namespace { -constexpr Vector3 VerticesWireframe[]{ - {-1.0f, -1.0f, 0.0f}, - { 1.0f, -1.0f, 0.0f}, - { 1.0f, 1.0f, 0.0f}, - {-1.0f, 1.0f, 0.0f} +/* Can't be just an array of Vector3 because MSVC 2015 is special. See + Crosshair.cpp for details. */ +constexpr struct VertexWireframe { + Vector3 position; +} VerticesWireframe[]{ + {{-1.0f, -1.0f, 0.0f}}, + {{ 1.0f, -1.0f, 0.0f}}, + {{ 1.0f, 1.0f, 0.0f}}, + {{-1.0f, 1.0f, 0.0f}} }; constexpr Trade::MeshAttributeData AttributesWireframe[]{ Trade::MeshAttributeData{Trade::MeshAttribute::Position, - Containers::arrayView(VerticesWireframe)} + Containers::stridedArrayView(VerticesWireframe, &VerticesWireframe[0].position, + Containers::arraySize(VerticesWireframe), sizeof(VertexWireframe))} }; } diff --git a/src/Magnum/Primitives/Square.cpp b/src/Magnum/Primitives/Square.cpp index 82dd8165be..f0d9901923 100644 --- a/src/Magnum/Primitives/Square.cpp +++ b/src/Magnum/Primitives/Square.cpp @@ -33,11 +33,15 @@ namespace Magnum { namespace Primitives { namespace { -constexpr Vector2 VerticesSolid[] { - { 1.0f, -1.0f}, - { 1.0f, 1.0f}, - {-1.0f, -1.0f}, - {-1.0f, 1.0f} +/* Can't be just an array of Vector2 because MSVC 2015 is special. See + Crosshair.cpp for details. */ +constexpr struct VertexSolid { + Vector2 position; +} VerticesSolid[] { + {{ 1.0f, -1.0f}}, + {{ 1.0f, 1.0f}}, + {{-1.0f, -1.0f}}, + {{-1.0f, 1.0f}} }; constexpr struct VertexSolidTextureCoords { Vector2 position; @@ -50,7 +54,8 @@ constexpr struct VertexSolidTextureCoords { }; constexpr Trade::MeshAttributeData AttributesSolid[]{ Trade::MeshAttributeData{Trade::MeshAttribute::Position, - Containers::stridedArrayView(VerticesSolid)} + Containers::stridedArrayView(VerticesSolid, &VerticesSolid[0].position, + Containers::arraySize(VerticesSolid), sizeof(VertexSolid))}, }; constexpr Trade::MeshAttributeData AttributesSolidTextureCoords[]{ Trade::MeshAttributeData{Trade::MeshAttribute::Position, @@ -78,15 +83,20 @@ Trade::MeshData squareSolid(const SquareTextureCoords textureCoords) { namespace { -constexpr Vector2 VerticesWireframe[]{ - {-1.0f, -1.0f}, - { 1.0f, -1.0f}, - { 1.0f, 1.0f}, - {-1.0f, 1.0f} +/* Can't be just an array of Vector2 because MSVC 2015 is special. See + Crosshair.cpp for details. */ +constexpr struct VertexWireframe { + Vector2 position; +} VerticesWireframe[]{ + {{-1.0f, -1.0f}}, + {{ 1.0f, -1.0f}}, + {{ 1.0f, 1.0f}}, + {{-1.0f, 1.0f}} }; constexpr Trade::MeshAttributeData AttributesWireframe[]{ Trade::MeshAttributeData{Trade::MeshAttribute::Position, - Containers::stridedArrayView(VerticesWireframe)} + Containers::stridedArrayView(VerticesWireframe, &VerticesWireframe[0].position, + Containers::arraySize(VerticesWireframe), sizeof(VertexWireframe))} }; } From a02e9465d6b19e3ba06dbd4e98c9713308ac2832 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 26 Feb 2020 10:42:52 +0100 Subject: [PATCH 073/107] Trade: allow retrieving importer data directly by name. --- doc/changelog.dox | 2 + src/Magnum/Trade/AbstractImporter.cpp | 87 +++++ src/Magnum/Trade/AbstractImporter.h | 168 +++++++++- .../Trade/Test/AbstractImporterTest.cpp | 316 ++++++++++++++---- 4 files changed, 502 insertions(+), 71 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 160c07cdcd..9f009fcba3 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -185,6 +185,8 @@ See also: Equivalent APIs are exposed in both @ref Trade::ImageData and @ref Trade::MeshData as well. See @ref Trade-AnimationData-usage-mutable for more information. +- New convenience @ref Trade::AbstractImporter::material(const std::string&) + etc. APIs allowing to directly get a data using a string name @subsubsection changelog-latest-new-vk Vk library diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index 3939dae591..5606fe0841 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -248,6 +248,13 @@ Containers::Optional AbstractImporter::doScene(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::scene(): not implemented", {}); } +Containers::Optional AbstractImporter::scene(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::scene(): no file opened", {}); + const Int id = doSceneForName(name); + if(id == -1) return {}; + return scene(id); /* not doScene(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::animationCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::animationCount(): no file opened", {}); return doAnimationCount(); @@ -285,6 +292,13 @@ Containers::Optional AbstractImporter::doAnimation(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::animation(): not implemented", {}); } +Containers::Optional AbstractImporter::animation(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::animation(): no file opened", {}); + const Int id = doAnimationForName(name); + if(id == -1) return {}; + return animation(id); /* not doAnimation(), so we get the checks also */ +} + UnsignedInt AbstractImporter::lightCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::lightCount(): no file opened", {}); return doLightCount(); @@ -317,6 +331,13 @@ Containers::Optional AbstractImporter::doLight(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::light(): not implemented", {}); } +Containers::Optional AbstractImporter::light(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::light(): no file opened", {}); + const Int id = doLightForName(name); + if(id == -1) return {}; + return light(id); /* not doLight(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::cameraCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::cameraCount(): no file opened", {}); return doCameraCount(); @@ -349,6 +370,13 @@ Containers::Optional AbstractImporter::doCamera(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::camera(): not implemented", {}); } +Containers::Optional AbstractImporter::camera(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::camera(): no file opened", {}); + const Int id = doCameraForName(name); + if(id == -1) return {}; + return camera(id); /* not doCamera(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::object2DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object2DCount(): no file opened", {}); return doObject2DCount(); @@ -381,6 +409,13 @@ Containers::Pointer AbstractImporter::doObject2D(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::object2D(): not implemented", {}); } +Containers::Pointer AbstractImporter::object2D(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object2D(): no file opened", {}); + const Int id = doObject2DForName(name); + if(id == -1) return {}; + return object2D(id); /* not doObject2D(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::object3DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object3DCount(): no file opened", {}); return doObject3DCount(); @@ -413,6 +448,13 @@ Containers::Pointer AbstractImporter::doObject3D(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::object3D(): not implemented", {}); } +Containers::Pointer AbstractImporter::object3D(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::object3D(): no file opened", {}); + const Int id = doObject3DForName(name); + if(id == -1) return {}; + return object3D(id); /* not doObject3D(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::meshCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshCount(): no file opened", {}); return doMeshCount(); @@ -451,6 +493,13 @@ Containers::Optional AbstractImporter::doMesh(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::mesh(): not implemented", {}); } +Containers::Optional AbstractImporter::mesh(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh(): no file opened", {}); + const Int id = doMeshForName(name); + if(id == -1) return {}; + return mesh(id); /* not doMesh(), so we get the checks also */ +} + MeshAttribute AbstractImporter::meshAttributeForName(const std::string& name) { const MeshAttribute out = doMeshAttributeForName(name); CORRADE_ASSERT(out == MeshAttribute{} || isMeshAttributeCustom(out), @@ -592,6 +641,13 @@ Containers::Pointer AbstractImporter::doMaterial(UnsignedI CORRADE_ASSERT(false, "Trade::AbstractImporter::material(): not implemented", {}); } +Containers::Pointer AbstractImporter::material(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::material(): no file opened", {}); + const Int id = doMaterialForName(name); + if(id == -1) return {}; + return material(id); /* not doMaterial(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::textureCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::textureCount(): no file opened", {}); return doTextureCount(); @@ -624,6 +680,13 @@ Containers::Optional AbstractImporter::doTexture(UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::texture(): not implemented", {}); } +Containers::Optional AbstractImporter::texture(const std::string& name) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::texture(): no file opened", {}); + const Int id = doTextureForName(name); + if(id == -1) return {}; + return texture(id); /* not doTexture(), so we get the range checks also */ +} + UnsignedInt AbstractImporter::image1DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image1DCount(): no file opened", {}); return doImage1DCount(); @@ -680,6 +743,14 @@ Containers::Optional AbstractImporter::doImage1D(UnsignedInt, Unsig CORRADE_ASSERT(false, "Trade::AbstractImporter::image1D(): not implemented", {}); } +Containers::Optional AbstractImporter::image1D(const std::string& name, const UnsignedInt level) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image1D(): no file opened", {}); + const Int id = doImage1DForName(name); + if(id == -1) return {}; + /* not doImage1D(), so we get the range checks also */ + return image1D(id, level); +} + UnsignedInt AbstractImporter::image2DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image2DCount(): no file opened", {}); return doImage2DCount(); @@ -736,6 +807,14 @@ Containers::Optional AbstractImporter::doImage2D(UnsignedInt, Unsig CORRADE_ASSERT(false, "Trade::AbstractImporter::image2D(): not implemented", {}); } +Containers::Optional AbstractImporter::image2D(const std::string& name, const UnsignedInt level) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image2D(): no file opened", {}); + const Int id = doImage2DForName(name); + if(id == -1) return {}; + /* not doImage2D(), so we get the range checks also */ + return image2D(id, level); +} + UnsignedInt AbstractImporter::image3DCount() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image3DCount(): no file opened", {}); return doImage3DCount(); @@ -792,6 +871,14 @@ Containers::Optional AbstractImporter::doImage3D(UnsignedInt, Unsig CORRADE_ASSERT(false, "Trade::AbstractImporter::image3D(): not implemented", {}); } +Containers::Optional AbstractImporter::image3D(const std::string& name, const UnsignedInt level) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::image3D(): no file opened", {}); + const Int id = doImage3DForName(name); + if(id == -1) return {}; + /* not doImage3D(), so we get the range checks also */ + return image3D(id, level); +} + const void* AbstractImporter::importerState() const { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::importerState(): no file opened", {}); return doImporterState(); diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index a897e07ee5..58ccdb742d 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -491,7 +491,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no scene for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref sceneName() + * @see @ref sceneName(), @ref scene(const std::string&) */ Int sceneForName(const std::string& name); @@ -510,9 +510,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given scene or @ref Containers::NullOpt if import failed. * Expects that a file is opened. + * @see @ref scene(const std::string&) */ Containers::Optional scene(UnsignedInt id); + /** + * @brief Scene for given name + * @m_since_latest + * + * A convenience API combining @ref sceneForName() and + * @ref scene(UnsignedInt). Returns @ref Containers::NullOpt either + * if @ref sceneForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Optional scene(const std::string& name); + /** * @brief Animation count * @@ -525,7 +537,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no animation for given name exists, returns @cpp -1 @ce. Expects * that a file is opened. - * @see @ref animationName() + * @see @ref animationName(), @ref animation(const std::string&) */ Int animationForName(const std::string& name); @@ -544,9 +556,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given animation or @ref Containers::NullOpt if importing * failed. Expects that a file is opened. + * @see @ref animation(const std::string&) */ Containers::Optional animation(UnsignedInt id); + /** + * @brief Animation for given name + * @m_since_latest + * + * A convenience API combining @ref animationForName() and + * @ref animation(UnsignedInt). Returns @ref Containers::NullOpt either + * if @ref animationForName() returns @cpp -1 @ce or if importing + * fails. Expects that a file is opened. + */ + Containers::Optional animation(const std::string& name); + /** * @brief Light count * @@ -559,7 +583,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no light for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref lightName() + * @see @ref lightName(), @ref light(const std::string&) */ Int lightForName(const std::string& name); @@ -578,9 +602,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given light or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @see @ref light(const std::string&) */ Containers::Optional light(UnsignedInt id); + /** + * @brief Light for given name + * @m_since_latest + * + * A convenience API combining @ref lightForName() and + * @ref light(UnsignedInt). Returns @ref Containers::NullOpt either if + * @ref lightForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Optional light(const std::string& name); + /** * @brief Camera count * @@ -593,7 +629,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no camera for given name exists, returns @cpp -1 @ce. Expects * that a file is opened. - * @see @ref cameraName() + * @see @ref cameraName(), @ref camera(const std::string&) */ Int cameraForName(const std::string& name); @@ -612,9 +648,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given camera or @ref Containers::NullOpt if importing * failed. Expects that a file is opened. + * @see @ref camera(const std::string&) */ Containers::Optional camera(UnsignedInt id); + /** + * @brief Camera for given name + * @m_since_latest + * + * A convenience API combining @ref cameraForName() and + * @ref camera(UnsignedInt). Returns @ref Containers::NullOpt either if + * @ref cameraForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Optional camera(const std::string& name); + /** * @brief Two-dimensional object count * @@ -627,7 +675,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no scene for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref object2DName() + * @see @ref object2DName(), @ref object2D(const std::string&) */ Int object2DForName(const std::string& name); @@ -646,9 +694,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given object or @cpp nullptr @ce if importing failed. * Expects that a file is opened. + * @see @ref object2D(const std::string&) */ Containers::Pointer object2D(UnsignedInt id); + /** + * @brief Two-dimensional object for given name + * @m_since_latest + * + * A convenience API combining @ref object2DForName() and + * @ref object2D(UnsignedInt). Returns @cpp nullptr @ce either if + * @ref object2DForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Pointer object2D(const std::string& name); + /** * @brief Three-dimensional object count * @@ -661,7 +721,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no scene for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref object3DName() + * @see @ref object3DName(), @ref object3D(const std::string&) */ Int object3DForName(const std::string& name); @@ -680,9 +740,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given object or @cpp nullptr @ce if importing failed. * Expects that a file is opened. + * @see @ref object3D(const std::string&) */ Containers::Pointer object3D(UnsignedInt id); + /** + * @brief Three-dimensional object for given name + * @m_since_latest + * + * A convenience API combining @ref object3DForName() and + * @ref object3D(UnsignedInt). Returns @cpp nullptr @ce either if + * @ref object3DForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Pointer object3D(const std::string& name); + /** * @brief Mesh count * @m_since_latest @@ -697,7 +769,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no mesh for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref meshName() + * @see @ref meshName(), @ref mesh(const std::string&) */ Int meshForName(const std::string& name); @@ -718,9 +790,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given mesh or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @see @ref mesh(const std::string&) */ Containers::Optional mesh(UnsignedInt id); + /** + * @brief Mesh for given name + * @m_since_latest + * + * A convenience API combining @ref meshForName() and + * @ref mesh(UnsignedInt). Returns @ref Containers::NullOpt either if + * @ref meshForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Optional mesh(const std::string& name); + /** * @brief Mesh attribute for given name * @m_since_latest @@ -852,7 +936,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @param id Material ID, from range [0, @ref materialCount()). * * Expects that a file is opened. - * @see @ref materialForName() + * @see @ref materialForName(), @ref material(const std::string&) */ std::string materialName(UnsignedInt id); @@ -862,9 +946,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given material or @cpp nullptr @ce if importing failed. * Expects that a file is opened. + * @see @ref material(const std::string&) */ Containers::Pointer material(UnsignedInt id); + /** + * @brief Material for given name + * @m_since_latest + * + * A convenience API combining @ref materialForName() and + * @ref material(UnsignedInt). Returns @ref Containers::NullOpt either + * if @ref materialForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Pointer material(const std::string& name); + /** * @brief Texture count * @@ -877,7 +973,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no texture for given name exists, returns @cpp -1 @ce. Expects * that a file is opened. - * @see @ref textureName() + * @see @ref textureName(), @ref texture(const std::string&) */ Int textureForName(const std::string& name); @@ -896,9 +992,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given texture or @ref Containers::NullOpt if importing * failed. Expects that a file is opened. + * @see @ref texture(const std::string&) */ Containers::Optional texture(UnsignedInt id); + /** + * @brief Texture for given name + * @m_since_latest + * + * A convenience API combining @ref textureForName() and + * @ref texture(UnsignedInt). Returns @ref Containers::NullOpt either + * if @ref textureForName() returns @cpp -1 @ce or if importing fails. + * Expects that a file is opened. + */ + Containers::Optional texture(const std::string& name); + /** * @brief One-dimensional image count * @@ -922,7 +1030,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no image for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref image1DName() + * @see @ref image1DName(), @ref image1D(const std::string&, UnsignedInt) */ Int image1DForName(const std::string& name); @@ -942,9 +1050,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given image or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @see @ref image1D(const std::string&, UnsignedInt) */ Containers::Optional image1D(UnsignedInt id, UnsignedInt level = 0); + /** + * @brief One-dimensional image for given name + * @m_since_latest + * + * A convenience API combining @ref image1DForName() and + * @ref image1D(UnsignedInt, UnsignedInt). Returns + * @ref Containers::NullOpt either if @ref image1DForName() returns + * @cpp -1 @ce or if importing fails. Expects that a file is opened. + */ + Containers::Optional image1D(const std::string& name, UnsignedInt level = 0); + /** * @brief Two-dimensional image count * @@ -968,7 +1088,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no image for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref image2DName() + * @see @ref image2DName(), @ref image2D(const std::string&, UnsignedInt) */ Int image2DForName(const std::string& name); @@ -988,9 +1108,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given image or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @see @ref image2D(const std::string&, UnsignedInt) */ Containers::Optional image2D(UnsignedInt id, UnsignedInt level = 0); + /** + * @brief Two-dimensional image for given name + * @m_since_latest + * + * A convenience API combining @ref image2DForName() and + * @ref image2D(UnsignedInt, UnsignedInt). Returns + * @ref Containers::NullOpt either if @ref image2DForName() returns + * @cpp -1 @ce or if importing fails. Expects that a file is opened. + */ + Containers::Optional image2D(const std::string& name, UnsignedInt level = 0); + /** * @brief Three-dimensional image count * @@ -1014,7 +1146,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * If no image for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref image3DName() + * @see @ref image3DName(), @ref image3D(const std::string&, UnsignedInt) */ Int image3DForName(const std::string& name); @@ -1034,9 +1166,21 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * * Returns given image or @ref Containers::NullOpt if importing failed. * Expects that a file is opened. + * @see @ref image3D(const std::string&, UnsignedInt) */ Containers::Optional image3D(UnsignedInt id, UnsignedInt level = 0); + /** + * @brief Three-dimensional image for given name + * @m_since_latest + * + * A convenience API combining @ref image3DForName() and + * @ref image3D(UnsignedInt, UnsignedInt). Returns + * @ref Containers::NullOpt either if @ref image3DForName() returns + * @cpp -1 @ce or if importing fails. Expects that a file is opened. + */ + Containers::Optional image3D(const std::string& name, UnsignedInt level = 0); + /*@}*/ /** diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 1535abb056..ed02d1a9e8 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -1097,9 +1097,18 @@ void AbstractImporterTest::scene() { CORRADE_COMPARE(importer.sceneForName("eighth"), 7); CORRADE_COMPARE(importer.sceneName(7), "eighth"); - auto data = importer.scene(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.scene(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.scene("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.scene("foo")); + } } void AbstractImporterTest::sceneCountNotImplemented() { @@ -1219,7 +1228,10 @@ void AbstractImporterTest::sceneNoFile() { Error redirectError{&out}; importer.scene(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::scene(): no file opened\n"); + importer.scene("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::scene(): no file opened\n" + "Trade::AbstractImporter::scene(): no file opened\n"); } void AbstractImporterTest::sceneOutOfRange() { @@ -1268,9 +1280,18 @@ void AbstractImporterTest::animation() { CORRADE_COMPARE(importer.animationForName("eighth"), 7); CORRADE_COMPARE(importer.animationName(7), "eighth"); - auto data = importer.animation(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.animation(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.animation("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.animation("foo")); + } } void AbstractImporterTest::animationCountNotImplemented() { @@ -1390,7 +1411,10 @@ void AbstractImporterTest::animationNoFile() { Error redirectError{&out}; importer.animation(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animation(): no file opened\n"); + importer.animation("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::animation(): no file opened\n" + "Trade::AbstractImporter::animation(): no file opened\n"); } void AbstractImporterTest::animationOutOfRange() { @@ -1457,6 +1481,7 @@ void AbstractImporterTest::animationCustomDataDeleter() { void doClose() override {} UnsignedInt doAnimationCount() const override { return 1; } + Int doAnimationForName(const std::string&) override { return 0; } Containers::Optional doAnimation(UnsignedInt) override { return AnimationData{Containers::Array{nullptr, 0, [](char*, std::size_t) {}}, nullptr}; } @@ -1466,7 +1491,10 @@ void AbstractImporterTest::animationCustomDataDeleter() { Error redirectError{&out}; importer.animation(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n"); + importer.animation(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n"); } void AbstractImporterTest::animationCustomTrackDeleter() { @@ -1476,6 +1504,7 @@ void AbstractImporterTest::animationCustomTrackDeleter() { void doClose() override {} UnsignedInt doAnimationCount() const override { return 1; } + Int doAnimationForName(const std::string&) override { return 0; } Containers::Optional doAnimation(UnsignedInt) override { return AnimationData{nullptr, Containers::Array{nullptr, 0, [](AnimationTrackData*, std::size_t) {}}}; } @@ -1485,7 +1514,10 @@ void AbstractImporterTest::animationCustomTrackDeleter() { Error redirectError{&out}; importer.animation(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n"); + importer.animation(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::animation(): implementation is not allowed to use a custom Array deleter\n"); } void AbstractImporterTest::light() { @@ -1513,9 +1545,18 @@ void AbstractImporterTest::light() { CORRADE_COMPARE(importer.lightForName("eighth"), 7); CORRADE_COMPARE(importer.lightName(7), "eighth"); - auto data = importer.light(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.light(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.light("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.light("foo")); + } } void AbstractImporterTest::lightCountNotImplemented() { @@ -1635,7 +1676,10 @@ void AbstractImporterTest::lightNoFile() { Error redirectError{&out}; importer.light(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::light(): no file opened\n"); + importer.light("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::light(): no file opened\n" + "Trade::AbstractImporter::light(): no file opened\n"); } void AbstractImporterTest::lightOutOfRange() { @@ -1679,9 +1723,18 @@ void AbstractImporterTest::camera() { CORRADE_COMPARE(importer.cameraForName("eighth"), 7); CORRADE_COMPARE(importer.cameraName(7), "eighth"); - auto data = importer.camera(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.camera(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.camera("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.camera("foo")); + } } void AbstractImporterTest::cameraCountNotImplemented() { @@ -1801,7 +1854,10 @@ void AbstractImporterTest::cameraNoFile() { Error redirectError{&out}; importer.camera(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::camera(): no file opened\n"); + importer.camera("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::camera(): no file opened\n" + "Trade::AbstractImporter::camera(): no file opened\n"); } void AbstractImporterTest::cameraOutOfRange() { @@ -1845,9 +1901,18 @@ void AbstractImporterTest::object2D() { CORRADE_COMPARE(importer.object2DForName("eighth"), 7); CORRADE_COMPARE(importer.object2DName(7), "eighth"); - auto data = importer.object2D(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.object2D(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.object2D("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.object2D("foo")); + } } void AbstractImporterTest::object2DCountNotImplemented() { @@ -1967,7 +2032,10 @@ void AbstractImporterTest::object2DNoFile() { Error redirectError{&out}; importer.object2D(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object2D(): no file opened\n"); + importer.object2D("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::object2D(): no file opened\n" + "Trade::AbstractImporter::object2D(): no file opened\n"); } void AbstractImporterTest::object2DOutOfRange() { @@ -2011,9 +2079,18 @@ void AbstractImporterTest::object3D() { CORRADE_COMPARE(importer.object3DForName("eighth"), 7); CORRADE_COMPARE(importer.object3DName(7), "eighth"); - auto data = importer.object3D(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.object3D(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.object3D("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.object3D("foo")); + } } void AbstractImporterTest::object3DCountNotImplemented() { @@ -2133,7 +2210,10 @@ void AbstractImporterTest::object3DNoFile() { Error redirectError{&out}; importer.object3D(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::object3D(): no file opened\n"); + importer.object3D("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::object3D(): no file opened\n" + "Trade::AbstractImporter::object3D(): no file opened\n"); } void AbstractImporterTest::object3DOutOfRange() { @@ -2179,9 +2259,18 @@ void AbstractImporterTest::mesh() { CORRADE_COMPARE(importer.meshForName("eighth"), 7); CORRADE_COMPARE(importer.meshName(7), "eighth"); - auto data = importer.mesh(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.mesh(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.mesh("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.mesh("foo")); + } } #ifdef MAGNUM_BUILD_DEPRECATED @@ -2340,7 +2429,10 @@ void AbstractImporterTest::meshNoFile() { Error redirectError{&out}; importer.mesh(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): no file opened\n"); + importer.mesh("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::mesh(): no file opened\n" + "Trade::AbstractImporter::mesh(): no file opened\n"); } void AbstractImporterTest::meshOutOfRange() { @@ -2418,6 +2510,7 @@ void AbstractImporterTest::meshCustomIndexDataDeleter() { void doClose() override {} UnsignedInt doMeshCount() const override { return 1; } + Int doMeshForName(const std::string&) override { return 0; } Containers::Optional doMesh(UnsignedInt) override { return MeshData{MeshPrimitive::Triangles, Containers::Array{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}}; } @@ -2429,7 +2522,10 @@ void AbstractImporterTest::meshCustomIndexDataDeleter() { Error redirectError{&out}; importer.mesh(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); + importer.mesh(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); } void AbstractImporterTest::meshCustomVertexDataDeleter() { @@ -2439,6 +2535,7 @@ void AbstractImporterTest::meshCustomVertexDataDeleter() { void doClose() override {} UnsignedInt doMeshCount() const override { return 1; } + Int doMeshForName(const std::string&) override { return 0; } Containers::Optional doMesh(UnsignedInt) override { return MeshData{MeshPrimitive::Triangles, Containers::Array{nullptr, 0, [](char*, std::size_t) {}}, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}}; } @@ -2448,7 +2545,10 @@ void AbstractImporterTest::meshCustomVertexDataDeleter() { Error redirectError{&out}; importer.mesh(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); + importer.mesh(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); } void AbstractImporterTest::meshCustomAttributesDeleter() { @@ -2458,6 +2558,7 @@ void AbstractImporterTest::meshCustomAttributesDeleter() { void doClose() override {} UnsignedInt doMeshCount() const override { return 1; } + Int doMeshForName(const std::string&) override { return 0; } Containers::Optional doMesh(UnsignedInt) override { return MeshData{MeshPrimitive::Triangles, nullptr, Containers::Array{&positions, 1, [](MeshAttributeData*, std::size_t) {}}}; } @@ -2469,7 +2570,11 @@ void AbstractImporterTest::meshCustomAttributesDeleter() { Error redirectError{&out}; importer.mesh(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n"); + importer.mesh(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::mesh(): implementation is not allowed to use a custom Array deleter\n" + ); } void AbstractImporterTest::meshAttributeName() { @@ -2933,9 +3038,18 @@ void AbstractImporterTest::material() { CORRADE_COMPARE(importer.materialForName("eighth"), 7); CORRADE_COMPARE(importer.materialName(7), "eighth"); - auto data = importer.material(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.material(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.material("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.material("foo")); + } } void AbstractImporterTest::materialCountNotImplemented() { @@ -3055,7 +3169,10 @@ void AbstractImporterTest::materialNoFile() { Error redirectError{&out}; importer.material(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::material(): no file opened\n"); + importer.material("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::material(): no file opened\n" + "Trade::AbstractImporter::material(): no file opened\n"); } void AbstractImporterTest::materialOutOfRange() { @@ -3099,9 +3216,18 @@ void AbstractImporterTest::texture() { CORRADE_COMPARE(importer.textureForName("eighth"), 7); CORRADE_COMPARE(importer.textureName(7), "eighth"); - auto data = importer.texture(7); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.texture(7); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.texture("eighth"); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.texture("foo")); + } } void AbstractImporterTest::textureCountNotImplemented() { @@ -3221,7 +3347,10 @@ void AbstractImporterTest::textureNoFile() { Error redirectError{&out}; importer.texture(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::texture(): no file opened\n"); + importer.texture("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::texture(): no file opened\n" + "Trade::AbstractImporter::texture(): no file opened\n"); } void AbstractImporterTest::textureOutOfRange() { @@ -3270,9 +3399,18 @@ void AbstractImporterTest::image1D() { CORRADE_COMPARE(importer.image1DForName("eighth"), 7); CORRADE_COMPARE(importer.image1DName(7), "eighth"); - auto data = importer.image1D(7, 2); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.image1D(7, 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.image1D("eighth", 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.image1D("foo")); + } } void AbstractImporterTest::image1DCountNotImplemented() { @@ -3346,6 +3484,7 @@ void AbstractImporterTest::image1DLevelCountZero() { void doClose() override {} UnsignedInt doImage1DCount() const override { return 8; } + Int doImage1DForName(const std::string&) override { return 0; } UnsignedInt doImage1DLevelCount(UnsignedInt) override { return 0; } } importer; @@ -3355,8 +3494,10 @@ void AbstractImporterTest::image1DLevelCountZero() { /* This should print a similar message instead of a confusing "level 1 out of range for 0 entries" */ importer.image1D(7, 1); + importer.image1D("", 1); CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image1DLevelCount(): implementation reported zero levels\n" + "Trade::AbstractImporter::image1D(): implementation reported zero levels\n" "Trade::AbstractImporter::image1D(): implementation reported zero levels\n"); } @@ -3453,7 +3594,10 @@ void AbstractImporterTest::image1DNoFile() { Error redirectError{&out}; importer.image1D(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image1D(): no file opened\n"); + importer.image1D("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image1D(): no file opened\n" + "Trade::AbstractImporter::image1D(): no file opened\n"); } void AbstractImporterTest::image1DOutOfRange() { @@ -3479,13 +3623,17 @@ void AbstractImporterTest::image1DLevelOutOfRange() { void doClose() override {} UnsignedInt doImage1DCount() const override { return 8; } + Int doImage1DForName(const std::string&) override { return 0; } UnsignedInt doImage1DLevelCount(UnsignedInt) override { return 3; } } importer; std::ostringstream out; Error redirectError{&out}; importer.image1D(7, 3); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image1D(): level 3 out of range for 3 entries\n"); + importer.image1D("", 3); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image1D(): level 3 out of range for 3 entries\n" + "Trade::AbstractImporter::image1D(): level 3 out of range for 3 entries\n"); } void AbstractImporterTest::image1DNonOwningDeleter() { @@ -3533,6 +3681,7 @@ void AbstractImporterTest::image1DCustomDeleter() { void doClose() override {} UnsignedInt doImage1DCount() const override { return 1; } + Int doImage1DForName(const std::string&) override { return 0; } Containers::Optional doImage1D(UnsignedInt, UnsignedInt) override { return ImageData1D{PixelFormat::RGBA8Unorm, {}, Containers::Array{nullptr, 0, [](char*, std::size_t) {}}}; } @@ -3542,7 +3691,10 @@ void AbstractImporterTest::image1DCustomDeleter() { Error redirectError{&out}; importer.image1D(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter\n"); + importer.image1D(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::image1D(): implementation is not allowed to use a custom Array deleter\n"); } void AbstractImporterTest::image2D() { @@ -3575,9 +3727,18 @@ void AbstractImporterTest::image2D() { CORRADE_COMPARE(importer.image2DForName("eighth"), 7); CORRADE_COMPARE(importer.image2DName(7), "eighth"); - auto data = importer.image2D(7, 2); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.image2D(7, 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.image2D("eighth", 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.image2D("foo")); + } } void AbstractImporterTest::image2DCountNotImplemented() { @@ -3651,6 +3812,7 @@ void AbstractImporterTest::image2DLevelCountZero() { void doClose() override {} UnsignedInt doImage2DCount() const override { return 8; } + Int doImage2DForName(const std::string&) override { return 0; } UnsignedInt doImage2DLevelCount(UnsignedInt) override { return 0; } } importer; @@ -3660,8 +3822,10 @@ void AbstractImporterTest::image2DLevelCountZero() { /* This should print a similar message instead of a confusing "level 1 out of range for 0 entries" */ importer.image2D(7, 1); + importer.image2D(7, 1); CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image2DLevelCount(): implementation reported zero levels\n" + "Trade::AbstractImporter::image2D(): implementation reported zero levels\n" "Trade::AbstractImporter::image2D(): implementation reported zero levels\n"); } @@ -3758,7 +3922,10 @@ void AbstractImporterTest::image2DNoFile() { Error redirectError{&out}; importer.image2D(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image2D(): no file opened\n"); + importer.image2D("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image2D(): no file opened\n" + "Trade::AbstractImporter::image2D(): no file opened\n"); } void AbstractImporterTest::image2DOutOfRange() { @@ -3784,13 +3951,17 @@ void AbstractImporterTest::image2DLevelOutOfRange() { void doClose() override {} UnsignedInt doImage2DCount() const override { return 8; } + Int doImage2DForName(const std::string&) override { return 0; } UnsignedInt doImage2DLevelCount(UnsignedInt) override { return 3; } } importer; std::ostringstream out; Error redirectError{&out}; importer.image2D(7, 3); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image2D(): level 3 out of range for 3 entries\n"); + importer.image2D("", 3); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image2D(): level 3 out of range for 3 entries\n" + "Trade::AbstractImporter::image2D(): level 3 out of range for 3 entries\n"); } void AbstractImporterTest::image2DNonOwningDeleter() { @@ -3838,6 +4009,7 @@ void AbstractImporterTest::image2DCustomDeleter() { void doClose() override {} UnsignedInt doImage2DCount() const override { return 1; } + Int doImage2DForName(const std::string&) override { return 0; } Containers::Optional doImage2D(UnsignedInt, UnsignedInt) override { return ImageData2D{PixelFormat::RGBA8Unorm, {}, Containers::Array{nullptr, 0, [](char*, std::size_t) {}}}; } @@ -3847,7 +4019,10 @@ void AbstractImporterTest::image2DCustomDeleter() { Error redirectError{&out}; importer.image2D(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter\n"); + importer.image2D(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::image2D(): implementation is not allowed to use a custom Array deleter\n"); } void AbstractImporterTest::image3D() { @@ -3880,9 +4055,18 @@ void AbstractImporterTest::image3D() { CORRADE_COMPARE(importer.image3DForName("eighth"), 7); CORRADE_COMPARE(importer.image3DName(7), "eighth"); - auto data = importer.image3D(7, 2); - CORRADE_VERIFY(data); - CORRADE_COMPARE(data->importerState(), &state); + { + auto data = importer.image3D(7, 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + auto data = importer.image3D("eighth", 2); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->importerState(), &state); + } { + /* This should fail gracefully, not assert */ + CORRADE_VERIFY(!importer.image3D("foo")); + } } void AbstractImporterTest::image3DCountNotImplemented() { @@ -3957,6 +4141,7 @@ void AbstractImporterTest::image3DLevelCountZero() { void doClose() override {} UnsignedInt doImage3DCount() const override { return 8; } + Int doImage3DForName(const std::string&) override { return 0; } UnsignedInt doImage3DLevelCount(UnsignedInt) override { return 0; } } importer; @@ -3966,8 +4151,10 @@ void AbstractImporterTest::image3DLevelCountZero() { /* This should print a similar message instead of a confusing "level 1 out of range for 0 entries" */ importer.image3D(7, 1); + importer.image3D("", 1); CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image3DLevelCount(): implementation reported zero levels\n" + "Trade::AbstractImporter::image3D(): implementation reported zero levels\n" "Trade::AbstractImporter::image3D(): implementation reported zero levels\n"); } @@ -4064,7 +4251,10 @@ void AbstractImporterTest::image3DNoFile() { Error redirectError{&out}; importer.image3D(42); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image3D(): no file opened\n"); + importer.image3D("foo"); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image3D(): no file opened\n" + "Trade::AbstractImporter::image3D(): no file opened\n"); } void AbstractImporterTest::image3DOutOfRange() { @@ -4090,13 +4280,17 @@ void AbstractImporterTest::image3DLevelOutOfRange() { void doClose() override {} UnsignedInt doImage3DCount() const override { return 8; } + Int doImage3DForName(const std::string&) override { return 0; } UnsignedInt doImage3DLevelCount(UnsignedInt) override { return 3; } } importer; std::ostringstream out; Error redirectError{&out}; importer.image3D(7, 3); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image3D(): level 3 out of range for 3 entries\n"); + importer.image3D("", 3); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image3D(): level 3 out of range for 3 entries\n" + "Trade::AbstractImporter::image3D(): level 3 out of range for 3 entries\n"); } void AbstractImporterTest::image3DNonOwningDeleter() { @@ -4144,6 +4338,7 @@ void AbstractImporterTest::image3DCustomDeleter() { void doClose() override {} UnsignedInt doImage3DCount() const override { return 1; } + Int doImage3DForName(const std::string&) override { return 0; } Containers::Optional doImage3D(UnsignedInt, UnsignedInt) override { return ImageData3D{PixelFormat::RGBA8Unorm, {}, Containers::Array{nullptr, 0, [](char*, std::size_t) {}}}; } @@ -4153,7 +4348,10 @@ void AbstractImporterTest::image3DCustomDeleter() { Error redirectError{&out}; importer.image3D(0); - CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter\n"); + importer.image3D(""); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter\n" + "Trade::AbstractImporter::image3D(): implementation is not allowed to use a custom Array deleter\n"); } void AbstractImporterTest::importerState() { From 5e81d10e1312e4daab8ecc0b7a36deab1f1d7687 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 16 Feb 2020 16:17:20 +0100 Subject: [PATCH 074/107] Trade: pack MeshAttributeData internals better. Before it was a 32-byte structure with 3 bytes free (or a 20-byte structure with 3 bytes free), now it's a 24-byte structure with 5 bytes free. Exploiting the fact that strides can't be too high for a GPU anyway (so 2 bytes is enough instead of 8), and vertex count is capped to 32bit by MeshData anyway (so no need for 8 also), saving 10 bytes on a 64-bit build. --- src/Magnum/Trade/MeshData.cpp | 155 ++++++++++++++----------- src/Magnum/Trade/MeshData.h | 49 ++++++-- src/Magnum/Trade/Test/MeshDataTest.cpp | 30 +++-- 3 files changed, 141 insertions(+), 93 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index cb930b30d6..7e597334d1 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -57,7 +57,7 @@ MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexForma logic */ /** @todo support zero / negative stride? would be hard to transfer to GL */ CORRADE_ASSERT(data.empty() || std::ptrdiff_t(vertexFormatSize(format)) <= data.stride(), - "Trade::MeshAttributeData: view stride" << data.stride() << "is not large enough to contain" << format, ); + "Trade::MeshAttributeData: expected stride to be positive and enough to fit" << format << Debug::nospace << ", got" << data.stride(), ); } MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, format, Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, nullptr} { @@ -83,7 +83,7 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde "Trade::MeshData: indices are expected to be valid if there are no attributes and vertex count isn't passed explicitly", ); /** @todo some better value? attributeless indexed with defined vertex count? */ _vertexCount = 0; - } else _vertexCount = _attributes[0]._data.size(); + } else _vertexCount = _attributes[0]._vertexCount; CORRADE_ASSERT(!_indices.empty() || _indexData.empty(), "Trade::MeshData: indexData passed for a non-indexed mesh", ); @@ -97,12 +97,12 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde const MeshAttributeData& attribute = _attributes[i]; CORRADE_ASSERT(attribute._format != VertexFormat{}, "Trade::MeshData: attribute" << i << "doesn't specify anything", ); - - const Containers::StridedArrayView1D data = Containers::arrayCast(attribute._data); - CORRADE_ASSERT(data.size() == _vertexCount, - "Trade::MeshData: attribute" << i << "has" << data.size() << "vertices but" << _vertexCount << "expected", ); - CORRADE_ASSERT(data.empty() || (&data.front() >= _vertexData.begin() && &data.back() + vertexFormatSize(attribute._format) <= _vertexData.end()), - "Trade::MeshData: attribute" << i << "[" << Debug::nospace << static_cast(&data.front()) << Debug::nospace << ":" << Debug::nospace << static_cast(&data.back() + vertexFormatSize(attribute._format)) << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); + CORRADE_ASSERT(attribute._vertexCount == _vertexCount, + "Trade::MeshData: attribute" << i << "has" << attribute._vertexCount << "vertices but" << _vertexCount << "expected", ); + const void* const begin = static_cast(attribute._data); + const void* const end = static_cast(attribute._data) + (_vertexCount - 1)*attribute._stride + vertexFormatSize(attribute._format); + CORRADE_ASSERT(!_vertexCount || (begin >= _vertexData.begin() && end <= _vertexData.end()), + "Trade::MeshData: attribute" << i << "[" << Debug::nospace << begin << Debug::nospace << ":" << Debug::nospace << end << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); } #endif } @@ -233,13 +233,13 @@ VertexFormat MeshData::attributeFormat(UnsignedInt id) const { std::size_t MeshData::attributeOffset(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); - return static_cast(_attributes[id]._data.data()) - _vertexData.data(); + return static_cast(_attributes[id]._data) - _vertexData.data(); } UnsignedInt MeshData::attributeStride(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeStride(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); - return _attributes[id]._data.stride(); + return _attributes[id]._stride; } UnsignedInt MeshData::attributeCount(const MeshAttribute name) const { @@ -292,8 +292,9 @@ Containers::StridedArrayView2D MeshData::attribute(UnsignedInt id) c /* Build a 2D view using information about attribute type size, return only a prefix of the actual vertex count (which is zero in case vertex data is released) */ - return Containers::arrayCast<2, const char>(_attributes[id]._data, - vertexFormatSize(_attributes[id]._format)).prefix(_vertexCount); + return Containers::arrayCast<2, const char>( + attributeDataViewInternal(_attributes[id]), + vertexFormatSize(_attributes[id]._format)); } Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) { @@ -304,8 +305,9 @@ Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) /* Build a 2D view using information about attribute type size, return only a prefix of the actual vertex count (which is zero in case vertex data is released) */ - auto out = Containers::arrayCast<2, const char>(_attributes[id]._data, - vertexFormatSize(_attributes[id]._format)).prefix(_vertexCount); + auto out = Containers::arrayCast<2, const char>( + attributeDataViewInternal(_attributes[id]), + vertexFormatSize(_attributes[id]._format)); /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the @@ -360,45 +362,56 @@ Containers::Array MeshData::indicesAsArray() const { return output; } +Containers::StridedArrayView1D MeshData::attributeDataViewInternal(const MeshAttributeData& attribute) const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + {attribute._data, ~std::size_t{}}, + /* Not using attribute._vertexCount because that gets stale after + releaseVertexData() gets called, and then we would need to slice the + result inside attribute() and elsewhere */ + _vertexCount, attribute._stride}; +} + void MeshData::positions2DInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(MeshAttribute::Position, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; - + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination2f = Containers::arrayCast<2, Float>(destination); /* Copy 2D positions as-is, for 3D positions ignore Z */ if(attribute._format == VertexFormat::Vector2 || attribute._format == VertexFormat::Vector3) - Utility::copy(Containers::arrayCast(attribute._data), destination); + Utility::copy(Containers::arrayCast(attributeData), destination); else if(attribute._format == VertexFormat::Vector2h || attribute._format == VertexFormat::Vector3h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ub || attribute._format == VertexFormat::Vector3ub) - Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2b || attribute._format == VertexFormat::Vector3b) - Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2us || attribute._format == VertexFormat::Vector3us) - Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2s || attribute._format == VertexFormat::Vector3s) - Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ubNormalized || attribute._format == VertexFormat::Vector3ubNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2bNormalized || attribute._format == VertexFormat::Vector3bNormalized) - Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2usNormalized || attribute._format == VertexFormat::Vector3usNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2sNormalized || attribute._format == VertexFormat::Vector3sNormalized) - Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -413,54 +426,54 @@ void MeshData::positions3DInto(const Containers::StridedArrayView1D des CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions3DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions3DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; - + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const Containers::StridedArrayView2D destination2f = Containers::arrayCast<2, Float>(Containers::arrayCast(destination)); const Containers::StridedArrayView2D destination3f = Containers::arrayCast<2, Float>(destination); /* For 2D positions copy the XY part to the first two components */ if(attribute._format == VertexFormat::Vector2) - Utility::copy(Containers::arrayCast(attribute._data), + Utility::copy(Containers::arrayCast(attributeData), Containers::arrayCast(destination)); else if(attribute._format == VertexFormat::Vector2h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ub) - Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2b) - Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2us) - Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2s) - Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ubNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2bNormalized) - Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2usNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2sNormalized) - Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); /* Copy 3D positions as-is */ else if(attribute._format == VertexFormat::Vector3) - Utility::copy(Containers::arrayCast(attribute._data), destination); + Utility::copy(Containers::arrayCast(attributeData), destination); else if(attribute._format == VertexFormat::Vector3h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3ub) - Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 3), destination3f); + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3b) - Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 3), destination3f); + Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3us) - Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3s) - Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 3), destination3f); + Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3ubNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3bNormalized) - Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3usNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3sNormalized) - Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ /* For 2D positions finally fill the Z with a single value */ @@ -492,17 +505,17 @@ void MeshData::normalsInto(const Containers::StridedArrayView1D destina CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal) << "normal attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::normalsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; - + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination3f = Containers::arrayCast<2, Float>(destination); if(attribute._format == VertexFormat::Vector3) - Utility::copy(Containers::arrayCast(attribute._data), destination); + Utility::copy(Containers::arrayCast(attributeData), destination); else if(attribute._format == VertexFormat::Vector3h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3bNormalized) - Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3sNormalized) - Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -517,29 +530,29 @@ void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination2f = Containers::arrayCast<2, Float>(destination); if(attribute._format == VertexFormat::Vector2) - Utility::copy(Containers::arrayCast(attribute._data), destination); + Utility::copy(Containers::arrayCast(attributeData), destination); else if(attribute._format == VertexFormat::Vector2h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ub) - Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2b) - Math::castInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2us) - Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2s) - Math::castInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ubNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2bNormalized) - Math::unpackInto(Containers::arrayCast<2, const Byte>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2usNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2sNormalized) - Math::unpackInto(Containers::arrayCast<2, const Short>(attribute._data, 2), destination2f); + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } @@ -554,32 +567,32 @@ void MeshData::colorsInto(const Containers::StridedArrayView1D destinati CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::colorsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Color) << "color attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::colorsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; - + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const Containers::StridedArrayView2D destination3f = Containers::arrayCast<2, Float>(Containers::arrayCast(destination)); const Containers::StridedArrayView2D destination4f = Containers::arrayCast<2, Float>(destination); /* For three-component colors copy the RGB part to the first three components */ if(attribute._format == VertexFormat::Vector3) - Utility::copy(Containers::arrayCast(attribute._data), + Utility::copy(Containers::arrayCast(attributeData), Containers::arrayCast(destination)); else if(attribute._format == VertexFormat::Vector3h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3ubNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3usNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 3), destination3f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); /* Copy four-component colors as-is */ else if(attribute._format == VertexFormat::Vector4) - Utility::copy(Containers::arrayCast(attribute._data), + Utility::copy(Containers::arrayCast(attributeData), Containers::arrayCast(destination)); else if(attribute._format == VertexFormat::Vector4h) - Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 4), destination4f); + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4f); else if(attribute._format == VertexFormat::Vector4ubNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attribute._data, 4), destination4f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 4), destination4f); else if(attribute._format == VertexFormat::Vector4usNormalized) - Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attribute._data, 4), destination4f); + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ /* For three-component colors finally fill the alpha with a single value */ diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 825deed275..95e79ddc02 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -244,7 +244,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * initialization of the attribute array for @ref MeshData, expected to * be replaced with concrete values later. */ - constexpr explicit MeshAttributeData() noexcept: _name{}, _format{}, _data{} {} + constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{} {} /** * @brief Type-erased constructor @@ -313,7 +313,10 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * multiple different attributes onto each other. Not meant to be * passed to @ref MeshData. */ - constexpr explicit MeshAttributeData(Int padding): _name{}, _format{}, _data{nullptr, 0, padding} {} + constexpr explicit MeshAttributeData(Int padding): _data{nullptr}, _vertexCount{0}, _format{}, _stride{ + (CORRADE_CONSTEXPR_ASSERT(padding >= -32768 && padding <= 32767, + "Trade::MeshAttributeData: at most 32k padding supported, got" << padding), Short(padding)) + }, _name{} {} /** @brief Attribute name */ constexpr MeshAttribute name() const { return _name; } @@ -322,16 +325,29 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { constexpr VertexFormat format() const { return _format; } /** @brief Type-erased attribute data */ - constexpr Containers::StridedArrayView1D data() const { return _data; } + constexpr Containers::StridedArrayView1D data() const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + {_data, ~std::size_t{}}, + _vertexCount, _stride}; + } private: constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept; friend MeshData; - MeshAttribute _name; - /* Here's some room for flags */ + const void* _data; + /* Vertex count in MeshData is currently 32-bit, so this doesn't need + to be 64-bit either */ + UnsignedInt _vertexCount; VertexFormat _format; - Containers::StridedArrayView1D _data; + /* According to https://opengl.gpuinfo.org/displaycapability.php?name=GL_MAX_VERTEX_ATTRIB_STRIDE, + current largest reported stride is 4k so 32k should be enough */ + Short _stride; + MeshAttribute _name; + /* 4 bytes free for more stuff on 64b (20, aligned to 24); nothing on + 32b */ }; /** @relatesalso MeshAttributeData @@ -1183,6 +1199,12 @@ class MAGNUM_TRADE_EXPORT MeshData { /* Internal helper that doesn't assert, unlike attributeId() */ UnsignedInt attributeFor(MeshAttribute name, UnsignedInt id) const; + /* Like attribute(), but returning just a 1D view */ + Containers::StridedArrayView1D attributeDataViewInternal(const MeshAttributeData& attribute) const; + + /* GPUs don't currently support more than 32-bit index types / vertex + counts so this should be enough. Sanity check: + https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkIndexType.html */ UnsignedInt _vertexCount; MeshIndexType _indexType; MeshPrimitive _primitive; @@ -1298,9 +1320,14 @@ namespace Implementation { #endif constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept: - _name{name}, _format{format}, _data{(CORRADE_CONSTEXPR_ASSERT( - /* Double formats intentionally not supported for any builtin attributes - right now -- only for custom formats */ + _data{data.data()}, _vertexCount{UnsignedInt(data.size())}, _format{format}, + /** @todo support zero / negative stride? would be hard to transfer to GL */ + _stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(data.stride()) & 0xffff8000), + "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got" << data.stride()), + Short(data.stride())) + }, _name{(CORRADE_CONSTEXPR_ASSERT( + /* Double types intentionally not supported for any builtin attributes + right now -- only for custom types */ (name == MeshAttribute::Position && (format == VertexFormat::Vector2 || format == VertexFormat::Vector2h || @@ -1348,8 +1375,8 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V format == VertexFormat::Vector2s || format == VertexFormat::Vector2sNormalized)) || isMeshAttributeCustom(name) /* can be any format */, - "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), data)} - {} + "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name) + } {} template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), data, nullptr} {} diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index cde87476e9..b865b6b4e5 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -57,10 +57,10 @@ struct MeshDataTest: TestSuite::Tester { void constructAttribute2DWrongSize(); void constructAttribute2DNonContiguous(); void constructAttributeTypeErased(); - void constructAttributeTypeErasedWrongStride(); void constructAttributeNullptr(); void constructAttributePadding(); void constructAttributeNonOwningArray(); + void constructAttributeWrongStride(); void construct(); void constructZeroIndices(); @@ -173,10 +173,10 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttribute2DWrongSize, &MeshDataTest::constructAttribute2DNonContiguous, &MeshDataTest::constructAttributeTypeErased, - &MeshDataTest::constructAttributeTypeErasedWrongStride, &MeshDataTest::constructAttributeNullptr, &MeshDataTest::constructAttributePadding, &MeshDataTest::constructAttributeNonOwningArray, + &MeshDataTest::constructAttributeWrongStride, &MeshDataTest::construct, &MeshDataTest::constructZeroIndices, @@ -521,15 +521,6 @@ void MeshDataTest::constructAttributeTypeErased() { CORRADE_VERIFY(positions.data().data() == positionData); } -void MeshDataTest::constructAttributeTypeErasedWrongStride() { - char positionData[3*sizeof(Vector3)]{}; - - std::ostringstream out; - Error redirectError{&out}; - MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(positionData)}; - CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: view stride 1 is not large enough to contain VertexFormat::Vector3\n"); -} - void MeshDataTest::constructAttributeNullptr() { MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; CORRADE_COMPARE(positions.name(), MeshAttribute::Position); @@ -553,6 +544,23 @@ void MeshDataTest::constructAttributeNonOwningArray() { CORRADE_COMPARE(static_cast(array.data()), data); } +void MeshDataTest::constructAttributeWrongStride() { + char positionData[3*sizeof(Vector3)]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(positionData)}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::StridedArrayView1D{positionData, 0, -16}}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::StridedArrayView1D{positionData, 0, 65000}}; + MeshAttributeData{65000}; + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData: expected stride to be positive and enough to fit VertexFormat::Vector3, got 1\n" + "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got -16\n" + "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got 65000\n" + "Trade::MeshAttributeData: at most 32k padding supported, got 65000\n" + ); +} + void MeshDataTest::construct() { struct Vertex { Vector3 position; From 47695f097873c17af3b02ec3026ffa9c2129d18a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 20 Feb 2020 11:38:31 +0100 Subject: [PATCH 075/107] Trade: support offset-only MeshAttributeData. Originally this was done in order to make handling of deserialized data much simpler (as for those attributes also need to only contain an offset into some unknown data array), but seems this could be very useful elsewhere as well -- for example when the layout is known beforehand but the actual data not yet -- such as in the Line and Gradient primitives (going to switch them to this in the next commit). What still unfortunately has to be known in advance is the actual vertex count (as supplying it directly to MeshData would mean adding 6 new constructor overloads, and there's enough of those already). Might revisit later. --- src/Magnum/MeshTools/Duplicate.cpp | 6 + src/Magnum/MeshTools/Duplicate.h | 3 +- src/Magnum/MeshTools/Interleave.cpp | 6 + src/Magnum/MeshTools/Interleave.h | 13 +- src/Magnum/MeshTools/Test/DuplicateTest.cpp | 16 ++ src/Magnum/MeshTools/Test/InterleaveTest.cpp | 14 ++ src/Magnum/Trade/MeshData.cpp | 29 ++- src/Magnum/Trade/MeshData.h | 240 +++++++++++++------ src/Magnum/Trade/Test/MeshDataTest.cpp | 91 ++++++- 9 files changed, 322 insertions(+), 96 deletions(-) diff --git a/src/Magnum/MeshTools/Duplicate.cpp b/src/Magnum/MeshTools/Duplicate.cpp index 4d6b2cb455..3d6b49ed6a 100644 --- a/src/Magnum/MeshTools/Duplicate.cpp +++ b/src/Magnum/MeshTools/Duplicate.cpp @@ -93,6 +93,12 @@ Trade::MeshData duplicate(const Trade::MeshData& data, const Containers::ArrayVi /* Padding, ignore */ if(extra[i].format() == VertexFormat{}) continue; + /* Asserting here even though data() has another assert since that one + would be too confusing in this context */ + CORRADE_ASSERT(!extra[i].isOffsetOnly(), + "MeshTools::duplicate(): extra attribute" << i << "is offset-only, which is not supported", + (Trade::MeshData{MeshPrimitive::Triangles, 0})); + /* Copy the attribute in, if it is non-empty, otherwise keep the memory uninitialized */ if(extra[i].data()) { diff --git a/src/Magnum/MeshTools/Duplicate.h b/src/Magnum/MeshTools/Duplicate.h index a1bf32b8a8..e5458dfabd 100644 --- a/src/Magnum/MeshTools/Duplicate.h +++ b/src/Magnum/MeshTools/Duplicate.h @@ -137,7 +137,8 @@ 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. +behavior description. Note that offset-only @ref Trade::MeshAttributeData +instances are not supported in the @p extra array. 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 diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp index 525c5cabeb..8f89b60952 100644 --- a/src/Magnum/MeshTools/Interleave.cpp +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -185,6 +185,12 @@ Trade::MeshData interleave(Trade::MeshData&& data, const Containers::ArrayView indexData{4}; auto indexView = Containers::arrayCast(indexData); diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 7e597334d1..a28ff3a869 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -99,10 +99,17 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde "Trade::MeshData: attribute" << i << "doesn't specify anything", ); CORRADE_ASSERT(attribute._vertexCount == _vertexCount, "Trade::MeshData: attribute" << i << "has" << attribute._vertexCount << "vertices but" << _vertexCount << "expected", ); - const void* const begin = static_cast(attribute._data); - const void* const end = static_cast(attribute._data) + (_vertexCount - 1)*attribute._stride + vertexFormatSize(attribute._format); - CORRADE_ASSERT(!_vertexCount || (begin >= _vertexData.begin() && end <= _vertexData.end()), - "Trade::MeshData: attribute" << i << "[" << Debug::nospace << begin << Debug::nospace << ":" << Debug::nospace << end << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); + const UnsignedInt typeSize = vertexFormatSize(attribute._format); + if(attribute._isOffsetOnly) { + const std::size_t size = attribute._data.offset + (_vertexCount - 1)*attribute._stride + typeSize; + CORRADE_ASSERT(!_vertexCount || size <= _vertexData.size(), + "Trade::MeshData: offset attribute" << i << "spans" << size << "bytes but passed vertexData array has only" << _vertexData.size(), ); + } else { + const void* const begin = static_cast(attribute._data.pointer); + const void* const end = static_cast(attribute._data.pointer) + (_vertexCount - 1)*attribute._stride + typeSize; + CORRADE_ASSERT(!_vertexCount || (begin >= _vertexData.begin() && end <= _vertexData.end()), + "Trade::MeshData: attribute" << i << "[" << Debug::nospace << begin << Debug::nospace << ":" << Debug::nospace << end << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); + } } #endif } @@ -218,6 +225,14 @@ Containers::StridedArrayView2D MeshData::mutableIndices() { out.size(), out.stride()}; } +MeshAttributeData MeshData::attributeData(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeData(): index" << id << "out of range for" << _attributes.size() << "attributes", MeshAttributeData{}); + const MeshAttributeData& attribute = _attributes[id]; + return attribute._isOffsetOnly ? MeshAttributeData{attribute._name, + attribute._format, attributeDataViewInternal(attribute)} : attribute; +} + MeshAttribute MeshData::attributeName(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeName(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); @@ -233,7 +248,8 @@ VertexFormat MeshData::attributeFormat(UnsignedInt id) const { std::size_t MeshData::attributeOffset(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); - return static_cast(_attributes[id]._data) - _vertexData.data(); + return _attributes[id]._isOffsetOnly ? _attributes[id]._data.offset : + static_cast(_attributes[id]._data.pointer) - _vertexData.data(); } UnsignedInt MeshData::attributeStride(UnsignedInt id) const { @@ -366,7 +382,8 @@ Containers::StridedArrayView1D MeshData::attributeDataViewInternal(c return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ - {attribute._data, ~std::size_t{}}, + {attribute._isOffsetOnly ? _vertexData.data() + attribute._data.offset : + attribute._data.pointer, ~std::size_t{}}, /* Not using attribute._vertexCount because that gets stale after releaseVertexData() gets called, and then we would need to slice the result inside attribute() and elsewhere */ diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 95e79ddc02..fe9bdf6427 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -244,7 +244,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * initialization of the attribute array for @ref MeshData, expected to * be replaced with concrete values later. */ - constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{} {} + constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{}, _isOffsetOnly{false} {} /** * @brief Type-erased constructor @@ -305,6 +305,24 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /** @overload */ template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {} + /** + * @brief Construct an offset-only attribute + * @param name Attribute name + * @param format Attribute format + * @param offset Attribute data offset + * @param vertexCount Attribute vertex count + * @param stride Attribute stride + * + * Instances created this way refer to an offset in unspecified + * external vertex data instead of containing the data view directly. + * Useful when the location of the vertex data array is not known at + * attribute construction time. Note that instances created this way + * can't be used in most @ref MeshTools algorithms. + * @see @ref isOffsetOnly(), + * @ref data(Containers::ArrayView) const + */ + explicit constexpr MeshAttributeData(MeshAttribute name, VertexFormat format, std::size_t offset, UnsignedInt vertexCount, std::ptrdiff_t stride) noexcept; + /** * @brief Construct a pad value * @@ -316,7 +334,17 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { constexpr explicit MeshAttributeData(Int padding): _data{nullptr}, _vertexCount{0}, _format{}, _stride{ (CORRADE_CONSTEXPR_ASSERT(padding >= -32768 && padding <= 32767, "Trade::MeshAttributeData: at most 32k padding supported, got" << padding), Short(padding)) - }, _name{} {} + }, _name{}, _isOffsetOnly{false} {} + + /** + * @brief If the attribute is offset-only + * + * Returns @cpp true @ce if the attribute doesn't contain the data view + * directly, but instead refers to unspecified external vertex data. + * @see @ref data(Containers::ArrayView) const, + * @ref MeshAttributeData(MeshAttribute, VertexFormat, std::size_t, UnsignedInt, std::ptrdiff_t) + */ + constexpr bool isOffsetOnly() const { return _isOffsetOnly; } /** @brief Attribute name */ constexpr MeshAttribute name() const { return _name; } @@ -324,20 +352,48 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /** @brief Attribute format */ constexpr VertexFormat format() const { return _format; } - /** @brief Type-erased attribute data */ + /** + * @brief Type-erased attribute data + * + * Expects that the attribute is not offset-only, in that case use the + * @ref data(Containers::ArrayView) const overload + * instead. + * @see @ref isOffsetOnly() + */ constexpr Containers::StridedArrayView1D data() const { return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ - {_data, ~std::size_t{}}, - _vertexCount, _stride}; + {_data.pointer, ~std::size_t{}}, _vertexCount, + (CORRADE_CONSTEXPR_ASSERT(!_isOffsetOnly, "Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array"), _stride)}; + } + + /** + * @brief Type-erased attribute data for an offset-only attribute + * + * If the attribute is not offset-only, the @ref vertexData parameter + * is ignored. + * @see @ref isOffsetOnly(), @ref data() const + */ + Containers::StridedArrayView1D data(Containers::ArrayView vertexData) const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + vertexData, _isOffsetOnly ? reinterpret_cast(vertexData.data()) + _data.offset : _data.pointer, _vertexCount, _stride}; } private: constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept; friend MeshData; - const void* _data; + union Data { + /* FFS C++ why this doesn't JUST WORK goddamit?! */ + constexpr Data(const void* pointer = nullptr): pointer{pointer} {} + constexpr Data(std::size_t offset): offset{offset} {} + + const void* pointer; + std::size_t offset; + } _data; /* Vertex count in MeshData is currently 32-bit, so this doesn't need to be 64-bit either */ UnsignedInt _vertexCount; @@ -346,8 +402,9 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { current largest reported stride is 4k so 32k should be enough */ Short _stride; MeshAttribute _name; - /* 4 bytes free for more stuff on 64b (20, aligned to 24); nothing on - 32b */ + bool _isOffsetOnly; + /* 3 bytes free for more stuff on 64b (21, aligned to 24) and on 32b + (17 used, aligned to 20) */ }; /** @relatesalso MeshAttributeData @@ -679,14 +736,21 @@ class MAGNUM_TRADE_EXPORT MeshData { /** * @brief Raw attribute metadata * - * Useful mainly for passing particular attributes to @ref MeshTools - * algorithms, everything is otherwise exposed directly through various - * `attribute*()` getters. Returns @cpp nullptr @ce if the mesh has no - * attributes. - * @see @ref attributeCount(), @ref attributeName(), - * @ref attributeFormat(), @ref attribute() + * Returns the raw data that are used as a base for all `attribute*()` + * accessors, or @cpp nullptr @ce if the mesh has no attributes. In + * most cases you don't want to access those directly, but rather use + * the @ref attribute(), @ref attributeName(), @ref attributeFormat(), + * @ref attributeOffset(), @ref attributeStride() etc. accessors. + * Compared to those and to @ref attributeData(UnsignedInt) const, the + * @ref MeshAttributeData instances returned by this function may have + * different data pointers, and some of them might be offset-only --- + * use this function only if you *really* know what are you doing. + * @see @ref MeshAttributeData::isOffsetOnly() */ - Containers::ArrayView attributeData() const { return _attributes; } + Containers::ArrayView attributeData() const & { return _attributes; } + + /** @brief Taking a view to a r-value instance is not allowed */ + Containers::ArrayView attributeData() && = delete; /** * @brief Raw vertex data @@ -812,6 +876,25 @@ class MAGNUM_TRADE_EXPORT MeshData { */ UnsignedInt attributeCount() const { return _attributes.size(); } + /** + * @brief Raw attribute data + * + * Returns the raw data that are used as a base for all `attribute*()` + * accessors. In most cases you don't want to access those directly, + * but rather use the @ref attribute(), @ref attributeName(), + * @ref attributeFormat(), @ref attributeOffset(), + * @ref attributeStride() etc. accessors. + * + * Useful mainly for passing particular attributes unchanged directly + * to @ref MeshTools algorithms --- unlike with @ref attributeData() + * and @ref releaseAttributeData(), returned instances are guaranteed + * to always have an absolute data pointer (i.e., + * @ref MeshAttributeData::isOffsetOnly() always returning + * @cpp false @ce). The @p id is expected to be smaller than + * @ref attributeCount() const. + */ + MeshAttributeData attributeData(UnsignedInt id) const; + /** * @brief Attribute name * @@ -1159,10 +1242,13 @@ class MAGNUM_TRADE_EXPORT MeshData { * like if it has no attributes (but it can still have a non-zero * vertex count). Note that the returned array has a custom no-op * deleter when the data are not owned by the mesh, and while the - * returned array type is mutable, the actual memory might be not --- - * use this function only if you are sure about the origin of the - * array. - * @see @ref attributeData() + * returned array type is mutable, the actual memory might be not. + * Additionally, the returned @ref MeshAttributeData instances + * may have different data pointers and sizes than what's returned by + * the @ref attribute() and @ref attributeData(UnsignedInt) const + * accessors as some of them might be offset-only --- use this function + * only if you *really* know what are you doing. + * @see @ref attributeData(), @ref MeshAttributeData::isOffsetOnly() */ Containers::Array releaseAttributeData(); @@ -1316,6 +1402,59 @@ namespace Implementation { #undef _c #endif /* LCOV_EXCL_STOP */ + + constexpr bool isVertexFormatCompatibleWithAttribute(MeshAttribute name, VertexFormat format) { + /* Double types intentionally not supported for any builtin attributes + right now -- only for custom types */ + return + (name == MeshAttribute::Position && + (format == VertexFormat::Vector2 || + format == VertexFormat::Vector2h || + format == VertexFormat::Vector2ub || + format == VertexFormat::Vector2ubNormalized || + format == VertexFormat::Vector2b || + format == VertexFormat::Vector2bNormalized || + format == VertexFormat::Vector2us || + format == VertexFormat::Vector2usNormalized || + format == VertexFormat::Vector2s || + format == VertexFormat::Vector2sNormalized || + format == VertexFormat::Vector3 || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3ub || + format == VertexFormat::Vector3ubNormalized || + format == VertexFormat::Vector3b || + format == VertexFormat::Vector3bNormalized || + format == VertexFormat::Vector3us || + format == VertexFormat::Vector3usNormalized || + format == VertexFormat::Vector3s || + format == VertexFormat::Vector3sNormalized)) || + (name == MeshAttribute::Normal && + (format == VertexFormat::Vector3 || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3bNormalized || + format == VertexFormat::Vector3sNormalized)) || + (name == MeshAttribute::Color && + (format == VertexFormat::Vector3 || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3ubNormalized || + format == VertexFormat::Vector3usNormalized || + format == VertexFormat::Vector4 || + format == VertexFormat::Vector4h || + format == VertexFormat::Vector4ubNormalized || + format == VertexFormat::Vector4usNormalized)) || + (name == MeshAttribute::TextureCoordinates && + (format == VertexFormat::Vector2 || + format == VertexFormat::Vector2h || + format == VertexFormat::Vector2ub || + format == VertexFormat::Vector2ubNormalized || + format == VertexFormat::Vector2b || + format == VertexFormat::Vector2bNormalized || + format == VertexFormat::Vector2us || + format == VertexFormat::Vector2usNormalized || + format == VertexFormat::Vector2s || + format == VertexFormat::Vector2sNormalized)) || + isMeshAttributeCustom(name); /* can be any format */ + } } #endif @@ -1325,58 +1464,19 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V _stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(data.stride()) & 0xffff8000), "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got" << data.stride()), Short(data.stride())) - }, _name{(CORRADE_CONSTEXPR_ASSERT( - /* Double types intentionally not supported for any builtin attributes - right now -- only for custom types */ - (name == MeshAttribute::Position && - (format == VertexFormat::Vector2 || - format == VertexFormat::Vector2h || - format == VertexFormat::Vector2ub || - format == VertexFormat::Vector2ubNormalized || - format == VertexFormat::Vector2b || - format == VertexFormat::Vector2bNormalized || - format == VertexFormat::Vector2us || - format == VertexFormat::Vector2usNormalized || - format == VertexFormat::Vector2s || - format == VertexFormat::Vector2sNormalized || - format == VertexFormat::Vector3 || - format == VertexFormat::Vector3h || - format == VertexFormat::Vector3ub || - format == VertexFormat::Vector3ubNormalized || - format == VertexFormat::Vector3b || - format == VertexFormat::Vector3bNormalized || - format == VertexFormat::Vector3us || - format == VertexFormat::Vector3usNormalized || - format == VertexFormat::Vector3s || - format == VertexFormat::Vector3sNormalized)) || - (name == MeshAttribute::Normal && - (format == VertexFormat::Vector3 || - format == VertexFormat::Vector3h || - format == VertexFormat::Vector3bNormalized || - format == VertexFormat::Vector3sNormalized)) || - (name == MeshAttribute::Color && - (format == VertexFormat::Vector3 || - format == VertexFormat::Vector3h || - format == VertexFormat::Vector3ubNormalized || - format == VertexFormat::Vector3usNormalized || - format == VertexFormat::Vector4 || - format == VertexFormat::Vector4h || - format == VertexFormat::Vector4ubNormalized || - format == VertexFormat::Vector4usNormalized)) || - (name == MeshAttribute::TextureCoordinates && - (format == VertexFormat::Vector2 || - format == VertexFormat::Vector2h || - format == VertexFormat::Vector2ub || - format == VertexFormat::Vector2ubNormalized || - format == VertexFormat::Vector2b || - format == VertexFormat::Vector2bNormalized || - format == VertexFormat::Vector2us || - format == VertexFormat::Vector2usNormalized || - format == VertexFormat::Vector2s || - format == VertexFormat::Vector2sNormalized)) || - isMeshAttributeCustom(name) /* can be any format */, + }, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format), + "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name) + }, _isOffsetOnly{false} {} + +constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const std::size_t offset, const UnsignedInt vertexCount, const std::ptrdiff_t stride) noexcept: + _data{offset}, _vertexCount{vertexCount}, _format{format}, + /** @todo support zero / negative stride? would be hard to transfer to GL */ + _stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(stride) & 0xffff8000), + "Trade::MeshAttributeData: expected stride to be positive and at most 32k, got" << stride), + Short(stride)) + }, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format), "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name) - } {} + }, _isOffsetOnly{true} {} template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), data, nullptr} {} diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index b865b6b4e5..7da715e430 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -52,7 +52,6 @@ struct MeshDataTest: TestSuite::Tester { void constructAttribute(); void constructAttributeCustom(); - void constructAttributeWrongFormat(); void constructAttribute2D(); void constructAttribute2DWrongSize(); void constructAttribute2DNonContiguous(); @@ -60,7 +59,10 @@ struct MeshDataTest: TestSuite::Tester { void constructAttributeNullptr(); void constructAttributePadding(); void constructAttributeNonOwningArray(); + void constructAttributeOffsetOnly(); + void constructAttributeWrongFormat(); void constructAttributeWrongStride(); + void constructAttributeWrongDataAccess(); void construct(); void constructZeroIndices(); @@ -168,7 +170,6 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttribute, &MeshDataTest::constructAttributeCustom, - &MeshDataTest::constructAttributeWrongFormat, &MeshDataTest::constructAttribute2D, &MeshDataTest::constructAttribute2DWrongSize, &MeshDataTest::constructAttribute2DNonContiguous, @@ -176,7 +177,10 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttributeNullptr, &MeshDataTest::constructAttributePadding, &MeshDataTest::constructAttributeNonOwningArray, + &MeshDataTest::constructAttributeOffsetOnly, + &MeshDataTest::constructAttributeWrongFormat, &MeshDataTest::constructAttributeWrongStride, + &MeshDataTest::constructAttributeWrongDataAccess, &MeshDataTest::construct, &MeshDataTest::constructZeroIndices, @@ -450,14 +454,19 @@ constexpr Vector2 Positions[] { void MeshDataTest::constructAttribute() { const Vector2 positionData[3]; MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(positionData)}; + CORRADE_VERIFY(!positions.isOffsetOnly()); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_VERIFY(positions.data().data() == positionData); + /* This is allowed too for simplicity, it just ignores the parameter */ + CORRADE_VERIFY(positions.data(positionData).data() == positionData); constexpr MeshAttributeData cpositions{MeshAttribute::Position, Containers::arrayView(Positions)}; + constexpr bool isOffsetOnly = cpositions.isOffsetOnly(); constexpr MeshAttribute name = cpositions.name(); constexpr VertexFormat format = cpositions.format(); constexpr Containers::StridedArrayView1D data = cpositions.data(); + CORRADE_VERIFY(!isOffsetOnly); CORRADE_COMPARE(name, MeshAttribute::Position); CORRADE_COMPARE(format, VertexFormat::Vector2); CORRADE_COMPARE(data.data(), Positions); @@ -471,15 +480,6 @@ void MeshDataTest::constructAttributeCustom() { CORRADE_VERIFY(ids.data().data() == idData); } -void MeshDataTest::constructAttributeWrongFormat() { - Vector2 positionData[3]; - - std::ostringstream out; - Error redirectError{&out}; - MeshAttributeData{MeshAttribute::Color, Containers::arrayView(positionData)}; - CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n"); -} - void MeshDataTest::constructAttribute2D() { char positionData[4*sizeof(Vector2)]{}; auto positionView = Containers::StridedArrayView2D{positionData, @@ -544,12 +544,53 @@ void MeshDataTest::constructAttributeNonOwningArray() { CORRADE_COMPARE(static_cast(array.data()), data); } +void MeshDataTest::constructAttributeOffsetOnly() { + struct { + Vector2 position; + Vector2 textureCoordinates; + } vertexData[] { + {{}, {1.0f, 0.3f}}, + {{}, {0.5f, 0.7f}}, + }; + + MeshAttributeData a{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)}; + CORRADE_VERIFY(a.isOffsetOnly()); + CORRADE_COMPARE(a.name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(a.format(), VertexFormat::Vector2); + CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), + Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), + TestSuite::Compare::Container); + + constexpr MeshAttributeData ca{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)}; + CORRADE_VERIFY(ca.isOffsetOnly()); + CORRADE_COMPARE(ca.name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(ca.format(), VertexFormat::Vector2); + CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), + Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), + TestSuite::Compare::Container); +} + +void MeshDataTest::constructAttributeWrongFormat() { + Vector2 positionData[3]; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Color, Containers::arrayView(positionData)}; + MeshAttributeData{MeshAttribute::Color, VertexFormat::Vector2, 0, 3, sizeof(Vector2)}; + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n" + "Trade::MeshAttributeData: VertexFormat::Vector2 is not a valid format for Trade::MeshAttribute::Color\n"); +} + void MeshDataTest::constructAttributeWrongStride() { char positionData[3*sizeof(Vector3)]{}; std::ostringstream out; Error redirectError{&out}; MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(positionData)}; + /* We need this one to be constexpr, which means there can't be a warning + about stride not matching the size */ + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, 0, 3*sizeof(Vector3), 1}; MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::StridedArrayView1D{positionData, 0, -16}}; MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, Containers::StridedArrayView1D{positionData, 0, 65000}}; MeshAttributeData{65000}; @@ -561,6 +602,20 @@ void MeshDataTest::constructAttributeWrongStride() { ); } +void MeshDataTest::constructAttributeWrongDataAccess() { + Vector2 positionData[3]; + MeshAttributeData a{MeshAttribute::Position, Containers::arrayView(positionData)}; + MeshAttributeData b{MeshAttribute::Position, VertexFormat::Vector2, 0, 3, sizeof(Vector2)}; + CORRADE_VERIFY(!a.isOffsetOnly()); + CORRADE_VERIFY(b.isOffsetOnly()); + + std::ostringstream out; + Error redirectError{&out}; + b.data(); + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array\n"); +} + void MeshDataTest::construct() { struct Vertex { Vector3 position; @@ -597,8 +652,10 @@ void MeshDataTest::construct() { MeshIndexData indices{indexView}; MeshAttributeData positions{MeshAttribute::Position, Containers::StridedArrayView1D{vertexData, &vertexView[0].position, vertexView.size(), sizeof(Vertex)}}; + /* Using a relative offset */ MeshAttributeData normals{MeshAttribute::Normal, - Containers::StridedArrayView1D{vertexData, &vertexView[0].normal, vertexView.size(), sizeof(Vertex)}}; + VertexFormat::Vector3, offsetof(Vertex, normal), + UnsignedInt(vertexView.size()), sizeof(Vertex)}; MeshAttributeData textureCoordinates{MeshAttribute::TextureCoordinates, Containers::StridedArrayView1D{vertexData, &vertexView[0].textureCoordinate, vertexView.size(), sizeof(Vertex)}}; MeshAttributeData ids{meshAttributeCustom(13), @@ -692,6 +749,11 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.mutableAttribute(3)[1], (Vector2{0.250f, 0.375f})); CORRADE_COMPARE(data.mutableAttribute(4)[1], -374); + /* Raw attribute data access by ID */ + CORRADE_COMPARE(data.attributeData(3).name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(data.attributeData(3).format(), VertexFormat::Vector2); + CORRADE_COMPARE(Containers::arrayCast(data.attributeData(3).data())[1], (Vector2{0.250f, 0.375f})); + /* Attribute access by name */ CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Position)); CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Normal)); @@ -1167,14 +1229,17 @@ void MeshDataTest::constructAttributeNotContained() { Containers::ArrayView vertexData2{reinterpret_cast(0xdead), 3}; MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast(vertexData)}; MeshAttributeData positions2{MeshAttribute::Position, Containers::arrayView(vertexData2)}; + MeshAttributeData positions3{MeshAttribute::Position, VertexFormat::Vector2, 1, 3, 8}; std::ostringstream out; Error redirectError{&out}; MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}}; MeshData{MeshPrimitive::Triangles, nullptr, {positions}}; + MeshData{MeshPrimitive::Triangles, Containers::Array{24}, {positions3}}; CORRADE_COMPARE(out.str(), "Trade::MeshData: attribute 1 [0xdead:0xdec5] is not contained in passed vertexData array [0xbadda9:0xbaddc1]\n" - "Trade::MeshData: attribute 0 [0xbadda9:0xbaddc1] is not contained in passed vertexData array [0x0:0x0]\n"); + "Trade::MeshData: attribute 0 [0xbadda9:0xbaddc1] is not contained in passed vertexData array [0x0:0x0]\n" + "Trade::MeshData: offset attribute 0 spans 25 bytes but passed vertexData array has only 24\n"); } void MeshDataTest::constructInconsitentVertexCount() { From eb98f13b7230a2eaa79117d13fb842a7f6dc9fc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 17 Feb 2020 20:55:20 +0100 Subject: [PATCH 076/107] Primitives: use offset-only attribs for Line and Gradient. One less allocation for each. --- src/Magnum/Primitives/Gradient.cpp | 82 +++++++++++++++----------- src/Magnum/Primitives/Line.cpp | 22 ++++++- src/Magnum/Trade/MeshData.cpp | 32 +++++----- src/Magnum/Trade/Test/MeshDataTest.cpp | 4 ++ 4 files changed, 86 insertions(+), 54 deletions(-) diff --git a/src/Magnum/Primitives/Gradient.cpp b/src/Magnum/Primitives/Gradient.cpp index a4931453bb..4e2c961d90 100644 --- a/src/Magnum/Primitives/Gradient.cpp +++ b/src/Magnum/Primitives/Gradient.cpp @@ -32,14 +32,27 @@ namespace Magnum { namespace Primitives { -Trade::MeshData gradient2D(const Vector2& a, const Color4& colorA, const Vector2& b, const Color4& colorB) { - struct Vertex { - Vector2 position; - Color4 color; - }; +namespace { + +struct Vertex2D { + Vector2 position; + Color4 color; +}; + +constexpr Trade::MeshAttributeData Attributes2D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, offsetof(Vertex2D, position), + 4, sizeof(Vertex2D)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + VertexFormat::Vector4, offsetof(Vertex2D, color), + 4, sizeof(Vertex2D)}, +}; + +} - Containers::Array vertexData{sizeof(Vertex)*4}; - auto vertices = Containers::arrayCast(vertexData); +Trade::MeshData gradient2D(const Vector2& a, const Color4& colorA, const Vector2& b, const Color4& colorB) { + Containers::Array vertexData{sizeof(Vertex2D)*4}; + auto vertices = Containers::arrayCast(vertexData); vertices[0].position = { 1.0f, -1.0f}; vertices[1].position = { 1.0f, 1.0f}; vertices[2].position = {-1.0f, -1.0f}; @@ -56,14 +69,8 @@ Trade::MeshData gradient2D(const Vector2& a, const Color4& colorA, const Vector2 vertices[i].color = Math::lerp(colorA, colorB, t); } - Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, - Containers::stridedArrayView(vertices, &vertices[0].position, - vertices.size(), sizeof(Vertex))}; - Trade::MeshAttributeData colors{Trade::MeshAttribute::Color, - Containers::stridedArrayView(vertices, &vertices[0].color, - vertices.size(), sizeof(Vertex))}; - return Trade::MeshData{MeshPrimitive::TriangleStrip, - std::move(vertexData), {positions, colors}}; + return Trade::MeshData{MeshPrimitive::TriangleStrip, std::move(vertexData), + Trade::meshAttributeDataNonOwningArray(Attributes2D)}; } Trade::MeshData gradient2DHorizontal(const Color4& colorLeft, const Color4& colorRight) { @@ -74,15 +81,31 @@ Trade::MeshData gradient2DVertical(const Color4& colorBottom, const Color4& colo return Primitives::gradient2D({0.0f, -1.0f}, colorBottom, {0.0f, 1.0f}, colorTop); } +namespace { + +struct Vertex3D { + Vector3 position; + Vector3 normal; + Color4 color; +}; + +constexpr Trade::MeshAttributeData Attributes3D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector3, offsetof(Vertex3D, position), + 4, sizeof(Vertex3D)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3, offsetof(Vertex3D, normal), + 4, sizeof(Vertex3D)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + VertexFormat::Vector4, offsetof(Vertex3D, color), + 4, sizeof(Vertex3D)}, +}; + +} + Trade::MeshData gradient3D(const Vector3& a, const Color4& colorA, const Vector3& b, const Color4& colorB) { - struct Vertex { - Vector3 position; - Vector3 normal; - Color4 color; - }; - - Containers::Array vertexData{sizeof(Vertex)*4}; - auto vertices = Containers::arrayCast(vertexData); + Containers::Array vertexData{sizeof(Vertex3D)*4}; + auto vertices = Containers::arrayCast(vertexData); vertices[0].position = { 1.0f, -1.0f, 0}; vertices[1].position = { 1.0f, 1.0f, 0}; vertices[2].position = {-1.0f, -1.0f, 0}; @@ -103,17 +126,8 @@ Trade::MeshData gradient3D(const Vector3& a, const Color4& colorA, const Vector3 vertices[i].color = Math::lerp(colorA, colorB, t); } - Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, - Containers::stridedArrayView(vertices, &vertices[0].position, - vertices.size(), sizeof(Vertex))}; - Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, - Containers::stridedArrayView(vertices, &vertices[0].normal, - vertices.size(), sizeof(Vertex))}; - Trade::MeshAttributeData colors{Trade::MeshAttribute::Color, - Containers::stridedArrayView(vertices, &vertices[0].color, - vertices.size(), sizeof(Vertex))}; - return Trade::MeshData{MeshPrimitive::TriangleStrip, - std::move(vertexData), {positions, normals, colors}}; + return Trade::MeshData{MeshPrimitive::TriangleStrip, std::move(vertexData), + Trade::meshAttributeDataNonOwningArray(Attributes3D)}; } Trade::MeshData gradient3DHorizontal(const Color4& colorLeft, const Color4& colorRight) { diff --git a/src/Magnum/Primitives/Line.cpp b/src/Magnum/Primitives/Line.cpp index 37a05460a2..c688a9a848 100644 --- a/src/Magnum/Primitives/Line.cpp +++ b/src/Magnum/Primitives/Line.cpp @@ -31,6 +31,15 @@ namespace Magnum { namespace Primitives { +namespace { + +constexpr Trade::MeshAttributeData Attributes2D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, 0, 2, sizeof(Vector2)}, +}; + +} + Trade::MeshData line2D(const Vector2& a, const Vector2& b) { Containers::Array vertexData{sizeof(Vector2)*2}; auto positions = Containers::arrayCast(vertexData); @@ -38,7 +47,16 @@ Trade::MeshData line2D(const Vector2& a, const Vector2& b) { positions[1] = b; return Trade::MeshData{MeshPrimitive::Lines, std::move(vertexData), - {Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}}}; + Trade::meshAttributeDataNonOwningArray(Attributes2D)}; +} + +namespace { + +constexpr Trade::MeshAttributeData Attributes3D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector3, 0, 2, sizeof(Vector3)}, +}; + } Trade::MeshData line3D(const Vector3& a, const Vector3& b) { @@ -48,7 +66,7 @@ Trade::MeshData line3D(const Vector3& a, const Vector3& b) { positions[1] = b; return Trade::MeshData{MeshPrimitive::Lines, std::move(vertexData), - {Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}}}; + Trade::meshAttributeDataNonOwningArray(Attributes3D)}; } Trade::MeshData line2D() { diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index a28ff3a869..e8982ee70d 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -302,12 +302,22 @@ UnsignedInt MeshData::attributeStride(MeshAttribute name, UnsignedInt id) const return attributeStride(attributeId); } +Containers::StridedArrayView1D MeshData::attributeDataViewInternal(const MeshAttributeData& attribute) const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + {attribute._isOffsetOnly ? _vertexData.data() + attribute._data.offset : + attribute._data.pointer, ~std::size_t{}}, + /* Not using attribute._vertexCount because that gets stale after + releaseVertexData() gets called, and then we would need to slice the + result inside attribute() and elsewhere anyway */ + _vertexCount, attribute._stride}; +} + Containers::StridedArrayView2D MeshData::attribute(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); - /* Build a 2D view using information about attribute type size, return only - a prefix of the actual vertex count (which is zero in case vertex data - is released) */ + /* Build a 2D view using information about attribute type size */ return Containers::arrayCast<2, const char>( attributeDataViewInternal(_attributes[id]), vertexFormatSize(_attributes[id]._format)); @@ -318,9 +328,7 @@ Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); - /* Build a 2D view using information about attribute type size, return only - a prefix of the actual vertex count (which is zero in case vertex data - is released) */ + /* Build a 2D view using information about attribute type size */ auto out = Containers::arrayCast<2, const char>( attributeDataViewInternal(_attributes[id]), vertexFormatSize(_attributes[id]._format)); @@ -378,18 +386,6 @@ Containers::Array MeshData::indicesAsArray() const { return output; } -Containers::StridedArrayView1D MeshData::attributeDataViewInternal(const MeshAttributeData& attribute) const { - return Containers::StridedArrayView1D{ - /* We're *sure* the view is correct, so faking the view size */ - /** @todo better ideas for the StridedArrayView API? */ - {attribute._isOffsetOnly ? _vertexData.data() + attribute._data.offset : - attribute._data.pointer, ~std::size_t{}}, - /* Not using attribute._vertexCount because that gets stale after - releaseVertexData() gets called, and then we would need to slice the - result inside attribute() and elsewhere */ - _vertexCount, attribute._stride}; -} - void MeshData::positions2DInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(MeshAttribute::Position, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 7da715e430..972f2d254c 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -486,6 +486,7 @@ void MeshDataTest::constructAttribute2D() { {4, sizeof(Vector2)}}.every(2); MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, positionView}; + CORRADE_VERIFY(!positions.isOffsetOnly()); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_COMPARE(positions.data().data(), positionView.data()); @@ -516,6 +517,7 @@ void MeshDataTest::constructAttribute2DNonContiguous() { void MeshDataTest::constructAttributeTypeErased() { const Vector3 positionData[3]{}; MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(Containers::stridedArrayView(positionData))}; + CORRADE_VERIFY(!positions.isOffsetOnly()); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector3); CORRADE_VERIFY(positions.data().data() == positionData); @@ -523,6 +525,7 @@ void MeshDataTest::constructAttributeTypeErased() { void MeshDataTest::constructAttributeNullptr() { MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; + CORRADE_VERIFY(!positions.isOffsetOnly()); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_VERIFY(!positions.data().data()); @@ -530,6 +533,7 @@ void MeshDataTest::constructAttributeNullptr() { void MeshDataTest::constructAttributePadding() { MeshAttributeData padding{-35}; + CORRADE_VERIFY(!padding.isOffsetOnly()); CORRADE_COMPARE(padding.name(), MeshAttribute{}); CORRADE_COMPARE(padding.format(), VertexFormat{}); CORRADE_COMPARE(padding.data().size(), 0); From 31d3cdcdb69ef5d8f8d2aaf27e1a27203dc2c828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 19 Feb 2020 12:47:43 +0100 Subject: [PATCH 077/107] Enable embedding implementation-specific values in VertexFormat. Similarly to PixelFormat. Will be useful for Vulkan, unfortunately not so much for GL because there the format is specified by three orthogonal values and it's a terrible mess. --- src/Magnum/Test/CMakeLists.txt | 8 ++- src/Magnum/Test/VertexFormatTest.cpp | 102 +++++++++++++++++++++++++++ src/Magnum/VertexFormat.cpp | 19 +++++ src/Magnum/VertexFormat.h | 66 ++++++++++++++--- 4 files changed, 185 insertions(+), 10 deletions(-) diff --git a/src/Magnum/Test/CMakeLists.txt b/src/Magnum/Test/CMakeLists.txt index e46b1abf4c..e7cd1e216a 100644 --- a/src/Magnum/Test/CMakeLists.txt +++ b/src/Magnum/Test/CMakeLists.txt @@ -29,10 +29,8 @@ corrade_add_test(ImageTest ImageTest.cpp LIBRARIES MagnumTestLib) corrade_add_test(ImageViewTest ImageViewTest.cpp LIBRARIES MagnumTestLib) corrade_add_test(MeshTest MeshTest.cpp LIBRARIES MagnumTestLib) corrade_add_test(PixelFormatTest PixelFormatTest.cpp LIBRARIES MagnumTestLib) -target_compile_definitions(PixelFormatTest PRIVATE "CORRADE_GRACEFUL_ASSERT") corrade_add_test(PixelStorageTest PixelStorageTest.cpp LIBRARIES Magnum) corrade_add_test(ResourceManagerTest ResourceManagerTest.cpp LIBRARIES Magnum) -target_compile_definitions(ResourceManagerTest PRIVATE "CORRADE_GRACEFUL_ASSERT") corrade_add_test(SamplerTest SamplerTest.cpp LIBRARIES MagnumTestLib) corrade_add_test(TagsTest TagsTest.cpp LIBRARIES Magnum) corrade_add_test(VertexFormatTest VertexFormatTest.cpp LIBRARIES MagnumTestLib) @@ -49,3 +47,9 @@ set_target_properties( TagsTest VertexFormatTest PROPERTIES FOLDER "Magnum/Test") + +set_property(TARGET + PixelFormatTest + ResourceManagerTest + VertexFormatTest + APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") diff --git a/src/Magnum/Test/VertexFormatTest.cpp b/src/Magnum/Test/VertexFormatTest.cpp index 635225c92a..cefe655199 100644 --- a/src/Magnum/Test/VertexFormatTest.cpp +++ b/src/Magnum/Test/VertexFormatTest.cpp @@ -38,21 +38,32 @@ struct VertexFormatTest: TestSuite::Tester { void mapping(); + void isImplementationSpecific(); + void wrap(); + void wrapInvalid(); + void unwrap(); + void unwrapInvalid(); void size(); void sizeInvalid(); + void sizeImplementationSpecific(); void componentCount(); void componentCountInvalid(); + void componentCountImplementationSpecific(); void componentFormat(); void componentFormatInvalid(); + void componentFormatImplementationSpecific(); void isNormalized(); void isNormalizedInvalid(); + void isNormalizedImplementationSpecific(); void assemble(); void assembleRoundtrip(); void assembleCantNormalize(); void assembleInvalidComponentCount(); + void assembleImplementationSpecific(); void debug(); + void debugImplementationSpecific(); void configuration(); }; @@ -77,14 +88,23 @@ constexpr struct { VertexFormatTest::VertexFormatTest() { addTests({&VertexFormatTest::mapping, + &VertexFormatTest::isImplementationSpecific, + &VertexFormatTest::wrap, + &VertexFormatTest::wrapInvalid, + &VertexFormatTest::unwrap, + &VertexFormatTest::unwrapInvalid, &VertexFormatTest::size, &VertexFormatTest::sizeInvalid, + &VertexFormatTest::sizeImplementationSpecific, &VertexFormatTest::componentCount, &VertexFormatTest::componentCountInvalid, + &VertexFormatTest::componentCountImplementationSpecific, &VertexFormatTest::componentFormat, &VertexFormatTest::componentFormatInvalid, + &VertexFormatTest::componentFormatImplementationSpecific, &VertexFormatTest::isNormalized, &VertexFormatTest::isNormalizedInvalid, + &VertexFormatTest::isNormalizedImplementationSpecific, &VertexFormatTest::assemble}); @@ -93,8 +113,10 @@ VertexFormatTest::VertexFormatTest() { addTests({&VertexFormatTest::assembleCantNormalize, &VertexFormatTest::assembleInvalidComponentCount, + &VertexFormatTest::assembleImplementationSpecific, &VertexFormatTest::debug, + &VertexFormatTest::debugImplementationSpecific, &VertexFormatTest::configuration}); } @@ -137,6 +159,41 @@ void VertexFormatTest::mapping() { CORRADE_COMPARE(firstUnhandled, 0xffff); } +void VertexFormatTest::isImplementationSpecific() { + constexpr bool a = isVertexFormatImplementationSpecific(VertexFormat::Vector2sNormalized); + constexpr bool b = isVertexFormatImplementationSpecific(VertexFormat(0x8000dead)); + CORRADE_VERIFY(!a); + CORRADE_VERIFY(b); +} + +void VertexFormatTest::wrap() { + constexpr VertexFormat a = Magnum::vertexFormatWrap(0xdead); + CORRADE_COMPARE(UnsignedInt(a), 0x8000dead); +} + +void VertexFormatTest::wrapInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + Magnum::vertexFormatWrap(0xdeadbeef); + + CORRADE_COMPARE(out.str(), "vertexFormatWrap(): implementation-specific value 0xdeadbeef already wrapped or too large\n"); +} + +void VertexFormatTest::unwrap() { + constexpr UnsignedInt a = Magnum::vertexFormatUnwrap(VertexFormat(0x8000dead)); + CORRADE_COMPARE(a, 0xdead); +} + +void VertexFormatTest::unwrapInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + Magnum::vertexFormatUnwrap(VertexFormat::Float); + + CORRADE_COMPARE(out.str(), "vertexFormatUnwrap(): VertexFormat::Float isn't a wrapped implementation-specific value\n"); +} + void VertexFormatTest::size() { CORRADE_COMPARE(Magnum::vertexFormatSize(VertexFormat::Vector2), sizeof(Vector2)); CORRADE_COMPARE(Magnum::vertexFormatSize(VertexFormat::Vector3), sizeof(Vector3)); @@ -155,6 +212,13 @@ void VertexFormatTest::sizeInvalid() { "vertexFormatSize(): invalid format VertexFormat(0xdead)\n"); } +void VertexFormatTest::sizeImplementationSpecific() { + std::ostringstream out; + Error redirectError{&out}; + Magnum::vertexFormatSize(Magnum::vertexFormatWrap(0xdead)); + CORRADE_COMPARE(out.str(), "vertexFormatSize(): can't determine size of an implementation-specific format 0xdead\n"); +} + void VertexFormatTest::componentCount() { CORRADE_COMPARE(Magnum::vertexFormatComponentCount(VertexFormat::UnsignedByteNormalized), 1); CORRADE_COMPARE(Magnum::vertexFormatComponentCount(VertexFormat::Vector2us), 2); @@ -174,6 +238,14 @@ void VertexFormatTest::componentCountInvalid() { "vertexFormatComponentCount(): invalid format VertexFormat(0xdead)\n"); } +void VertexFormatTest::componentCountImplementationSpecific() { + std::ostringstream out; + Error redirectError{&out}; + Magnum::vertexFormatComponentCount(Magnum::vertexFormatWrap(0xdead)); + CORRADE_COMPARE(out.str(), + "vertexFormatComponentCount(): can't determine component count of an implementation-specific format 0xdead\n"); +} + void VertexFormatTest::componentFormat() { CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector4), VertexFormat::Float); CORRADE_COMPARE(Magnum::vertexFormatComponentFormat(VertexFormat::Vector3h), VertexFormat::Half); @@ -199,6 +271,14 @@ void VertexFormatTest::componentFormatInvalid() { "vertexFormatComponentType(): invalid format VertexFormat(0xdead)\n"); } +void VertexFormatTest::componentFormatImplementationSpecific() { + std::ostringstream out; + Error redirectError{&out}; + Magnum::vertexFormatComponentFormat(Magnum::vertexFormatWrap(0xdead)); + CORRADE_COMPARE(out.str(), + "vertexFormatComponentFormat(): can't determine component format of an implementation-specific format 0xdead\n"); +} + void VertexFormatTest::isNormalized() { CORRADE_VERIFY(isVertexFormatNormalized(VertexFormat::UnsignedByteNormalized)); CORRADE_VERIFY(!isVertexFormatNormalized(VertexFormat::Vector2us)); @@ -218,6 +298,14 @@ void VertexFormatTest::isNormalizedInvalid() { "isVertexFormatNormalized(): invalid format VertexFormat(0xdead)\n"); } +void VertexFormatTest::isNormalizedImplementationSpecific() { + std::ostringstream out; + Error redirectError{&out}; + isVertexFormatNormalized(Magnum::vertexFormatWrap(0xdead)); + CORRADE_COMPARE(out.str(), + "isVertexFormatNormalized(): can't determine normalization of an implementation-specific format 0xdead\n"); +} + void VertexFormatTest::assemble() { CORRADE_COMPARE(vertexFormat(VertexFormat::UnsignedShort, 3, true), VertexFormat::Vector3usNormalized); @@ -269,12 +357,26 @@ void VertexFormatTest::assembleInvalidComponentCount() { "vertexFormat(): invalid component count 5\n"); } +void VertexFormatTest::assembleImplementationSpecific() { + std::ostringstream out; + Error redirectError{&out}; + vertexFormat(Magnum::vertexFormatWrap(0xdead), 1, true); + CORRADE_COMPARE(out.str(), + "vertexFormat(): can't assemble a format out of an implementation-specific format 0xdead\n"); +} + void VertexFormatTest::debug() { std::ostringstream o; Debug(&o) << VertexFormat::Vector4 << VertexFormat(0xdead); CORRADE_COMPARE(o.str(), "VertexFormat::Vector4 VertexFormat(0xdead)\n"); } +void VertexFormatTest::debugImplementationSpecific() { + std::ostringstream o; + Debug(&o) << Magnum::vertexFormatWrap(0xdead); + CORRADE_COMPARE(o.str(), "VertexFormat::ImplementationSpecific(0xdead)\n"); +} + void VertexFormatTest::configuration() { Utility::Configuration c; diff --git a/src/Magnum/VertexFormat.cpp b/src/Magnum/VertexFormat.cpp index cfe0ac031a..25789d6590 100644 --- a/src/Magnum/VertexFormat.cpp +++ b/src/Magnum/VertexFormat.cpp @@ -33,6 +33,9 @@ namespace Magnum { UnsignedInt vertexFormatSize(const VertexFormat format) { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), + "vertexFormatSize(): can't determine size of an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); + switch(format) { case VertexFormat::UnsignedByte: case VertexFormat::UnsignedByteNormalized: @@ -102,6 +105,9 @@ UnsignedInt vertexFormatSize(const VertexFormat format) { } UnsignedInt vertexFormatComponentCount(const VertexFormat format) { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), + "vertexFormatComponentCount(): can't determine component count of an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); + switch(format) { case VertexFormat::Float: case VertexFormat::Half: @@ -168,6 +174,9 @@ UnsignedInt vertexFormatComponentCount(const VertexFormat format) { } VertexFormat vertexFormatComponentFormat(const VertexFormat format) { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), + "vertexFormatComponentFormat(): can't determine component format of an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); + switch(format) { case VertexFormat::Float: case VertexFormat::Vector2: @@ -244,6 +253,9 @@ VertexFormat vertexFormatComponentFormat(const VertexFormat format) { } bool isVertexFormatNormalized(const VertexFormat format) { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), + "isVertexFormatNormalized(): can't determine normalization of an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); + switch(format) { case VertexFormat::Float: case VertexFormat::Half: @@ -306,6 +318,9 @@ bool isVertexFormatNormalized(const VertexFormat format) { } VertexFormat vertexFormat(const VertexFormat format, UnsignedInt componentCount, bool normalized) { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), + "vertexFormat(): can't assemble a format out of an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)), {}); + VertexFormat componentFormat = vertexFormatComponentFormat(format); /* First turn the format into a normalized one, if requested */ @@ -360,6 +375,10 @@ constexpr const char* VertexFormatNames[] { Debug& operator<<(Debug& debug, const VertexFormat value) { debug << "VertexFormat" << Debug::nospace; + if(isVertexFormatImplementationSpecific(value)) { + return debug << "::ImplementationSpecific(" << Debug::nospace << reinterpret_cast(vertexFormatUnwrap(value)) << Debug::nospace << ")"; + } + if(UnsignedInt(value) - 1 < Containers::arraySize(VertexFormatNames)) { return debug << "::" << Debug::nospace << VertexFormatNames[UnsignedInt(value) - 1]; } diff --git a/src/Magnum/VertexFormat.h b/src/Magnum/VertexFormat.h index 6b66aa88e9..149c7a60d1 100644 --- a/src/Magnum/VertexFormat.h +++ b/src/Magnum/VertexFormat.h @@ -26,9 +26,10 @@ */ /** @file - * @brief Enum @ref Magnum::VertexFormat, @ref Magnum::vertexFormatSize(), @ref Magnum::vertexFormatComponentCount(), @ref Magnum::vertexFormatComponentFormat(), @ref Magnum::isVertexFormatNormalized() + * @brief Enum @ref Magnum::VertexFormat, function @ref Magnum::isVertexFormatImplementationSpecific(), @ref Magnum::vertexFormatWrap(), @ref Magnum::vertexFormatUnwrap(), @ref Magnum::vertexFormatSize(), @ref Magnum::vertexFormatComponentCount(), @ref Magnum::vertexFormatComponentFormat(), @ref Magnum::isVertexFormatNormalized() */ +#include #include #include "Magnum/Magnum.h" @@ -41,7 +42,12 @@ namespace Magnum { @m_since_latest Like @ref PixelFormat, but for mesh attributes --- including double-precision -types and matrices. +types and matrices. Can act also as a wrapper for implementation-specific mesh +attribute type values using @ref vertexFormatWrap() and +@ref vertexFormatUnwrap(). Distinction between generic and +implementation-specific types can be done using +@ref isVertexFormatImplementationSpecific(). + @see @ref Trade::MeshData, @ref Trade::MeshAttributeData, @ref Trade::MeshAttribute */ @@ -311,6 +317,56 @@ enum class VertexFormat: UnsignedInt { Vector4i }; +/** +@debugoperatorenum{VertexFormat} +@m_since_latest +*/ +MAGNUM_EXPORT Debug& operator<<(Debug& debug, VertexFormat value); + +/** +@brief Whether a @ref VertexFormat value wraps an implementation-specific identifier +@m_since_latest + +Returns @cpp true @ce if value of @p format has its highest bit set, +@cpp false @ce otherwise. Use @ref vertexFormatWrap() and @ref vertexFormatUnwrap() +to wrap/unwrap an implementation-specific indentifier to/from +@ref VertexFormat. +*/ +constexpr bool isVertexFormatImplementationSpecific(VertexFormat format) { + return UnsignedInt(format) & (1u << 31); +} + +/** +@brief Wrap an implementation-specific vertex format identifier in @ref VertexFormat +@m_since_latest + +Sets the highest bit on @p type to mark it as implementation-specific. Expects +that @p type fits into the remaining bits. Use @ref vertexFormatUnwrap() +for the inverse operation. +@see @ref isVertexFormatImplementationSpecific() +*/ +template constexpr VertexFormat vertexFormatWrap(T implementationSpecific) { + static_assert(sizeof(T) <= 4, "types larger than 32bits are not supported"); + return CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(implementationSpecific) & (1u << 31)), + "vertexFormatWrap(): implementation-specific value" << reinterpret_cast(implementationSpecific) << "already wrapped or too large"), + VertexFormat((1u << 31)|UnsignedInt(implementationSpecific)); +} + +/** +@brief Unwrap an implementation-specific vertex format identifier from @ref VertexFormat +@m_since_latest + +Unsets the highest bit from @p type to extract the implementation-specific +value. Expects that @p type has it set. Use @ref vertexFormatWrap() for +the inverse operation. +@see @ref isVertexFormatImplementationSpecific() +*/ +template constexpr T vertexFormatUnwrap(VertexFormat format) { + return CORRADE_CONSTEXPR_ASSERT(UnsignedInt(format) & (1u << 31), + "vertexFormatUnwrap():" << format << "isn't a wrapped implementation-specific value"), + T(UnsignedInt(format) & ~(1u << 31)); +} + /** @brief Size of given vertex format @m_since_latest @@ -370,12 +426,6 @@ normalization. Expects that @p componentCount is not larger than @cpp 4 @ce and */ MAGNUM_EXPORT VertexFormat vertexFormat(VertexFormat format, UnsignedInt componentCount, bool normalized); -/** -@debugoperatorenum{VertexFormat} -@m_since_latest -*/ -MAGNUM_EXPORT Debug& operator<<(Debug& debug, VertexFormat value); - } namespace Corrade { namespace Utility { From 7d44bccd9bda58c32154d9a9e992203515fafcbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 22 Feb 2020 21:20:50 +0100 Subject: [PATCH 078/107] Provide reliable mapping of VertexFormat to GL and Vulkan. GL was missing a check whether given format is available on a target (for example double types are not on ES), and for Vulkan we need something similar to pixel format mapping as well. --- doc/changelog.dox | 2 + src/Magnum/GL/Attribute.cpp | 37 +- src/Magnum/GL/Attribute.h | 34 +- src/Magnum/GL/Test/AttributeTest.cpp | 49 ++ src/Magnum/VertexFormat.h | 504 +++++++++++++++++- src/Magnum/Vk/CMakeLists.txt | 4 +- src/Magnum/Vk/Enums.cpp | 30 ++ src/Magnum/Vk/Enums.h | 51 +- .../Vk/Implementation/vertexFormatMapping.hpp | 80 +++ src/Magnum/Vk/Test/EnumsTest.cpp | 104 ++++ 10 files changed, 858 insertions(+), 37 deletions(-) create mode 100644 src/Magnum/Vk/Implementation/vertexFormatMapping.hpp diff --git a/doc/changelog.dox b/doc/changelog.dox index 9f009fcba3..12fd83728c 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -191,6 +191,8 @@ See also: @subsubsection changelog-latest-new-vk Vk library - Updated Vulkan headers for version 1.2 +- Conversion of @ref VertexFormat values to the @type_vk{Format} enum using + @ref Vk::vkFormat(Magnum::VertexFormat) @subsection changelog-latest-changes Changes and improvements diff --git a/src/Magnum/GL/Attribute.cpp b/src/Magnum/GL/Attribute.cpp index 9233df4fa7..66e028d091 100644 --- a/src/Magnum/GL/Attribute.cpp +++ b/src/Magnum/GL/Attribute.cpp @@ -469,7 +469,41 @@ Debug& operator<<(Debug& debug, const Attribute>::DataTyp } +bool hasVertexFormat(const VertexFormat format) { + switch(vertexFormatComponentFormat(format)) { + case VertexFormat::UnsignedByte: + case VertexFormat::Byte: + case VertexFormat::UnsignedShort: + case VertexFormat::Short: + case VertexFormat::UnsignedInt: + case VertexFormat::Int: + case VertexFormat::Float: + return true; + + case VertexFormat::Half: + #if !(defined(MAGNUM_TARGET_WEBGL) && defined(MAGNUM_TARGET_GLES2)) + return true; + #else + return false; + #endif + + case VertexFormat::Double: + #ifndef MAGNUM_TARGET_GLES + return true; + #else + return false; + #endif + + /* Nothing else expected to be returned from + vertexFormatComponentFormat() */ + default: CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } +} + DynamicAttribute::DynamicAttribute(const Kind kind, UnsignedInt location, const VertexFormat format, GLint maxComponents): _kind{kind}, _location{location}, _components{Components(vertexFormatComponentCount(format))} { + CORRADE_ASSERT(hasVertexFormat(format), + "GL::DynamicAttribute:" << format << "isn't available on this target", ); + /* Translate component type to a GL-specific value */ switch(vertexFormatComponentFormat(format)) { #define _c(format) \ @@ -492,7 +526,8 @@ DynamicAttribute::DynamicAttribute(const Kind kind, UnsignedInt location, const #undef _c /* Nothing else expected to be returned from - vertexFormatComponentFormat() */ + vertexFormatComponentFormat(), the unavailable formats were caught + by the hasVertexFormat() above already */ default: CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } diff --git a/src/Magnum/GL/Attribute.h b/src/Magnum/GL/Attribute.h index 4dd3114c0b..dc1a714306 100644 --- a/src/Magnum/GL/Attribute.h +++ b/src/Magnum/GL/Attribute.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Class @ref Magnum::GL::Attribute + * @brief Class @ref Magnum::GL::Attribute, @ref Magnum::GL::DynamicAttribute, function @ref Magnum::GL::hasVertexFormat() */ #include @@ -547,10 +547,11 @@ class MAGNUM_GL_EXPORT DynamicAttribute { * @brief Construct from a generic mesh attribute type * @m_since_latest * - * The @p type is expected to be compatible with @p kind --- i.e., - * normalized or floating-point for @ref Kind::GenericNormalized, - * non-normalized for @ref Kind::Integral / @ref Kind::Long and - * integral for @ref Kind::Integral. + * The @p type is expected to be available on given target and be + * compatible with @p kind --- i.e., normalized or floating-point for + * @ref Kind::GenericNormalized, non-normalized for @ref Kind::Integral + * / @ref Kind::Long and integral for @ref Kind::Integral. + * @see @ref hasVertexFormat() */ explicit DynamicAttribute(Kind kind, UnsignedInt location, VertexFormat format): DynamicAttribute{kind, location, format, 4} {} @@ -600,6 +601,29 @@ MAGNUM_GL_EXPORT Debug& operator<<(Debug& debug, DynamicAttribute::Components); /** @debugoperatorclassenum{DynamicAttribute,DynamicAttribute::DataType} */ MAGNUM_GL_EXPORT Debug& operator<<(Debug& debug, DynamicAttribute::DataType); +/** +@brief Check availability of a generic mesh attribute type +@m_since_latest + +Some OpenGL targets don't support all mesh attribute types (for example OpenGL +ES doesn't support double-precision types). Returns @cpp false @ce if current +target can't support such type, @cpp true @ce otherwise. The @p type value is +expected to be valid. + +Note that, unlike with pixel format mapping, there's no way to represent an +implementation-specific mesh attribute type using a single 32-bit value and +thus this function returns @cpp false @ce also for all formats for which +@ref isVertexFormatImplementationSpecific() is @cpp true @ce --- you need to do +such mapping by hand by creating a corresponding @ref DynamicAttribute. + +@note Support of some formats depends on presence of a particular OpenGL + extension. Such check is outside of the scope of this function and you are + expected to verify extension availability before using such type. + +@see @ref DynamicAttribute::DynamicAttribute(Kind, UnsignedInt, VertexFormat) +*/ +MAGNUM_GL_EXPORT bool hasVertexFormat(Magnum::VertexFormat format); + namespace Implementation { template constexpr DynamicAttribute::Kind kindFor(typename std::enable_if::ScalarType, Float>::value, typename GL::Attribute::DataOptions>::type options) { diff --git a/src/Magnum/GL/Test/AttributeTest.cpp b/src/Magnum/GL/Test/AttributeTest.cpp index a745c2a13c..8a88001047 100644 --- a/src/Magnum/GL/Test/AttributeTest.cpp +++ b/src/Magnum/GL/Test/AttributeTest.cpp @@ -70,6 +70,9 @@ struct AttributeTest: TestSuite::Tester { void attributeFromGenericFormatUnexpectedForLongKind(); #endif void attributeFromGenericFormatTooManyComponents(); + void attributeFromGenericFormatNotAvailable(); + + void hasVertexFormat(); void debugComponents1(); void debugComponents2(); @@ -132,6 +135,9 @@ AttributeTest::AttributeTest() { &AttributeTest::attributeFromGenericFormatUnexpectedForLongKind, #endif &AttributeTest::attributeFromGenericFormatTooManyComponents, + &AttributeTest::attributeFromGenericFormatNotAvailable, + + &AttributeTest::hasVertexFormat, &AttributeTest::debugComponents1, &AttributeTest::debugComponents2, @@ -640,6 +646,49 @@ void AttributeTest::attributeFromGenericFormatTooManyComponents() { "GL::DynamicAttribute: can't use VertexFormat::Vector3 for a 2-component attribute\n"); } +void AttributeTest::attributeFromGenericFormatNotAvailable() { + #ifndef MAGNUM_TARGET_GLES + CORRADE_SKIP("All attribute formats available on desktop GL."); + #else + std::ostringstream out; + Error redirectError{&out}; + DynamicAttribute{Attribute<7, Vector2>{}, VertexFormat::Vector3d}; + CORRADE_COMPARE(out.str(), + "GL::DynamicAttribute: VertexFormat::Vector3d isn't available on this target\n"); + #endif +} + +void AttributeTest::hasVertexFormat() { + CORRADE_VERIFY(GL::hasVertexFormat(Magnum::VertexFormat::Vector2i)); + #ifdef MAGNUM_TARGET_GLES + CORRADE_VERIFY(!GL::hasVertexFormat(Magnum::VertexFormat::Vector3d)); + #endif + + /* Ensure all generic formats are handled by going though all and executing + out functions on those. This goes through the first 16 bits, which + should be enough. Going through 32 bits takes 8 seconds, too much. */ + for(UnsignedInt i = 1; i <= 0xffff; ++i) { + const auto format = Magnum::VertexFormat(i); + /* Each case only verifies that hasVertexFormat() handles the format + and doesn't fall into unreachable code */ + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" + #endif + switch(format) { + #define _c(format) \ + case Magnum::VertexFormat::format: \ + GL::hasVertexFormat(Magnum::VertexFormat::format); \ + break; + #include "Magnum/Implementation/vertexFormatMapping.hpp" + #undef _c + } + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + } +} + void AttributeTest::debugComponents1() { typedef Attribute<3, Float> Attribute; diff --git a/src/Magnum/VertexFormat.h b/src/Magnum/VertexFormat.h index 149c7a60d1..60198f2a6c 100644 --- a/src/Magnum/VertexFormat.h +++ b/src/Magnum/VertexFormat.h @@ -48,81 +48,245 @@ attribute type values using @ref vertexFormatWrap() and implementation-specific types can be done using @ref isVertexFormatImplementationSpecific(). +In case of OpenGL, corresponds to a tuple of @ref GL::DynamicAttribute::Kind, +@ref GL::DynamicAttribute::Components and @ref GL::DynamicAttribute::DataType +and is convertible to them using +@ref GL::DynamicAttribute::DynamicAttribute(Kind, UnsignedInt, VertexFormat). +See documentation of each value for more information about the mapping. Note +that not every format is available on all targets, use +@ref GL::hasVertexFormat() to check for its presence. + +In case of Vulkan, corresponds to @type_vk_keyword{Format} and is convertible +to it using @ref Vk::vkFormat(Magnum::VertexFormat). See documentation of each +value for more information about the mapping. Note that not every format may be +available, use @ref Vk::hasVkFormat(Magnum::VertexFormat) to check for its +presence. + +For D3D, corresponds to @m_class{m-doc-external} [DXGI_FORMAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format); +for Metal, corresponds to @m_class{m-doc-external} [MTLVertexFormat](https://developer.apple.com/documentation/metal/mtlvertexformat?language=objc). +See documentation of each value for more information about the mapping. @see @ref Trade::MeshData, @ref Trade::MeshAttributeData, @ref Trade::MeshAttribute */ enum class VertexFormat: UnsignedInt { /* Zero reserved for an invalid type (but not being a named value) */ - /** @ref Float */ + /** + * @ref Float. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Float; + * @def_vk_keyword{FORMAT_R32_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatFloat](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatfloat?language=objc). + * @m_keywords{DXGI_FORMAT_R32_FLOAT MTLVertexFormatFloat} + */ Float = 1, - /** @ref Half */ + /** + * @ref Half. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Half; + * @def_vk_keyword{FORMAT_R16_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatHalf](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformathalf?language=objc). + * @m_keywords{DXGI_FORMAT_R16_FLOAT MTLVertexFormatHalf} + */ Half, - /** @ref Double */ + /** + * @ref Double. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Long + * @ref GL::DynamicAttribute::DataType::Double; + * @def_vk_keyword{FORMAT_R64_SFLOAT,Format}. No D3D or Metal equivalent. + */ Double, - /** @ref UnsignedByte */ + /** + * @ref UnsignedByte. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar?language=objc) + * @m_keywords{DXGI_FORMAT_R8_UINT MTLVertexFormatUChar} + */ UnsignedByte, /** * @ref UnsignedByte, with range @f$ [0, 255] @f$ interpreted as * @f$ [0.0, 1.0] @f$. + * + * Corresponds to single-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUCharNormalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatucharnormalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8_UNORM MTLVertexFormatUCharNormalized} */ UnsignedByteNormalized, - /** @ref Byte */ + /** + * @ref Byte. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar?language=objc). + * @m_keywords{DXGI_FORMAT_R8_SINT MTLVertexFormatChar} + */ Byte, /** * @ref Byte, with range @f$ [-127, 127] @f$ interpreted as * @f$ [-1.0, 1.0] @f$. + * + * Corresponds to single-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatCharNormalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatcharnormalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8_SNORM MTLVertexFormatCharNormalized} */ ByteNormalized, - /** @ref UnsignedShort */ + /** + * @ref UnsignedShort. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedShort; + * @def_vk_keyword{FORMAT_R16_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort?language=objc). + * @m_keywords{DXGI_FORMAT_R16_UINT MTLVertexFormatUShort} + */ UnsignedShort, /** * @ref UnsignedShort, with range @f$ [0, 65535] @f$ interpreted as * @f$ [0.0, 1.0] @f$. + * + * Corresponds to single-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R16_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShortNormalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushortnormalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16_UNORM MTLVertexFormatUShortNormalized} */ UnsignedShortNormalized, - /** @ref Short */ + /** + * @ref Short. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort?language=objc). + * @m_keywords{DXGI_FORMAT_R16_SINT MTLVertexFormatShort} + */ Short, /** * @ref Short, with range @f$ [-32767, 32767] @f$ interpreted as * @f$ [-1.0, 1.0] @f$. + * + * Corresponds to single-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShortNormalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshortnormalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16_SNORM MTLVertexFormatShortNormalized} */ ShortNormalized, - /** @ref UnsignedInt */ + /** + * @ref UnsignedInt. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedInt; + * @def_vk_keyword{FORMAT_R32_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUInt](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuint?language=objc). + * @m_keywords{DXGI_FORMAT_R32_UINT MTLVertexFormatUInt} + */ UnsignedInt, - /** @ref Int */ + /** + * @ref Int. + * + * Corresponds to single-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Int; + * @def_vk_keyword{FORMAT_R32_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatInt](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatint?language=objc). + * @m_keywords{DXGI_FORMAT_R32_SINT MTLVertexFormatInt} + */ Int, /** * @ref Vector2. Usually used for 2D positions and 2D texture coordinates. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Float; + * @def_vk_keyword{FORMAT_R32G32_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatFloat2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatfloat2?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32_FLOAT MTLVertexFormatFloat2} */ Vector2, /** * @ref Vector2h. Can be used instead of @ref VertexFormat::Vector2 for 2D * positions and 2D texture coordinates. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Half; + * @def_vk_keyword{FORMAT_R16G16_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatHalf2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformathalf2?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16_FLOAT MTLVertexFormatHalf2} */ Vector2h, - /** @ref Vector2d */ + /** + * @ref Vector2d. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Long + * @ref GL::DynamicAttribute::DataType::Double; + * @def_vk_keyword{FORMAT_R64G64_SFLOAT,Format}. No D3D or Metal + * equivalent. + */ Vector2d, /** * @ref Vector2ub. Can be used instead of @ref VertexFormat::Vector2 for * packed 2D positions and 2D texture coordinates, in which case the range * @f$ [0, 255] @f$ is interpreted as @f$ [0.0, 255.0] @f$. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8G8_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar2?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8_UINT MTLVertexFormatUChar2} */ Vector2ub, @@ -130,6 +294,14 @@ enum class VertexFormat: UnsignedInt { * @ref Vector2ub, with range @f$ [0, 255] @f$ interpreted as * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 * for packed 2D positions and 2D texture coordinates. + * + * Corresponds to two-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8G8_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar2Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar2normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8_UNORM MTLVertexFormatUChar2Normalized} */ Vector2ubNormalized, @@ -137,6 +309,14 @@ enum class VertexFormat: UnsignedInt { * @ref Vector2b. Can be used instead of @ref VertexFormat::Vector2 for * packed 2D positions and 2D texture coordinates, in which case the range * @f$ [-128, 127] @f$ is interpreted as @f$ [-128.0, 127.0] @f$. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8G8_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar2?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8_SINT MTLVertexFormatChar2} */ Vector2b, @@ -144,6 +324,14 @@ enum class VertexFormat: UnsignedInt { * @ref Vector2b, with range @f$ [-127, 127] @f$ interpreted as * @f$ [-1.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 * for packed 2D positions and 2D texture coordinates. + * + * Corresponds to two-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8G8_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar2Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar2normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8_SNORM MTLVertexFormatChar2Normalized} */ Vector2bNormalized, @@ -151,6 +339,14 @@ enum class VertexFormat: UnsignedInt { * @ref Vector2us. Can be used instead of @ref VertexFormat::Vector2 for * packed 2D positions and 2D texture coordinates, in which case the range * @f$ [0, 65535] @f$ is interpreted as @f$ [0.0, 65535.0] @f$. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedShort; + * @def_vk_keyword{FORMAT_R16G16_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort2?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16_UINT MTLVertexFormatUShort2} */ Vector2us, @@ -158,6 +354,14 @@ enum class VertexFormat: UnsignedInt { * @ref Vector2us, with range @f$ [0, 65535] @f$ interpreted as * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 * for packed 2D positions and 2D texture coordinates. + * + * Corresponds to two-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R16G16_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort2Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort2normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16_UNORM MTLVertexFormatUShort2Normalized} */ Vector2usNormalized, @@ -165,6 +369,14 @@ enum class VertexFormat: UnsignedInt { * @ref Vector2s. Can be used instead of @ref VertexFormat::Vector2 for * packed 2D positions and 2D texture coordinates, in which case the range * @f$ [-32768, 32767] @f$ is interpreted as @f$ [-32768.0, 32767.0] @f$. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16G16_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort2?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16_SINT MTLVertexFormatShort2} */ Vector2s, @@ -172,34 +384,92 @@ enum class VertexFormat: UnsignedInt { * @ref Vector2s, with range @f$ [-32767, 32767] @f$ interpreted as * @f$ [-1.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 * for packed 2D positions and 2D texture coordinates. + * + * Corresponds to two-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16G16_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort2Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort2normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16_SNORM MTLVertexFormatShort2Normalized} */ Vector2sNormalized, - /** @ref Vector2ui */ + /** + * @ref Vector2ui. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedInt; + * @def_vk_keyword{FORMAT_R32G32_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUInt2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuint2?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32_UINT MTLVertexFormatUInt2} + */ Vector2ui, - /** @ref Vector2i */ + /** + * @ref Vector2i. + * + * Corresponds to two-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Int; + * @def_vk_keyword{FORMAT_R32G32_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatInt2](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatint2?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32_SINT MTLVertexFormatInt2} + */ Vector2i, /** * @ref Vector3 or @ref Color3. Usually used for 3D positions, normals and * three-component colors. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Float; + * @def_vk_keyword{FORMAT_R32G32B32_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32B32_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatFloat3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatfloat3?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32B32_FLOAT MTLVertexFormatFloat3} */ Vector3, /** * @ref Vector3h. Can be used instead of @ref VertexFormat::Vector3 for * packed 3D positions and three-component colors. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Half; + * @def_vk_keyword{FORMAT_R16G16B16_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatHalf3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformathalf3?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16_FLOAT MTLVertexFormatHalf3} */ Vector3h, - /** @ref Vector3d */ + /** + * @ref Vector3d. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Long + * @ref GL::DynamicAttribute::DataType::Double; + * @def_vk_keyword{FORMAT_R64G64B64_SFLOAT,Format}. No D3D or Metal + * equivalent. + */ Vector3d, /** * @ref Vector3ub. Can be used instead of @ref VertexFormat::Vector3 for * packed 3D positions, in which case the range @f$ [0, 255] @f$ is * interpreted as @f$ [0.0, 255.0] @f$. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8G8B8_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar3?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8_UINT MTLVertexFormatUChar3} */ Vector3ub, @@ -207,6 +477,14 @@ enum class VertexFormat: UnsignedInt { * @ref Vector3ub, with range @f$ [0, 255] @f$ interpreted as * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector3 * for packed 3D positions and three-component colors. + * + * Corresponds to three-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8G8B8_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar3Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar3normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8_UNORM MTLVertexFormatUChar3Normalized} */ Vector3ubNormalized, @@ -214,6 +492,14 @@ enum class VertexFormat: UnsignedInt { * @ref Vector3b. Can be used instead of @ref VertexFormat::Vector3 for * packed 3D positions, in which case the range @f$ [-128, 127] @f$ is * interpreted as @f$ [-128.0, 127.0] @f$. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8G8B8_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar3?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8_SINT MTLVertexFormatChar3} */ Vector3b, @@ -221,6 +507,14 @@ enum class VertexFormat: UnsignedInt { * @ref Vector3b, with range @f$ [-127, 127] @f$ interpreted as * @f$ [-1.0, 1.0] @f$. Can be used instead of * @ref VertexFormat::Vector3 for packed 3D positions and normals. + * + * Corresponds to three-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8G8B8_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar3Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar3normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8_SNORM MTLVertexFormatChar3Normalized} */ Vector3bNormalized, @@ -228,6 +522,14 @@ enum class VertexFormat: UnsignedInt { * @ref Vector3us. Can be used instead of @ref VertexFormat::Vector3 for * packed 2D positions, in which case the range @f$ [0, 65535] @f$ is * interpreted as @f$ [0.0, 65535.0] @f$. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedShort; + * @def_vk_keyword{FORMAT_R16G16B16_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort3?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16_UINT MTLVertexFormatUShort3} */ Vector3us, @@ -235,6 +537,14 @@ enum class VertexFormat: UnsignedInt { * @ref Vector3us, with range @f$ [0, 65535] @f$ interpreted as * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector2 * for packed 3D positions and three-component colors. + * + * Corresponds to three-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R16G16B16_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort3Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort3normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16_UNORM MTLVertexFormatUShort3Normalized} */ Vector3usNormalized, @@ -242,6 +552,14 @@ enum class VertexFormat: UnsignedInt { * @ref Vector3s. Can be used instead of @ref VertexFormat::Vector3 for * packed 3D positions, in which case the range @f$ [-32768, 32767] @f$ is * interpreted as @f$ [-32768.0, 32767.0] @f$. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16G16B16_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort3?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16_SINT MTLVertexFormatShort3} */ Vector3s, @@ -249,71 +567,213 @@ enum class VertexFormat: UnsignedInt { * @ref Vector3s, with range @f$ [-32767, 32767] @f$ interpreted as * @f$ [-1.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector3 * for packed 3D positions and normals. + * + * Corresponds to three-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16G16B16_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort3Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort3normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16_SNORM MTLVertexFormatShort3Normalized} */ Vector3sNormalized, - /** @ref Vector3ui */ + /** + * @ref Vector3ui. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedInt; + * @def_vk_keyword{FORMAT_R32G32B32_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32B32_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUInt3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuint3?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32B32_UINT MTLVertexFormatUInt3} + */ Vector3ui, - /** @ref Vector3i */ + /** + * @ref Vector3i. + * + * Corresponds to three-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Int; + * @def_vk_keyword{FORMAT_R32G32B32_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32B32_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatInt3](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatint3?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32B32_SINT MTLVertexFormatInt3} + */ Vector3i, /** * @ref Vector4 or @ref Color4. Usually used for four-component colors. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Float; + * @def_vk_keyword{FORMAT_R32G32B32A32_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32B32A32_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatFloat4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatfloat4?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32B32A32_FLOAT MTLVertexFormatFloat4} */ Vector4, /** * @ref Vector4h. Can be used instead of @ref VertexFormat::Vector4 for * four-component colors. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * @ref GL::DynamicAttribute::DataType::Half; + * @def_vk_keyword{FORMAT_R16G16B16A16_SFLOAT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16A16_FLOAT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatHalf4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformathalf4?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16A16_FLOAT MTLVertexFormatHalf4} */ Vector4h, - /** @ref Vector4d */ + /** + * @ref Vector4d. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Long + * @ref GL::DynamicAttribute::DataType::Double; + * @def_vk_keyword{FORMAT_R64G64B64A64_SFLOAT,Format}. No D3D or Metal + * equivalent. + */ Vector4d, - /** @ref Vector4ub */ + /** + * @ref Vector4ub. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8G8B8A8_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8A8_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar4?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8A8_UINT MTLVertexFormatUChar4} + */ Vector4ub, /** * @ref Vector4ub, with range @f$ [0, 255] @f$ interpreted as * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector4 * for packed linear four-component colors. + * + * Corresponds to four-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R8G8B8A8_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8A8_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUChar4Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuchar4normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8A8_UNORM MTLVertexFormatUChar4Normalized} */ Vector4ubNormalized, - /** @ref Vector4b */ + /** + * @ref Vector4b. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8G8B8A8_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8A8_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar4?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8A8_SINT MTLVertexFormatChar4} + */ Vector4b, /** * @ref Vector4b, with range @f$ [-127, 127] @f$ interpreted as * @f$ [-1.0, 1.0] @f$. + * + * Corresponds to four-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Byte; + * @def_vk_keyword{FORMAT_R8G8B8A8_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R8G8B8A8_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatChar4Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatchar4normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R8G8B8A8_SNORM MTLVertexFormatChar4Normalized} */ Vector4bNormalized, - /** @ref Vector4us */ + /** + * @ref Vector4us. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedShort; + * @def_vk_keyword{FORMAT_R16G16B16A16_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16A16_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort4?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16A16_UINT MTLVertexFormatUShort4} + */ Vector4us, /** * @ref Vector4us, with range @f$ [0, 65535] @f$ interpreted as * @f$ [0.0, 1.0] @f$. Can be used instead of @ref VertexFormat::Vector4 * for packed linear four-component colors. + * + * Corresponds to four-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::UnsignedByte; + * @def_vk_keyword{FORMAT_R16G16B16A16_UNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16A16_UNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUShort4Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatushort4normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16A16_UNORM MTLVertexFormatUShort4Normalized} */ Vector4usNormalized, - /** @ref Vector4s */ + /** + * @ref Vector4s. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16G16B16A16_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16A16_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort4?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16A16_SINT MTLVertexFormatShort4} + */ Vector4s, /** * @ref Vector4s, with range @f$ [-32767, 32767] @f$ interpreted as * @f$ [-1.0, 1.0] @f$. + * + * Corresponds to four-component + * @ref GL::DynamicAttribute::Kind::GenericNormalized + * @ref GL::DynamicAttribute::DataType::Short; + * @def_vk_keyword{FORMAT_R16G16B16A16_SNORM,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R16G16B16A16_SNORM](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatShort4Normalized](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatshort4normalized?language=objc). + * @m_keywords{DXGI_FORMAT_R16G16B16A16_SNORM MTLVertexFormatShort4Normalized} */ Vector4sNormalized, - /** @ref Vector4ui */ + /** + * @ref Vector4ui. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::UnsignedInt; + * @def_vk_keyword{FORMAT_R32G32B32A16_UINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32B32A32_UINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatUInt4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatuint4?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32B32A32_UINT MTLVertexFormatUInt4} + */ Vector4ui, - /** @ref Vector4i */ + /** + * @ref Vector4i. + * + * Corresponds to four-component @ref GL::DynamicAttribute::Kind::Generic + * or @ref GL::DynamicAttribute::Kind::Integral + * @ref GL::DynamicAttribute::DataType::Int; + * @def_vk_keyword{FORMAT_R32G32B32A32_SINT,Format}; + * @m_class{m-doc-external} [DXGI_FORMAT_R32G32B32A32_SINT](https://docs.microsoft.com/en-us/windows/win32/api/dxgiformat/ne-dxgiformat-dxgi_format) + * or @m_class{m-doc-external} [MTLVertexFormatInt4](https://developer.apple.com/documentation/metal/mtlvertexformat/mtlvertexformatint4?language=objc). + * @m_keywords{DXGI_FORMAT_R32G32B32A32_SINT MTLVertexFormatInt4} + */ Vector4i }; diff --git a/src/Magnum/Vk/CMakeLists.txt b/src/Magnum/Vk/CMakeLists.txt index a2dcf4490d..dc21ac4ba0 100644 --- a/src/Magnum/Vk/CMakeLists.txt +++ b/src/Magnum/Vk/CMakeLists.txt @@ -42,7 +42,9 @@ set(MagnumVk_HEADERS visibility.h) set(MagnumVk_PRIVATE_HEADERS - Implementation/FormatMapping.hpp) + Implementation/compressedPixelFormatMapping.hpp + Implementation/meshAttributeTypeMapping.hpp + Implementation/pixelFormatMapping.hpp) # Objects shared between main and test library # add_library(MagnumVkObjects OBJECT diff --git a/src/Magnum/Vk/Enums.cpp b/src/Magnum/Vk/Enums.cpp index 6e8a69c47e..47e236dd33 100644 --- a/src/Magnum/Vk/Enums.cpp +++ b/src/Magnum/Vk/Enums.cpp @@ -30,6 +30,7 @@ #include "Magnum/Mesh.h" #include "Magnum/PixelFormat.h" #include "Magnum/Sampler.h" +#include "Magnum/VertexFormat.h" namespace Magnum { namespace Vk { @@ -54,6 +55,14 @@ constexpr VkIndexType IndexTypeMapping[]{ #ifndef DOXYGEN_GENERATING_OUTPUT /* It gets *really* confused */ static_assert(VK_FORMAT_UNDEFINED == 0, "VK_FORMAT_UNDEFINED is assumed to be 0"); +constexpr VkFormat VertexFormatMapping[] { + #define _c(input, format) VK_FORMAT_ ## format, + #define _s(input) {}, + #include "Magnum/Vk/Implementation/vertexFormatMapping.hpp" + #undef _s + #undef _c +}; + constexpr VkFormat PixelFormatMapping[] { #define _c(input, format) VK_FORMAT_ ## format, #define _s(input) {}, @@ -122,6 +131,15 @@ VkIndexType vkIndexType(const Magnum::MeshIndexType type) { return out; } +bool hasVkFormat(const Magnum::VertexFormat format) { + if(isVertexFormatImplementationSpecific(format)) + return true; + + CORRADE_ASSERT(UnsignedInt(format) - 1 < Containers::arraySize(VertexFormatMapping), + "Vk::hasVkFormat(): invalid format" << format, {}); + return UnsignedInt(VertexFormatMapping[UnsignedInt(format) - 1]); +} + bool hasVkFormat(const Magnum::PixelFormat format) { if(isPixelFormatImplementationSpecific(format)) return true; @@ -140,6 +158,18 @@ bool hasVkFormat(const Magnum::CompressedPixelFormat format) { return UnsignedInt(CompressedPixelFormatMapping[UnsignedInt(format) - 1]); } +VkFormat vkFormat(const Magnum::VertexFormat format) { + if(isVertexFormatImplementationSpecific(format)) + return vertexFormatUnwrap(format); + + CORRADE_ASSERT(UnsignedInt(format) - 1 < Containers::arraySize(VertexFormatMapping), + "Vk::vkFormat(): invalid format" << format, {}); + const VkFormat out = VertexFormatMapping[UnsignedInt(format) - 1]; + CORRADE_ASSERT(UnsignedInt(out), + "Vk::vkFormat(): unsupported format" << format, {}); + return out; +} + VkFormat vkFormat(const Magnum::PixelFormat format) { if(isPixelFormatImplementationSpecific(format)) return pixelFormatUnwrap(format); diff --git a/src/Magnum/Vk/Enums.h b/src/Magnum/Vk/Enums.h index 3c44b60fbe..39f4ed7f06 100644 --- a/src/Magnum/Vk/Enums.h +++ b/src/Magnum/Vk/Enums.h @@ -79,6 +79,24 @@ of given index type. */ MAGNUM_VK_EXPORT VkIndexType vkIndexType(Magnum::MeshIndexType type); +/** +@brief Check availability of a generic vertex format +@m_since_latest + +Some Vulkan targets don't support all generic vertex formats. Returns +@cpp false @ce if current target can't support such format, @cpp true @ce +otherwise. Moreover, returns @cpp true @ce also for all formats that are +@ref isVertexFormatImplementationSpecific(). The @p format value is expected +to be valid. + +@note Support of some formats depends on presence of a particular Vulkan + extension. Such check is outside of the scope of this function and you are + expected to verify extension availability before using such format. + +@see @ref vkFormat(Magnum::VertexFormat) +*/ +MAGNUM_VK_EXPORT bool hasVkFormat(Magnum::VertexFormat format); + /** @brief Check availability of a generic pixel format @@ -92,7 +110,7 @@ be valid. extension. Such check is outside of the scope of this function and you are expected to verify extension availability before using such format. -@see @ref vkFormat() +@see @ref vkFormat(Magnum::PixelFormat) */ MAGNUM_VK_EXPORT bool hasVkFormat(Magnum::PixelFormat format); @@ -109,22 +127,38 @@ expected to be valid. extension. Such check is outside of the scope of this function and you are expected to verify extension availability before using such format. -@see @ref vkFormat() +@see @ref vkFormat(Magnum::CompressedPixelFormat) */ MAGNUM_VK_EXPORT bool hasVkFormat(Magnum::CompressedPixelFormat format); +/** +@brief Convert a generic vertex format to Vulkan format +@m_since_latest + +In case @ref isVertexFormatImplementationSpecific() returns @cpp false @ce for +@p format, maps it to a corresponding Vulkan format. In case +@ref isVertexFormatImplementationSpecific() returns @cpp true @ce, assumes +@p format stores a Vulkan-specific format and returns @ref vertexFormatUnwrap() +cast to @type_vk{Format}. + +Not all generic vertex formats may be available on all targets and this +function expects that given format is available on the target. Use +@ref hasVkFormat(Magnum::VertexFormat) to query availability of given format. +*/ +MAGNUM_VK_EXPORT VkFormat vkFormat(Magnum::VertexFormat format); + /** @brief Convert a generic pixel format to Vulkan format In case @ref isPixelFormatImplementationSpecific() returns @cpp false @ce for @p format, maps it to a corresponding Vulkan format. In case @ref isPixelFormatImplementationSpecific() returns @cpp true @ce, assumes -@p format stores Vulkan-specific format and returns @ref pixelFormatUnwrap() +@p format stores a Vulkan-specific format and returns @ref pixelFormatUnwrap() cast to @type_vk{Format}. Not all generic pixel formats may be available on all targets and this function -expects that given format is available on the target. Use @ref hasVkFormat() to -query availability of given format. +expects that given format is available on the target. Use +@ref hasVkFormat(Magnum::PixelFormat) to query availability of given format. */ MAGNUM_VK_EXPORT VkFormat vkFormat(Magnum::PixelFormat format); @@ -134,12 +168,13 @@ MAGNUM_VK_EXPORT VkFormat vkFormat(Magnum::PixelFormat format); In case @ref isCompressedPixelFormatImplementationSpecific() returns @cpp false @ce for @p format, maps it to a corresponding Vulkan format. In case @ref isCompressedPixelFormatImplementationSpecific() returns @cpp true @ce, -assumes @p format stores Vulkan-specific format and returns +assumes @p format stores a Vulkan-specific format and returns @ref compressedPixelFormatUnwrap() cast to @type_vk{Format}. Not all generic pixel formats may be available on all targets and this function -expects that given format is available on the target. Use @ref hasVkFormat() to -query availability of given format. +expects that given format is available on the target. Use +@ref hasVkFormat(Magnum::CompressedPixelFormat) to query availability of given +format. */ MAGNUM_VK_EXPORT VkFormat vkFormat(Magnum::CompressedPixelFormat format); diff --git a/src/Magnum/Vk/Implementation/vertexFormatMapping.hpp b/src/Magnum/Vk/Implementation/vertexFormatMapping.hpp new file mode 100644 index 0000000000..2efe832bc6 --- /dev/null +++ b/src/Magnum/Vk/Implementation/vertexFormatMapping.hpp @@ -0,0 +1,80 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/* See Magnum/Vk/Enums.cpp and Magnum/Vk/Test/EnumsTest.cpp */ +#ifdef _c +_c(Float, R32_SFLOAT) +_c(Half, R16_SFLOAT) +_c(Double, R64_SFLOAT) +_c(UnsignedByte, R8_UINT) +_c(UnsignedByteNormalized, R8_UNORM) +_c(Byte, R8_SINT) +_c(ByteNormalized, R8_SNORM) +_c(UnsignedShort, R16_UINT) +_c(UnsignedShortNormalized, R16_UNORM) +_c(Short, R16_SINT) +_c(ShortNormalized, R16_SNORM) +_c(UnsignedInt, R32_UINT) +_c(Int, R32_SINT) +_c(Vector2, R32G32_SFLOAT) +_c(Vector2h, R16G16_SFLOAT) +_c(Vector2d, R64G64_SFLOAT) +_c(Vector2ub, R8G8_UINT) +_c(Vector2ubNormalized, R8G8_UNORM) +_c(Vector2b, R8G8_SINT) +_c(Vector2bNormalized, R8G8_SNORM) +_c(Vector2us, R16G16_UINT) +_c(Vector2usNormalized, R16G16_UNORM) +_c(Vector2s, R16G16_SINT) +_c(Vector2sNormalized, R16G16_SNORM) +_c(Vector2ui, R32G32_UINT) +_c(Vector2i, R32G32_SINT) +_c(Vector3, R32G32B32_SFLOAT) +_c(Vector3h, R16G16B16_SFLOAT) +_c(Vector3d, R64G64B64_SFLOAT) +_c(Vector3ub, R8G8B8_UINT) +_c(Vector3ubNormalized, R8G8B8_UNORM) +_c(Vector3b, R8G8B8_SINT) +_c(Vector3bNormalized, R8G8B8_SNORM) +_c(Vector3us, R16G16B16_UINT) +_c(Vector3usNormalized, R16G16B16_UNORM) +_c(Vector3s, R16G16B16_SINT) +_c(Vector3sNormalized, R16G16B16_SNORM) +_c(Vector3ui, R32G32B32_UINT) +_c(Vector3i, R32G32B32_SINT) +_c(Vector4, R32G32B32A32_SFLOAT) +_c(Vector4h, R16G16B16A16_SFLOAT) +_c(Vector4d, R64G64B64A64_SFLOAT) +_c(Vector4ub, R8G8B8A8_UINT) +_c(Vector4ubNormalized, R8G8B8A8_UNORM) +_c(Vector4b, R8G8B8A8_SINT) +_c(Vector4bNormalized, R8G8B8A8_SNORM) +_c(Vector4us, R16G16B16A16_UINT) +_c(Vector4usNormalized, R16G16B16A16_UNORM) +_c(Vector4s, R16G16B16A16_SINT) +_c(Vector4sNormalized, R16G16B16A16_SNORM) +_c(Vector4ui, R32G32B32A32_UINT) +_c(Vector4i, R32G32B32A32_SINT) +#endif diff --git a/src/Magnum/Vk/Test/EnumsTest.cpp b/src/Magnum/Vk/Test/EnumsTest.cpp index 05cf727929..baea77c7d0 100644 --- a/src/Magnum/Vk/Test/EnumsTest.cpp +++ b/src/Magnum/Vk/Test/EnumsTest.cpp @@ -30,6 +30,7 @@ #include "Magnum/Mesh.h" #include "Magnum/PixelFormat.h" #include "Magnum/Sampler.h" +#include "Magnum/VertexFormat.h" #include "Magnum/Vk/Enums.h" namespace Magnum { namespace Vk { namespace Test { namespace { @@ -45,6 +46,11 @@ struct EnumsTest: TestSuite::Tester { void mapVkIndexTypeUnsupported(); void mapVkIndexTypeInvalid(); + void mapVkFormatVertexFormat(); + void mapVkFormatVertexFormatImplementationSpecific(); + void mapVkFormatVertexFormatUnsupported(); + void mapVkFormatVertexFormatInvalid(); + void mapVkFormatPixelFormat(); void mapVkFormatPixelFormatImplementationSpecific(); void mapVkFormatPixelFormatUnsupported(); @@ -76,6 +82,11 @@ EnumsTest::EnumsTest() { &EnumsTest::mapVkIndexTypeUnsupported, &EnumsTest::mapVkIndexTypeInvalid, + &EnumsTest::mapVkFormatVertexFormat, + &EnumsTest::mapVkFormatVertexFormatImplementationSpecific, + &EnumsTest::mapVkFormatVertexFormatUnsupported, + &EnumsTest::mapVkFormatVertexFormatInvalid, + &EnumsTest::mapVkFormatPixelFormat, &EnumsTest::mapVkFormatPixelFormatImplementationSpecific, &EnumsTest::mapVkFormatPixelFormatUnsupported, @@ -228,6 +239,99 @@ void EnumsTest::mapVkIndexTypeInvalid() { "Vk::vkIndexType(): invalid type MeshIndexType(0x12)\n"); } +void EnumsTest::mapVkFormatVertexFormat() { + /* Touchstone verification */ + CORRADE_VERIFY(hasVkFormat(Magnum::VertexFormat::Vector3us)); + CORRADE_COMPARE(vkFormat(Magnum::VertexFormat::Vector3us), VK_FORMAT_R16G16B16_UINT); + + /* This goes through the first 16 bits, which should be enough. Going + through 32 bits takes 8 seconds, too much. */ + UnsignedInt firstUnhandled = 0xffff; + UnsignedInt nextHandled = 1; /* 0 is an invalid format */ + for(UnsignedInt i = 1; i <= 0xffff; ++i) { + const auto format = Magnum::VertexFormat(i); + /* Each case verifies: + - that the entries are ordered by number by comparing a function to + expected result (so insertion here is done in proper place) + - that there was no gap (unhandled value inside the range) + - that a particular vertex format maps to a particular VkFormat */ + #ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic error "-Wswitch" + #endif + switch(format) { + #define _c(format, expectedFormat) \ + case Magnum::VertexFormat::format: \ + CORRADE_COMPARE(nextHandled, i); \ + CORRADE_COMPARE(firstUnhandled, 0xffff); \ + CORRADE_VERIFY(hasVkFormat(Magnum::VertexFormat::format)); \ + CORRADE_COMPARE(vkFormat(Magnum::VertexFormat::format), VK_FORMAT_ ## expectedFormat); \ + ++nextHandled; \ + continue; + #define _s(format) \ + case Magnum::VertexFormat::format: { \ + CORRADE_COMPARE(nextHandled, i); \ + CORRADE_COMPARE(firstUnhandled, 0xffff); \ + CORRADE_VERIFY(!hasVkFormat(Magnum::VertexFormat::format)); \ + std::ostringstream out; \ + { /* Redirected otherwise graceful assert would abort */ \ + Error redirectError{&out}; \ + vkFormat(Magnum::VertexFormat::format); \ + } \ + Debug{Debug::Flag::NoNewlineAtTheEnd} << out.str(); \ + ++nextHandled; \ + continue; \ + } + #include "Magnum/Vk/Implementation/vertexFormatMapping.hpp" + #undef _s + #undef _c + } + #ifdef __GNUC__ + #pragma GCC diagnostic pop + #endif + + /* Not handled by any value, remember -- we might either be at the end + of the enum range (which is okay) or some value might be unhandled + here */ + firstUnhandled = i; + } + + CORRADE_COMPARE(firstUnhandled, 0xffff); +} + +void EnumsTest::mapVkFormatVertexFormatImplementationSpecific() { + CORRADE_VERIFY(hasVkFormat(Magnum::vertexFormatWrap(VK_FORMAT_A8B8G8R8_SINT_PACK32))); + CORRADE_COMPARE(vkFormat(Magnum::vertexFormatWrap(VK_FORMAT_A8B8G8R8_SINT_PACK32)), + VK_FORMAT_A8B8G8R8_SINT_PACK32); +} + +void EnumsTest::mapVkFormatVertexFormatUnsupported() { + #if 1 + CORRADE_SKIP("All vertex formats are supported."); + #else + std::ostringstream out; + Error redirectError{&out}; + + vkFormat(Magnum::VertexFormat::Vector3d); + CORRADE_COMPARE(out.str(), "Vk::vkFormat(): unsupported format VertexFormat::Vector3d\n"); + #endif +} + +void EnumsTest::mapVkFormatVertexFormatInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + hasVkFormat(Magnum::VertexFormat{}); + hasVkFormat(Magnum::VertexFormat(0x123)); + vkFormat(Magnum::VertexFormat{}); + vkFormat(Magnum::VertexFormat(0x123)); + CORRADE_COMPARE(out.str(), + "Vk::hasVkFormat(): invalid format VertexFormat(0x0)\n" + "Vk::hasVkFormat(): invalid format VertexFormat(0x123)\n" + "Vk::vkFormat(): invalid format VertexFormat(0x0)\n" + "Vk::vkFormat(): invalid format VertexFormat(0x123)\n"); +} + void EnumsTest::mapVkFormatPixelFormat() { /* Touchstone verification */ CORRADE_VERIFY(hasVkFormat(Magnum::PixelFormat::RGBA8Unorm)); From a29e9dc009166c8c694ffa2dee5a985c6b5e1317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 21 Feb 2020 17:09:42 +0100 Subject: [PATCH 079/107] Trade: handle implementation-specific vertex formats in MeshData. --- src/Magnum/Trade/MeshData.cpp | 33 ++++-- src/Magnum/Trade/MeshData.h | 123 ++++++++++++++++------- src/Magnum/Trade/Test/MeshDataTest.cpp | 134 +++++++++++++++++++++++++ 3 files changed, 246 insertions(+), 44 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index e8982ee70d..938fb83017 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -56,7 +56,7 @@ MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexForma because I feel that makes more sense than duplicating the full assert logic */ /** @todo support zero / negative stride? would be hard to transfer to GL */ - CORRADE_ASSERT(data.empty() || std::ptrdiff_t(vertexFormatSize(format)) <= data.stride(), + CORRADE_ASSERT(data.empty() || isVertexFormatImplementationSpecific(format) || std::ptrdiff_t(vertexFormatSize(format)) <= data.stride(), "Trade::MeshAttributeData: expected stride to be positive and enough to fit" << format << Debug::nospace << ", got" << data.stride(), ); } @@ -64,7 +64,7 @@ MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexForma /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ - CORRADE_ASSERT(data.empty()[0] || vertexFormatSize(format) == data.size()[1], + CORRADE_ASSERT(data.empty()[0] || isVertexFormatImplementationSpecific(format) || vertexFormatSize(format) == data.size()[1], "Trade::MeshAttributeData: second view dimension size" << data.size()[1] << "doesn't match" << format, ); CORRADE_ASSERT(data.isContiguous<1>(), "Trade::MeshAttributeData: second view dimension is not contiguous", ); @@ -99,7 +99,12 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde "Trade::MeshData: attribute" << i << "doesn't specify anything", ); CORRADE_ASSERT(attribute._vertexCount == _vertexCount, "Trade::MeshData: attribute" << i << "has" << attribute._vertexCount << "vertices but" << _vertexCount << "expected", ); - const UnsignedInt typeSize = vertexFormatSize(attribute._format); + /* Check that the view fits into the provided vertex data array. For + implementation-specific formats we don't know the size so use 0 to + check at least partially. */ + const UnsignedInt typeSize = + isVertexFormatImplementationSpecific(attribute._format) ? 0 : + vertexFormatSize(attribute._format); if(attribute._isOffsetOnly) { const std::size_t size = attribute._data.offset + (_vertexCount - 1)*attribute._stride + typeSize; CORRADE_ASSERT(!_vertexCount || size <= _vertexData.size(), @@ -317,10 +322,12 @@ Containers::StridedArrayView1D MeshData::attributeDataViewInternal(c Containers::StridedArrayView2D MeshData::attribute(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + const MeshAttributeData& attribute = _attributes[id]; /* Build a 2D view using information about attribute type size */ return Containers::arrayCast<2, const char>( - attributeDataViewInternal(_attributes[id]), - vertexFormatSize(_attributes[id]._format)); + attributeDataViewInternal(attribute), + isVertexFormatImplementationSpecific(attribute._format) ? + attribute._stride : vertexFormatSize(attribute._format)); } Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) { @@ -328,10 +335,12 @@ Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << _attributes.size() << "attributes", nullptr); + const MeshAttributeData& attribute = _attributes[id]; /* Build a 2D view using information about attribute type size */ auto out = Containers::arrayCast<2, const char>( - attributeDataViewInternal(_attributes[id]), - vertexFormatSize(_attributes[id]._format)); + attributeDataViewInternal(attribute), + isVertexFormatImplementationSpecific(attribute._format) ? + attribute._stride : vertexFormatSize(attribute._format)); /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the @@ -391,6 +400,8 @@ void MeshData::positions2DInto(const Containers::StridedArrayView1D des CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::positions2DInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination2f = Containers::arrayCast<2, Float>(destination); @@ -439,6 +450,8 @@ void MeshData::positions3DInto(const Containers::StridedArrayView1D des CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions3DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions3DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::positions3DInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const Containers::StridedArrayView2D destination2f = Containers::arrayCast<2, Float>(Containers::arrayCast(destination)); const Containers::StridedArrayView2D destination3f = Containers::arrayCast<2, Float>(destination); @@ -518,6 +531,8 @@ void MeshData::normalsInto(const Containers::StridedArrayView1D destina CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal) << "normal attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::normalsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::normalsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination3f = Containers::arrayCast<2, Float>(destination); @@ -543,6 +558,8 @@ void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination2f = Containers::arrayCast<2, Float>(destination); @@ -580,6 +597,8 @@ void MeshData::colorsInto(const Containers::StridedArrayView1D destinati CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::colorsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Color) << "color attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::colorsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::colorsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const Containers::StridedArrayView2D destination3f = Containers::arrayCast<2, Float>(Containers::arrayCast(destination)); const Containers::StridedArrayView2D destination4f = Containers::arrayCast<2, Float>(destination); diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index fe9bdf6427..34c5b54fed 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -1001,9 +1001,12 @@ class MAGNUM_TRADE_EXPORT MeshData { * * The @p id is expected to be smaller than @ref attributeCount() const. * The second dimension represents the actual data type (its size is - * equal to type size) and is guaranteed to be contiguous. Use the - * templated overload below to get the attribute in a concrete type. - * @see @ref Corrade::Containers::StridedArrayView::isContiguous() + * equal to format size for known @ref VertexFormat values and to + * attribute stride for implementation-specific values) and is + * guaranteed to be contiguous. Use the templated overload below to get + * the attribute in a concrete type. + * @see @ref Corrade::Containers::StridedArrayView::isContiguous(), + * @ref isVertexFormatImplementationSpecific() */ Containers::StridedArrayView2D attribute(UnsignedInt id) const; @@ -1021,14 +1024,18 @@ class MAGNUM_TRADE_EXPORT MeshData { * * The @p id is expected to be smaller than @ref attributeCount() const * and @p T is expected to correspond to - * @ref attributeFormat(UnsignedInt) const. You can also use the - * non-templated @ref positions2DAsArray(), @ref positions3DAsArray(), - * @ref normalsAsArray(), @ref textureCoordinates2DAsArray() and - * @ref colorsAsArray() accessors to get common attributes converted to - * usual types, but note that these operations involve extra allocation - * and data conversion. + * @ref attributeFormat(UnsignedInt) const. Expects that the vertex + * format is *not* implementation-specific, in that case you can only + * access the attribute via the typeless @ref attribute(UnsignedInt) const + * above. You can also use the non-templated @ref positions2DAsArray(), + * @ref positions3DAsArray(), @ref normalsAsArray(), + * @ref textureCoordinates2DAsArray() and @ref colorsAsArray() + * accessors to get common attributes converted to usual types, but + * note that these operations involve extra allocation and data + * conversion. * @see @ref attribute(MeshAttribute, UnsignedInt) const, - * @ref mutableAttribute(MeshAttribute, UnsignedInt) + * @ref mutableAttribute(MeshAttribute, UnsignedInt), + * @ref isVertexFormatImplementationSpecific() */ template Containers::StridedArrayView1D attribute(UnsignedInt id) const; @@ -1046,12 +1053,15 @@ class MAGNUM_TRADE_EXPORT MeshData { * * The @p id is expected to be smaller than * @ref attributeCount(MeshAttribute) const. The second dimension - * represents the actual data type (its size is equal to type size) and - * is guaranteed to be contiguous. Use the templated overload below to - * get the attribute in a concrete type. + * represents the actual data type (its size is equal to format size + * for known @ref VertexFormat values and to attribute stride for + * implementation-specific values) and is guaranteed to be contiguous. + * Use the templated overload below to get the attribute in a concrete + * type. * @see @ref attribute(UnsignedInt) const, * @ref mutableAttribute(MeshAttribute, UnsignedInt), - * @ref Corrade::Containers::StridedArrayView::isContiguous() + * @ref Corrade::Containers::StridedArrayView::isContiguous(), + * @ref isVertexFormatImplementationSpecific() */ Containers::StridedArrayView2D attribute(MeshAttribute name, UnsignedInt id = 0) const; @@ -1070,14 +1080,18 @@ class MAGNUM_TRADE_EXPORT MeshData { * The @p id is expected to be smaller than * @ref attributeCount(MeshAttribute) const and @p T is expected to * correspond to @ref attributeFormat(MeshAttribute, UnsignedInt) const. - * You can also use the non-templated @ref positions2DAsArray(), + * Expects that the vertex format is *not* implementation-specific, in + * that case you can only access the attribute via the typeless + * @ref attribute(MeshAttribute, UnsignedInt) const above. You can also + * use the non-templated @ref positions2DAsArray(), * @ref positions3DAsArray(), @ref normalsAsArray(), * @ref textureCoordinates2DAsArray() and @ref colorsAsArray() * accessors to get common attributes converted to usual types, but * note that these operations involve extra data conversion and an * allocation. * @see @ref attribute(UnsignedInt) const, - * @ref mutableAttribute(MeshAttribute, UnsignedInt) + * @ref mutableAttribute(MeshAttribute, UnsignedInt), + * @ref isVertexFormatImplementationSpecific() */ template Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0) const; @@ -1117,8 +1131,11 @@ class MAGNUM_TRADE_EXPORT MeshData { * with @ref MeshAttribute::Position as the first argument. Converts * the position array from an arbitrary underlying type and returns it * in a newly-allocated array. If the underlying type is - * three-component, the last component is dropped. - * @see @ref positions2DInto() + * three-component, the last component is dropped. Expects that the + * vertex format is *not* implementation-specific, in that case you can + * only access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref positions2DInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() */ Containers::Array positions2DAsArray(UnsignedInt id = 0) const; @@ -1139,8 +1156,11 @@ class MAGNUM_TRADE_EXPORT MeshData { * with @ref MeshAttribute::Position as the first argument. Converts * the position array from an arbitrary underlying type and returns it * in a newly-allocated array. If the underlying type is two-component, - * the Z component is set to @cpp 0.0f @ce. - * @see @ref positions3DInto() + * the Z component is set to @cpp 0.0f @ce. Expects that the vertex + * format is *not* implementation-specific, in that case you can only + * access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref positions3DInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() */ Containers::Array positions3DAsArray(UnsignedInt id = 0) const; @@ -1160,8 +1180,11 @@ class MAGNUM_TRADE_EXPORT MeshData { * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const * with @ref MeshAttribute::Normal as the first argument. Converts the * normal array from an arbitrary underlying type and returns it in a - * newly-allocated array. - * @see @ref normalsInto() + * newly-allocated array. Expects that the vertex format is *not* + * implementation-specific, in that case you can only access the + * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref normalsInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() */ Containers::Array normalsAsArray(UnsignedInt id = 0) const; @@ -1181,8 +1204,12 @@ class MAGNUM_TRADE_EXPORT MeshData { * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const * with @ref MeshAttribute::TextureCoordinates as the first argument. * Converts the texture coordinate array from an arbitrary underlying - * type and returns it in a newly-allocated array. - * @see @ref textureCoordinates2DInto() + * type and returns it in a newly-allocated array. Expects that the + * vertex format is *not* implementation-specific, in that case you can + * only access the attribute via the typeless + * @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref textureCoordinates2DInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() */ Containers::Array textureCoordinates2DAsArray(UnsignedInt id = 0) const; @@ -1203,8 +1230,11 @@ class MAGNUM_TRADE_EXPORT MeshData { * with @ref MeshAttribute::Color as the first argument. Converts the * color array from an arbitrary underlying type and returns it in a * newly-allocated array. If the underlying type is three-component, - * the alpha component is set to @cpp 1.0f @ce. - * @see @ref colorsInto() + * the alpha component is set to @cpp 1.0f @ce. Expects that the vertex + * format is *not* implementation-specific, in that case you can only + * access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * @see @ref colorsInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() */ Containers::Array colorsAsArray(UnsignedInt id = 0) const; @@ -1407,6 +1437,10 @@ namespace Implementation { /* Double types intentionally not supported for any builtin attributes right now -- only for custom types */ return + /* Implementation-specific formats can be used for any attribute + (tho the access capabilities will be reduced) */ + isVertexFormatImplementationSpecific(format) || + /* Named attributes are restricted so we can decode them */ (name == MeshAttribute::Position && (format == VertexFormat::Vector2 || format == VertexFormat::Vector2h || @@ -1453,7 +1487,8 @@ namespace Implementation { format == VertexFormat::Vector2usNormalized || format == VertexFormat::Vector2s || format == VertexFormat::Vector2sNormalized)) || - isMeshAttributeCustom(name); /* can be any format */ + /* Custom attributes can be anything */ + isMeshAttributeCustom(name); } } #endif @@ -1505,8 +1540,13 @@ template Containers::StridedArrayView1D MeshData::attribute(Un #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif - CORRADE_ASSERT(Implementation::isVertexFormatCompatible(_attributes[id]._format), - "Trade::MeshData::attribute(): improper type requested for" << _attributes[id]._name << "of format" << _attributes[id]._format, nullptr); + #ifndef CORRADE_NO_ASSERT + const MeshAttributeData& attribute = _attributes[id]; + #endif + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), {}); + CORRADE_ASSERT(Implementation::isVertexFormatCompatible(attribute._format), + "Trade::MeshData::attribute(): improper type requested for" << attribute._name << "of format" << attribute._format, nullptr); return Containers::arrayCast<1, const T>(data); } @@ -1515,8 +1555,13 @@ template Containers::StridedArrayView1D MeshData::mutableAttribute(U #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif - CORRADE_ASSERT(Implementation::isVertexFormatCompatible(_attributes[id]._format), - "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[id]._name << "of format" << _attributes[id]._format, nullptr); + #ifndef CORRADE_NO_ASSERT + const MeshAttributeData& attribute = _attributes[id]; + #endif + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), {}); + CORRADE_ASSERT(Implementation::isVertexFormatCompatible(attribute._format), + "Trade::MeshData::mutableAttribute(): improper type requested for" << attribute._name << "of format" << attribute._format, nullptr); return Containers::arrayCast<1, T>(data); } @@ -1526,10 +1571,12 @@ template Containers::StridedArrayView1D MeshData::attribute(Me if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - const UnsignedInt attributeId = attributeFor(name, id); + const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; #endif - CORRADE_ASSERT(Implementation::isVertexFormatCompatible(_attributes[attributeId]._format), - "Trade::MeshData::attribute(): improper type requested for" << _attributes[attributeId]._name << "of format" << _attributes[attributeId]._format, nullptr); + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), {}); + CORRADE_ASSERT(Implementation::isVertexFormatCompatible(attribute._format), + "Trade::MeshData::attribute(): improper type requested for" << attribute._name << "of format" << attribute._format, nullptr); return Containers::arrayCast<1, const T>(data); } @@ -1539,10 +1586,12 @@ template Containers::StridedArrayView1D MeshData::mutableAttribute(M if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - const UnsignedInt attributeId = attributeFor(name, id); + const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; #endif - CORRADE_ASSERT(Implementation::isVertexFormatCompatible(_attributes[attributeId]._format), - "Trade::MeshData::mutableAttribute(): improper type requested for" << _attributes[attributeId]._name << "of format" << _attributes[attributeId]._format, nullptr); + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), {}); + CORRADE_ASSERT(Implementation::isVertexFormatCompatible(attribute._format), + "Trade::MeshData::mutableAttribute(): improper type requested for" << attribute._name << "of format" << attribute._format, nullptr); return Containers::arrayCast<1, T>(data); } diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 972f2d254c..0657703f32 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -60,6 +60,7 @@ struct MeshDataTest: TestSuite::Tester { void constructAttributePadding(); void constructAttributeNonOwningArray(); void constructAttributeOffsetOnly(); + void constructAttributeImplementationSpecificFormat(); void constructAttributeWrongFormat(); void constructAttributeWrongStride(); void constructAttributeWrongDataAccess(); @@ -123,6 +124,10 @@ struct MeshDataTest: TestSuite::Tester { template void colorsAsArrayPackedUnsignedNormalized(); void colorsIntoArrayInvalidSize(); + void implementationSpecificVertexFormat(); + void implementationSpecificVertexFormatWrongAccess(); + void implementationSpecificVertexFormatNotContained(); + void mutableAccessNotAllowed(); void indicesNotIndexed(); @@ -178,6 +183,7 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttributePadding, &MeshDataTest::constructAttributeNonOwningArray, &MeshDataTest::constructAttributeOffsetOnly, + &MeshDataTest::constructAttributeImplementationSpecificFormat, &MeshDataTest::constructAttributeWrongFormat, &MeshDataTest::constructAttributeWrongStride, &MeshDataTest::constructAttributeWrongDataAccess, @@ -288,6 +294,10 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, &MeshDataTest::colorsIntoArrayInvalidSize, + &MeshDataTest::implementationSpecificVertexFormat, + &MeshDataTest::implementationSpecificVertexFormatWrongAccess, + &MeshDataTest::implementationSpecificVertexFormatNotContained, + &MeshDataTest::mutableAccessNotAllowed, &MeshDataTest::indicesNotIndexed, @@ -574,6 +584,18 @@ void MeshDataTest::constructAttributeOffsetOnly() { TestSuite::Compare::Container); } +void MeshDataTest::constructAttributeImplementationSpecificFormat() { + Vector2 positions[]{{1.0f, 0.3f}, {0.5f, 0.7f}}; + + /* This should not fire any asserts */ + MeshAttributeData a{MeshAttribute::TextureCoordinates, vertexFormatWrap(0x3a), positions}; + CORRADE_COMPARE(a.name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(a.format(), vertexFormatWrap(0x3a)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.data()), + Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), + TestSuite::Compare::Container); +} + void MeshDataTest::constructAttributeWrongFormat() { Vector2 positionData[3]; @@ -1234,6 +1256,8 @@ void MeshDataTest::constructAttributeNotContained() { MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast(vertexData)}; MeshAttributeData positions2{MeshAttribute::Position, Containers::arrayView(vertexData2)}; MeshAttributeData positions3{MeshAttribute::Position, VertexFormat::Vector2, 1, 3, 8}; + /* See implementationSpecificVertexFormatNotContained() below for + implementation-specific formats */ std::ostringstream out; Error redirectError{&out}; @@ -1862,6 +1886,116 @@ void MeshDataTest::colorsIntoArrayInvalidSize() { "Trade::MeshData::colorsInto(): expected a view with 3 elements but got 2\n"); } +void MeshDataTest::implementationSpecificVertexFormat() { + struct Vertex { + Long:64; + long double thing; + } vertexData[] { + {456.0l}, + {456.0l} + }; + + /* Constructing should work w/o asserts */ + Containers::StridedArrayView1D attribute{vertexData, + &vertexData[0].thing, 2, sizeof(Vertex)}; + MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { + MeshAttributeData{MeshAttribute::Position, + vertexFormatWrap(0xdead1), attribute}, + MeshAttributeData{MeshAttribute::Normal, + vertexFormatWrap(0xdead2), attribute}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + vertexFormatWrap(0xdead3), attribute}, + MeshAttributeData{MeshAttribute::Color, + vertexFormatWrap(0xdead4), attribute}}}; + + /* Getting typeless attribute should work also */ + UnsignedInt format = 0xdead1; + for(MeshAttribute name: {MeshAttribute::Position, + MeshAttribute::Normal, + MeshAttribute::TextureCoordinates, + MeshAttribute::Color}) { + CORRADE_ITERATION(name); + CORRADE_COMPARE(data.attributeFormat(name), vertexFormatWrap(format++)); + + /* The actual type size is unknown, so this will use the full stride */ + CORRADE_COMPARE(data.attribute(name).size()[1], sizeof(Vertex)); + + CORRADE_COMPARE_AS((Containers::arrayCast<1, const long double>( + data.attribute(name).prefix({2, sizeof(long double)}))), + attribute, TestSuite::Compare::Container); + CORRADE_COMPARE_AS((Containers::arrayCast<1, const long double>( + data.mutableAttribute(name).prefix({2, sizeof(long double)}))), + attribute, TestSuite::Compare::Container); + } +} + +void MeshDataTest::implementationSpecificVertexFormatWrongAccess() { + struct Vertex { + Long:64; + long double thing; + } vertexData[] { + {456.0l}, + {456.0l} + }; + + Containers::StridedArrayView1D attribute{vertexData, + &vertexData[0].thing, 2, sizeof(Vertex)}; + MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { + MeshAttributeData{MeshAttribute::Position, + vertexFormatWrap(0xdead1), attribute}, + MeshAttributeData{MeshAttribute::Normal, + vertexFormatWrap(0xdead2), attribute}, + MeshAttributeData{MeshAttribute::TextureCoordinates, + vertexFormatWrap(0xdead3), attribute}, + MeshAttributeData{MeshAttribute::Color, + vertexFormatWrap(0xdead4), attribute}}}; + + std::ostringstream out; + Error redirectError{&out}; + data.attribute(MeshAttribute::Position); + data.attribute(MeshAttribute::Normal); + data.attribute(MeshAttribute::TextureCoordinates); + data.attribute(MeshAttribute::Color); + data.mutableAttribute(MeshAttribute::Position); + data.mutableAttribute(MeshAttribute::Normal); + data.mutableAttribute(MeshAttribute::TextureCoordinates); + data.mutableAttribute(MeshAttribute::Color); + data.positions2DAsArray(); + data.positions3DAsArray(); + data.normalsAsArray(); + data.textureCoordinates2DAsArray(); + data.colorsAsArray(); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead2\n" + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead3\n" + "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead4\n" + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead2\n" + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead3\n" + "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead4\n" + "Trade::MeshData::positions2DInto(): can't extract data out of an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::positions3DInto(): can't extract data out of an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::normalsInto(): can't extract data out of an implementation-specific vertex format 0xdead2\n" + "Trade::MeshData::textureCoordinatesInto(): can't extract data out of an implementation-specific vertex format 0xdead3\n" + "Trade::MeshData::colorsInto(): can't extract data out of an implementation-specific vertex format 0xdead4\n"); +} + +void MeshDataTest::implementationSpecificVertexFormatNotContained() { + Containers::Array vertexData{reinterpret_cast(0xbadda9), 3, [](char*, std::size_t){}}; + Containers::ArrayView vertexData2{reinterpret_cast(0xdead), 3}; + MeshAttributeData positions{MeshAttribute::Position, vertexFormatWrap(0x3a), vertexData}; + MeshAttributeData positions2{MeshAttribute::Position, vertexFormatWrap(0x3a), vertexData2}; + + std::ostringstream out; + Error redirectError{&out}; + MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}}; + CORRADE_COMPARE(out.str(), + /* Assumes size of the type is 0, so the diagnostic is different from + constructAttributeNotContained() */ + "Trade::MeshData: attribute 1 [0xdead:0xdeaf] is not contained in passed vertexData array [0xbadda9:0xbaddac]\n"); +} + void MeshDataTest::mutableAccessNotAllowed() { const UnsignedShort indexData[]{0, 1, 0}; const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; From a3bb6ba4c5cbb11ed2d3ba2d3d844d245e043a4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 18 Feb 2020 20:29:06 +0100 Subject: [PATCH 080/107] MeshTools: explicitly handle unavailable attribute types in compile(). --- src/Magnum/MeshTools/Compile.cpp | 9 +++++++++ src/Magnum/MeshTools/Compile.h | 3 +++ src/Magnum/MeshTools/Test/CompileGLTest.cpp | 14 ++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/Magnum/MeshTools/Compile.cpp b/src/Magnum/MeshTools/Compile.cpp index 74b434449d..fa7938fa36 100644 --- a/src/Magnum/MeshTools/Compile.cpp +++ b/src/Magnum/MeshTools/Compile.cpp @@ -140,7 +140,16 @@ GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buff GL::Buffer verticesRef = GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array); for(UnsignedInt i = 0; i != meshData.attributeCount(); ++i) { Containers::Optional attribute; + + /* Ignore implementation-specific formats because GL needs three + separate values to describe them so there's no way to put them in a + single 32-bit value :( */ const VertexFormat format = meshData.attributeFormat(i); + if(isVertexFormatImplementationSpecific(format)) { + Warning{} << "MeshTools::compile(): ignoring attribute" << meshData.attributeName(i) << "with an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)); + continue; + } + switch(meshData.attributeName(i)) { case Trade::MeshAttribute::Position: /* Pick 3D position always, the type will properly reduce it to diff --git a/src/Magnum/MeshTools/Compile.h b/src/Magnum/MeshTools/Compile.h index 58e2a1fadf..3353495862 100644 --- a/src/Magnum/MeshTools/Compile.h +++ b/src/Magnum/MeshTools/Compile.h @@ -103,6 +103,9 @@ possibly also an index buffer, if the mesh is indexed. - If the mesh contains colors, these are bound to @ref Shaders::Generic::Color3 / @ref Shaders::Generic::Color4 based on their type. +- Custom attributes and known attributes of implementation-specific types + are ignored with a warning. See the @ref compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&) + for an example showing how to bind them manually. If normal generation is not requested, @ref Trade::MeshData::indexData() and @ref Trade::MeshData::vertexData() are uploaded as-is without any further diff --git a/src/Magnum/MeshTools/Test/CompileGLTest.cpp b/src/Magnum/MeshTools/Test/CompileGLTest.cpp index fa80cb067d..7debc5f29d 100644 --- a/src/Magnum/MeshTools/Test/CompileGLTest.cpp +++ b/src/Magnum/MeshTools/Test/CompileGLTest.cpp @@ -94,6 +94,7 @@ struct CompileGLTest: GL::OpenGLTester { void packedAttributes(); void unknownAttribute(); + void implementationSpecificAttributeFormat(); void generateNormalsNoPosition(); void generateNormals2DPosition(); void generateNormalsNoFloats(); @@ -205,6 +206,7 @@ CompileGLTest::CompileGLTest() { addTests({&CompileGLTest::packedAttributes, &CompileGLTest::unknownAttribute, + &CompileGLTest::implementationSpecificAttributeFormat, &CompileGLTest::generateNormalsNoPosition, &CompileGLTest::generateNormals2DPosition, &CompileGLTest::generateNormalsNoFloats}); @@ -724,6 +726,18 @@ void CompileGLTest::unknownAttribute() { "MeshTools::compile(): ignoring unknown attribute Trade::MeshAttribute::Custom(115)\n"); } +void CompileGLTest::implementationSpecificAttributeFormat() { + Trade::MeshData data{MeshPrimitive::Triangles, + nullptr, {Trade::MeshAttributeData{Trade::MeshAttribute::Position, + vertexFormatWrap(0xdead), nullptr}}}; + + std::ostringstream out; + Warning redirectError{&out}; + MeshTools::compile(data); + CORRADE_COMPARE(out.str(), + "MeshTools::compile(): ignoring attribute Trade::MeshAttribute::Position with an implementation-specific format 0xdead\n"); +} + void CompileGLTest::generateNormalsNoPosition() { Trade::MeshData data{MeshPrimitive::Triangles, 1}; From f46b522ec5f54796e28f0de6e3a723ba48bd1c56 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 17 Jan 2020 23:48:05 +0100 Subject: [PATCH 081/107] doc: dev guides for adding new mesh attributes name / type. --- doc/developers.dox | 57 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/doc/developers.dox b/doc/developers.dox index c1ae084d10..138fb391b3 100644 --- a/doc/developers.dox +++ b/doc/developers.dox @@ -407,7 +407,7 @@ in inverse --- but usually @ref developers-deprecation "deprecate first". - run [doxygen.py](http://mcss.mosra.cz/doxygen/) on `Doxyfile-mcss` and verify there are no new warnings - eyeball the relevant docs and fix suspicious things -10. Push to a temporary branch (e.g., `next`) +10. Push to a temporary branch (e.g., `next`) 11. Iterate until the CIs are green 12. Merge to `master` 13. If possible, trigger builds of dependent projects (where they are still @@ -488,6 +488,61 @@ in inverse --- but usually @ref developers-deprecation "deprecate first". 8. If possible, trigger builds of dependent projects and verify they are still green (or wait for the scheduled builds) +@section developers-adding-attribute Checklist for adding a new mesh attribute + +1. Extend @ref Trade::MeshAttribute with the new entry +2. Add a corresponding reserved type to @ref Shaders::Generic, if not there + already + - Also update `src/Magnum/Shaders/generic.glsl` with the reserved ID +3. Update the type assertion in the @ref Trade::MeshAttributeData constructor + to account for the new type +4. Add a pair of convenience getters to @ref Trade::MeshData similar to e.g. + @ref Trade::MeshData::normalsInto() / @ref Trade::MeshData::normalsAsArray() + with a type that's the same as the one used in the @ref Shaders::Generic + definition, test that it does the right thing for every supported type +5. Update @ref Trade::operator<<(Debug&, MeshAttribute) for the new entry +6. Update @ref MeshTools::compile() to recognize the new attribute. If there + is already a builtin shader capable of using this attribute, add new test + to `MeshToolsCompileGLTest`. +7. Push to a temporary branch (e.g., `next`) +8. Iterate until the CIs are green +9. Merge to `master` + +@section developers-adding-vertex-format Checklist for adding a new vertex format + +1. Extend @ref VertexFormat with the new entry, if it's not there already, + document mapping to GL, Vulkan, D3D and Metal (if exists) +2. Update docs of @ref Trade::MeshAttribute to mention where the format can be + newly used +3. Appropriately relax the assertion in the @ref Trade::MeshAttributeData + constructor +3. Extend `Trade::Implementation::vertexFormatFor()`, add a mapping between + this entry and a C++ type, optionally also + `isVertexFormatCompatibleWithAttribute()` if there's more than one entry + corresponding to a particular C++ type. If the mapping is unconventional, + be sure to mention it in the + @ref Trade::MeshAttributeData::MeshAttributeData(MeshAttribute, const Containers::StridedArrayView1D&) + constructor docs. +4. Update corresponding `Trade::MeshData::*Into()` convenience getters to + ensure they can handle this type +5. Update `src/Magnum/Implementation/vertexFormatMapping.hpp` and + `src/Magnum/Vk/Implementation/vertexFormatMapping.hpp` with the new entry +6. Update @ref vertexFormatSize(), @ref vertexFormatComponentFormat(), + @ref vertexFormatComponentCount() and @ref isVertexFormatNormalized() to + handle this format +7. Update the @ref GL::hasVertexFormat() utility and + @ref GL::DynamicAttribute::DynamicAttribute(Kind, UnsignedInt, VertexFormat) + constructor to provide mapping of the new type to GL; add a test for the + new type, if it's special in some way, otherwise the all-catching loop will + check it +8. Update @ref MeshTools::compile() to recognize the new type (if anything + extra needs to be done, usually doesn't as everything is handled by + @ref GL::DynamicAttribute already); add corresponding new test(s) to + `MeshToolsCompileGLTest`. +9. Push to a temporary branch (e.g., `next`) +10. Iterate until the CIs are green +11. Merge to `master` + @section developers-gl-extensions Checklist for adding / removing GL versions and extensions 1. Install [flextGL](https://github.com/mosra/flextgl) From c0e3a84250254758f629bda2de1e840f0505d62d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 21 Feb 2020 23:48:16 +0100 Subject: [PATCH 082/107] Trade: support array attributes in MeshData. The last major bit needed for meshlet support. --- src/Magnum/Trade/MeshData.cpp | 28 ++- src/Magnum/Trade/MeshData.h | 311 ++++++++++++++++++++----- src/Magnum/Trade/Test/MeshDataTest.cpp | 276 ++++++++++++++++++++++ 3 files changed, 549 insertions(+), 66 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 938fb83017..adaa1db160 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -51,7 +51,7 @@ MeshIndexData::MeshIndexData(const Containers::StridedArrayView2D& d _data = data.asContiguous(); } -MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, format, data, nullptr} { +MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, format, arraySize, data, nullptr} { /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ @@ -60,12 +60,16 @@ MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexForma "Trade::MeshAttributeData: expected stride to be positive and enough to fit" << format << Debug::nospace << ", got" << data.stride(), ); } -MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, format, Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, nullptr} { +MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, format, arraySize, Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, nullptr} { /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ - CORRADE_ASSERT(data.empty()[0] || isVertexFormatImplementationSpecific(format) || vertexFormatSize(format) == data.size()[1], + #ifndef CORRADE_NO_ASSERT + if(arraySize) CORRADE_ASSERT(data.empty()[0] || isVertexFormatImplementationSpecific(format) || data.size()[1] == vertexFormatSize(format)*arraySize, + "Trade::MeshAttributeData: second view dimension size" << data.size()[1] << "doesn't match" << format << "and array size" << arraySize, ); + else CORRADE_ASSERT(data.empty()[0] || isVertexFormatImplementationSpecific(format) || data.size()[1] == vertexFormatSize(format), "Trade::MeshAttributeData: second view dimension size" << data.size()[1] << "doesn't match" << format, ); + #endif CORRADE_ASSERT(data.isContiguous<1>(), "Trade::MeshAttributeData: second view dimension is not contiguous", ); } @@ -263,6 +267,12 @@ UnsignedInt MeshData::attributeStride(UnsignedInt id) const { return _attributes[id]._stride; } +UnsignedShort MeshData::attributeArraySize(UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeArraySize(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id]._arraySize; +} + UnsignedInt MeshData::attributeCount(const MeshAttribute name) const { UnsignedInt count = 0; for(const MeshAttributeData& attribute: _attributes) @@ -307,6 +317,12 @@ UnsignedInt MeshData::attributeStride(MeshAttribute name, UnsignedInt id) const return attributeStride(attributeId); } +UnsignedShort MeshData::attributeArraySize(MeshAttribute name, UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(name, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeArraySize(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + return attributeArraySize(attributeId); +} + Containers::StridedArrayView1D MeshData::attributeDataViewInternal(const MeshAttributeData& attribute) const { return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ @@ -327,7 +343,8 @@ Containers::StridedArrayView2D MeshData::attribute(UnsignedInt id) c return Containers::arrayCast<2, const char>( attributeDataViewInternal(attribute), isVertexFormatImplementationSpecific(attribute._format) ? - attribute._stride : vertexFormatSize(attribute._format)); + attribute._stride : vertexFormatSize(attribute._format)* + (attribute._arraySize ? attribute._arraySize : 1)); } Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) { @@ -340,7 +357,8 @@ Containers::StridedArrayView2D MeshData::mutableAttribute(UnsignedInt id) auto out = Containers::arrayCast<2, const char>( attributeDataViewInternal(attribute), isVertexFormatImplementationSpecific(attribute._format) ? - attribute._stride : vertexFormatSize(attribute._format)); + attribute._stride : vertexFormatSize(attribute._format)* + (attribute._arraySize ? attribute._arraySize : 1)); /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 34c5b54fed..efee5571f6 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -244,7 +244,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * initialization of the attribute array for @ref MeshData, expected to * be replaced with concrete values later. */ - constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{}, _isOffsetOnly{false} {} + constexpr explicit MeshAttributeData() noexcept: _data{}, _vertexCount{}, _format{}, _stride{}, _name{}, _arraySize{}, _isOffsetOnly{false} {} /** * @brief Type-erased constructor @@ -255,7 +255,21 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * Expects that @p data stride is large enough to fit @p type and that * @p type corresponds to @p name. */ - explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data) noexcept; + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, format, 0, data} {} + + /** + * @brief Type-erased constructor for an array attribute + * @param name Attribute name + * @param format Vertex format + * @param arraySize Array size + * @param data Attribute data + * + * Expects that @p data stride is large enough to fit @p type, @p type + * corresponds to @p name and @p arraySize is zero for builtin + * attributes. Passing @cpp 0 @ce to @p arraySize is equivalent to + * calling the above overload. + */ + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView1D& data) noexcept; /** * @brief Constructor @@ -266,10 +280,25 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * Expects that the second dimension of @p data is contiguous and its * size matches @p type; and that @p type corresponds to @p name. */ - explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView2D& data) noexcept; + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, format, 0, data} {} /** @overload */ - explicit MeshAttributeData(MeshAttribute name, VertexFormat format, std::nullptr_t) noexcept: MeshAttributeData{name, format, nullptr, nullptr} {} + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, std::nullptr_t) noexcept: MeshAttributeData{name, format, 0, nullptr, nullptr} {} + + /** + * @brief Construct an array attribute + * @param name Attribute name + * @param format Vertex format + * @param arraySize Array size + * @param data Attribute data + * + * Expects that the second dimension of @p data is contiguous and its + * size matches @p type and @p arraSize, that @p type corresponds to + * @p name and @p arraySize is zero for builtin attributes. Passing + * @cpp 0 @ce to @p arraySize is equivalent to calling the above + * overload. + */ + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView2D& data) noexcept; /** * @brief Constructor @@ -305,6 +334,21 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /** @overload */ template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {} + /** + * @brief Construct an array attribute + * @param name Attribute name + * @param data Attribute data + * + * Detects @ref VertexFormat based on @p T and calls + * @ref MeshAttributeData(MeshAttribute, VertexFormat, UnsignedShort, const Containers::StridedArrayView1D&) + * with the second dimension size passed to @p arraySize. Expects that + * the second dimension is contiguous. At the moment only custom + * attributes can be arrays, which means this function can't be used + * with a builtin @p name. See @ref MeshAttributeData(MeshAttribute, const Containers::StridedArrayView1D&) + * for details about @ref VertexFormat detection. + */ + template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView2D& data) noexcept; + /** * @brief Construct an offset-only attribute * @param name Attribute name @@ -312,16 +356,19 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * @param offset Attribute data offset * @param vertexCount Attribute vertex count * @param stride Attribute stride + * @param arraySize Array size. Use @cpp 0 @ce for non-array + * attributes. * * Instances created this way refer to an offset in unspecified * external vertex data instead of containing the data view directly. * Useful when the location of the vertex data array is not known at - * attribute construction time. Note that instances created this way - * can't be used in most @ref MeshTools algorithms. - * @see @ref isOffsetOnly(), + * attribute construction time. Expects that @p arraySize is zero for + * builtin attributes. Note that instances created this way can't be + * used in most @ref MeshTools algorithms. + * @see @ref isOffsetOnly(), @ref arraySize(), * @ref data(Containers::ArrayView) const */ - explicit constexpr MeshAttributeData(MeshAttribute name, VertexFormat format, std::size_t offset, UnsignedInt vertexCount, std::ptrdiff_t stride) noexcept; + explicit constexpr MeshAttributeData(MeshAttribute name, VertexFormat format, std::size_t offset, UnsignedInt vertexCount, std::ptrdiff_t stride, UnsignedShort arraySize = 0) noexcept; /** * @brief Construct a pad value @@ -334,7 +381,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { constexpr explicit MeshAttributeData(Int padding): _data{nullptr}, _vertexCount{0}, _format{}, _stride{ (CORRADE_CONSTEXPR_ASSERT(padding >= -32768 && padding <= 32767, "Trade::MeshAttributeData: at most 32k padding supported, got" << padding), Short(padding)) - }, _name{}, _isOffsetOnly{false} {} + }, _name{}, _arraySize{}, _isOffsetOnly{false} {} /** * @brief If the attribute is offset-only @@ -342,7 +389,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * Returns @cpp true @ce if the attribute doesn't contain the data view * directly, but instead refers to unspecified external vertex data. * @see @ref data(Containers::ArrayView) const, - * @ref MeshAttributeData(MeshAttribute, VertexFormat, std::size_t, UnsignedInt, std::ptrdiff_t) + * @ref MeshAttributeData(MeshAttribute, VertexFormat, std::size_t, UnsignedInt, std::ptrdiff_t, UnsignedShort) */ constexpr bool isOffsetOnly() const { return _isOffsetOnly; } @@ -352,6 +399,9 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /** @brief Attribute format */ constexpr VertexFormat format() const { return _format; } + /** @brief Attribute array size */ + constexpr UnsignedShort arraySize() const { return _arraySize; } + /** * @brief Type-erased attribute data * @@ -383,7 +433,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { } private: - constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept; + constexpr explicit MeshAttributeData(MeshAttribute name, VertexFormat format, UnsignedShort arraySize, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept; friend MeshData; union Data { @@ -402,9 +452,10 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { current largest reported stride is 4k so 32k should be enough */ Short _stride; MeshAttribute _name; + UnsignedShort _arraySize; bool _isOffsetOnly; - /* 3 bytes free for more stuff on 64b (21, aligned to 24) and on 32b - (17 used, aligned to 20) */ + /* 1 byte free for more stuff on 64b (23, aligned to 24) and on 32b + (19, aligned to 20) */ }; /** @relatesalso MeshAttributeData @@ -940,6 +991,26 @@ class MAGNUM_TRADE_EXPORT MeshData { */ UnsignedInt attributeStride(UnsignedInt id) const; + /** + * @brief Attribute array size + * + * In case given attribute is an array (the equivalent of e.g. + * @cpp int[30] @ce), returns array size, otherwise returns @cpp 0 @ce. + * At the moment only custom attributes can be arrays, no builtin + * @ref MeshAttribute is an array attribute. You can also use + * @ref attributeArraySize(MeshAttribute, UnsignedInt) const to + * directly get array size of given named attribute. + * + * Note that this is different from vertex count, which is exposed + * through @ref vertexCount(), and is an orthogonal concept to having + * multiple attributes of the same name (for example two sets of + * texture coordinates), which is exposed through + * @ref attributeCount(MeshAttribute) const. See + * @ref Trade-MeshData-populating-custom for an example. + * @see @ref isMeshAttributeCustom() + */ + UnsignedShort attributeArraySize(UnsignedInt id) const; + /** * @brief Whether the mesh has given attribute * @@ -997,7 +1068,18 @@ class MAGNUM_TRADE_EXPORT MeshData { UnsignedInt attributeStride(MeshAttribute name, UnsignedInt id = 0) const; /** - * @brief Data for given attribute array + * @brief Array size of a named attribute + * + * The @p id is expected to be smaller than + * @ref attributeCount(MeshAttribute) const. Note that this is + * different from vertex count, and is an orthogonal concept to having + * multiple attributes of the same name --- see + * @ref attributeArraySize(UnsignedInt) const for more information. + */ + UnsignedShort attributeArraySize(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Data for given attribute * * The @p id is expected to be smaller than @ref attributeCount() const. * The second dimension represents the actual data type (its size is @@ -1011,7 +1093,7 @@ class MAGNUM_TRADE_EXPORT MeshData { Containers::StridedArrayView2D attribute(UnsignedInt id) const; /** - * @brief Mutable data for given attribute array + * @brief Mutable data for given attribute * * Like @ref attribute(UnsignedInt) const, but returns a mutable view. * Expects that the mesh is mutable. @@ -1020,36 +1102,61 @@ class MAGNUM_TRADE_EXPORT MeshData { Containers::StridedArrayView2D mutableAttribute(UnsignedInt id); /** - * @brief Data for given attribute array in a concrete type + * @brief Data for given attribute in a concrete type * * The @p id is expected to be smaller than @ref attributeCount() const * and @p T is expected to correspond to * @ref attributeFormat(UnsignedInt) const. Expects that the vertex * format is *not* implementation-specific, in that case you can only * access the attribute via the typeless @ref attribute(UnsignedInt) const - * above. You can also use the non-templated @ref positions2DAsArray(), - * @ref positions3DAsArray(), @ref normalsAsArray(), - * @ref textureCoordinates2DAsArray() and @ref colorsAsArray() - * accessors to get common attributes converted to usual types, but - * note that these operations involve extra allocation and data - * conversion. + * above. The attribute is also expected to not be an array, in that + * case you need to use the overload below by using @cpp T[] @ce + * instead of @cpp T @ce. You can also use the non-templated + * @ref positions2DAsArray(), @ref positions3DAsArray(), + * @ref normalsAsArray(), @ref textureCoordinates2DAsArray() and + * @ref colorsAsArray() accessors to get common attributes converted to + * usual types, but note that these operations involve extra allocation + * and data conversion. * @see @ref attribute(MeshAttribute, UnsignedInt) const, * @ref mutableAttribute(MeshAttribute, UnsignedInt), - * @ref isVertexFormatImplementationSpecific() + * @ref isVertexFormatImplementationSpecific(), + * @ref attributeArraySize() + */ + template::value>::type> Containers::StridedArrayView1D attribute(UnsignedInt id) const; + + /** + * @brief Data for given array attribute in a concrete type + * + * Same as above, except that it works with array attributes instead + * --- you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref attributeArraySize() for given attribute. */ - template Containers::StridedArrayView1D attribute(UnsignedInt id) const; + template::value>::type> Containers::StridedArrayView2D::type> attribute(UnsignedInt id) const; /** - * @brief Mutable data for given attribute array in a concrete type + * @brief Mutable data for given attribute in a concrete type * * Like @ref attribute(UnsignedInt) const, but returns a mutable view. * Expects that the mesh is mutable. * @see @ref vertexDataFlags() */ - template Containers::StridedArrayView1D mutableAttribute(UnsignedInt id); + template::value>::type> Containers::StridedArrayView1D mutableAttribute(UnsignedInt id); /** - * @brief Data for given named attribute array + * @brief Mutable data for given array attribute in a concrete type + * + * Same as above, except that it works with array attributes instead + * --- you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref attributeArraySize() for given attribute. + */ + template::value>::type> Containers::StridedArrayView2D::type> mutableAttribute(UnsignedInt id); + + /** + * @brief Data for given named attribute * * The @p id is expected to be smaller than * @ref attributeCount(MeshAttribute) const. The second dimension @@ -1066,7 +1173,7 @@ class MAGNUM_TRADE_EXPORT MeshData { Containers::StridedArrayView2D attribute(MeshAttribute name, UnsignedInt id = 0) const; /** - * @brief Mutable data for given named attribute array + * @brief Mutable data for given named attribute * * Like @ref attribute(MeshAttribute, UnsignedInt) const, but returns a * mutable view. Expects that the mesh is mutable. @@ -1075,7 +1182,7 @@ class MAGNUM_TRADE_EXPORT MeshData { Containers::StridedArrayView2D mutableAttribute(MeshAttribute name, UnsignedInt id = 0); /** - * @brief Data for given named attribute array in a concrete type + * @brief Data for given named attribute in a concrete type * * The @p id is expected to be smaller than * @ref attributeCount(MeshAttribute) const and @p T is expected to @@ -1093,16 +1200,38 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref mutableAttribute(MeshAttribute, UnsignedInt), * @ref isVertexFormatImplementationSpecific() */ - template Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0) const; + template::value>::type> Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0) const; /** - * @brief Mutable data for given named attribute array in a concrete type + * @brief Data for given named array attribute in a concrete type + * + * Same as above, except that it works with array attributes instead + * --- you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref attributeArraySize() for given attribute. + */ + template::value>::type> Containers::StridedArrayView2D::type> attribute(MeshAttribute name, UnsignedInt id = 0) const; + + /** + * @brief Mutable data for given named attribute in a concrete type * * Like @ref attribute(MeshAttribute, UnsignedInt) const, but returns a * mutable view. Expects that the mesh is mutable. * @see @ref vertexDataFlags() */ - template Containers::StridedArrayView1D mutableAttribute(MeshAttribute name, UnsignedInt id = 0); + template::value>::type> Containers::StridedArrayView1D mutableAttribute(MeshAttribute name, UnsignedInt id = 0); + + /** + * @brief Mutable data for given named array attribute in a concrete type + * + * Same as above, except that it works with array attributes instead + * --- you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref attributeArraySize() for given attribute. + */ + template::value>::type> Containers::StridedArrayView2D::type> mutableAttribute(MeshAttribute name, UnsignedInt id = 0); /** * @brief Indices as 32-bit integers @@ -1318,6 +1447,10 @@ class MAGNUM_TRADE_EXPORT MeshData { /* Like attribute(), but returning just a 1D view */ Containers::StridedArrayView1D attributeDataViewInternal(const MeshAttributeData& attribute) const; + #ifndef CORRADE_NO_ASSERT + template bool checkAttributeTypeCompatibility(const MeshAttributeData& attribute, const char* prefix) const; + #endif + /* GPUs don't currently support more than 32-bit index types / vertex counts so this should be enough. Sanity check: https://www.khronos.org/registry/vulkan/specs/1.2-extensions/man/html/VkIndexType.html */ @@ -1490,10 +1623,14 @@ namespace Implementation { /* Custom attributes can be anything */ isMeshAttributeCustom(name); } + + constexpr bool isAttributeArrayAllowed(MeshAttribute name) { + return isMeshAttributeCustom(name); + } } #endif -constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept: +constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const UnsignedShort arraySize, const Containers::StridedArrayView1D& data, std::nullptr_t) noexcept: _data{data.data()}, _vertexCount{UnsignedInt(data.size())}, _format{format}, /** @todo support zero / negative stride? would be hard to transfer to GL */ _stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(data.stride()) & 0xffff8000), @@ -1501,9 +1638,12 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V Short(data.stride())) }, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format), "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name) - }, _isOffsetOnly{false} {} + }, _arraySize{(CORRADE_CONSTEXPR_ASSERT(!arraySize || Implementation::isAttributeArrayAllowed(name), + "Trade::MeshAttributeData:" << name << "can't be an array attribute"), arraySize) + }, _isOffsetOnly{(CORRADE_CONSTEXPR_ASSERT(!arraySize || !isVertexFormatImplementationSpecific(format), + "Trade::MeshAttributeData: array attributes can't have an implementation-specific format"), false)} {} -constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const std::size_t offset, const UnsignedInt vertexCount, const std::ptrdiff_t stride) noexcept: +constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const std::size_t offset, const UnsignedInt vertexCount, const std::ptrdiff_t stride, UnsignedShort arraySize) noexcept: _data{offset}, _vertexCount{vertexCount}, _format{format}, /** @todo support zero / negative stride? would be hard to transfer to GL */ _stride{(CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(stride) & 0xffff8000), @@ -1511,9 +1651,14 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V Short(stride)) }, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format), "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name) - }, _isOffsetOnly{true} {} + }, _arraySize{(CORRADE_CONSTEXPR_ASSERT(!arraySize || Implementation::isAttributeArrayAllowed(name), + "Trade::MeshAttributeData:" << name << "can't be an array attribute"), arraySize) + }, _isOffsetOnly{(CORRADE_CONSTEXPR_ASSERT(!arraySize || !isVertexFormatImplementationSpecific(format), + "Trade::MeshAttributeData: array attributes can't have an implementation-specific format"), true)} {} -template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), data, nullptr} {} +template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), 0, data, nullptr} {} + +template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{name, Implementation::vertexFormatFor::type>(), UnsignedShort(data.size()[1]), Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, (CORRADE_CONSTEXPR_ASSERT(data.stride()[1] == sizeof(T), "Trade::MeshAttributeData: second view dimension is not contiguous"), nullptr)} {} template Containers::ArrayView MeshData::indices() const { Containers::StridedArrayView2D data = indices(); @@ -1535,66 +1680,110 @@ template Containers::ArrayView MeshData::mutableIndices() { return Containers::arrayCast<1, T>(data).asContiguous(); } -template Containers::StridedArrayView1D MeshData::attribute(UnsignedInt id) const { +#ifndef CORRADE_NO_ASSERT +template bool MeshData::checkAttributeTypeCompatibility(const MeshAttributeData& attribute, const char* const prefix) const { + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + prefix << "can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), false); + CORRADE_ASSERT(Implementation::isVertexFormatCompatible::type>(attribute._format), + prefix << "improper type requested for" << attribute._name << "of format" << attribute._format, false); + CORRADE_ASSERT(std::is_array::value == !!attribute._arraySize, + prefix << "use T[] to access an array attribute", false); + return true; +} +#endif + +template Containers::StridedArrayView1D MeshData::attribute(const UnsignedInt id) const { Containers::StridedArrayView2D data = attribute(id); #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - const MeshAttributeData& attribute = _attributes[id]; + if(!checkAttributeTypeCompatibility(_attributes[id], "Trade::MeshData::attribute():")) return {}; #endif - CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), - "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), {}); - CORRADE_ASSERT(Implementation::isVertexFormatCompatible(attribute._format), - "Trade::MeshData::attribute(): improper type requested for" << attribute._name << "of format" << attribute._format, nullptr); return Containers::arrayCast<1, const T>(data); } -template Containers::StridedArrayView1D MeshData::mutableAttribute(UnsignedInt id) { +template Containers::StridedArrayView2D::type> MeshData::attribute(const UnsignedInt id) const { + Containers::StridedArrayView2D data = attribute(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + const MeshAttributeData& attribute = _attributes[id]; + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(attribute, "Trade::MeshData::attribute():")) return {}; + #endif + return Containers::arrayCast<2, const typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D MeshData::mutableAttribute(const UnsignedInt id) { Containers::StridedArrayView2D data = mutableAttribute(id); #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - const MeshAttributeData& attribute = _attributes[id]; + if(!checkAttributeTypeCompatibility(_attributes[id], "Trade::MeshData::mutableAttribute():")) return {}; #endif - CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), - "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), {}); - CORRADE_ASSERT(Implementation::isVertexFormatCompatible(attribute._format), - "Trade::MeshData::mutableAttribute(): improper type requested for" << attribute._name << "of format" << attribute._format, nullptr); return Containers::arrayCast<1, T>(data); } -template Containers::StridedArrayView1D MeshData::attribute(MeshAttribute name, UnsignedInt id) const { +template Containers::StridedArrayView2D::type> MeshData::mutableAttribute(const UnsignedInt id) { + Containers::StridedArrayView2D data = mutableAttribute(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + const MeshAttributeData& attribute = _attributes[id]; + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(attribute, "Trade::MeshData::mutableAttribute():")) return {}; + #endif + return Containers::arrayCast<2, typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D MeshData::attribute(MeshAttribute name, UnsignedInt id) const { Containers::StridedArrayView2D data = attribute(name, id); #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; + if(!checkAttributeTypeCompatibility(_attributes[attributeFor(name, id)], "Trade::MeshData::attribute():")) return {}; #endif - CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), - "Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), {}); - CORRADE_ASSERT(Implementation::isVertexFormatCompatible(attribute._format), - "Trade::MeshData::attribute(): improper type requested for" << attribute._name << "of format" << attribute._format, nullptr); return Containers::arrayCast<1, const T>(data); } -template Containers::StridedArrayView1D MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { +template Containers::StridedArrayView2D::type> MeshData::attribute(MeshAttribute name, UnsignedInt id) const { + Containers::StridedArrayView2D data = attribute(name, id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(attribute, "Trade::MeshData::attribute():")) return {}; + #endif + return Containers::arrayCast<2, const typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { Containers::StridedArrayView2D data = mutableAttribute(name, id); #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; + if(!checkAttributeTypeCompatibility(_attributes[attributeFor(name, id)], "Trade::MeshData::mutableAttribute():")) return {}; #endif - CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), - "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), {}); - CORRADE_ASSERT(Implementation::isVertexFormatCompatible(attribute._format), - "Trade::MeshData::mutableAttribute(): improper type requested for" << attribute._name << "of format" << attribute._format, nullptr); return Containers::arrayCast<1, T>(data); } +template Containers::StridedArrayView2D::type> MeshData::mutableAttribute(MeshAttribute name, UnsignedInt id) { + Containers::StridedArrayView2D data = mutableAttribute(name, id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + const MeshAttributeData& attribute = _attributes[attributeFor(name, id)]; + #ifndef CORRADE_NO_ASSERT + if(!checkAttributeTypeCompatibility(attribute, "Trade::MeshData::mutableAttribute():")) return {}; + #endif + return Containers::arrayCast<2, typename std::remove_extent::type>(data); +} + }} #endif diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 0657703f32..a72aaee30e 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -65,6 +65,15 @@ struct MeshDataTest: TestSuite::Tester { void constructAttributeWrongStride(); void constructAttributeWrongDataAccess(); + void constructArrayAttribute(); + void constructArrayAttributeNonContiguous(); + void constructArrayAttribute2D(); + void constructArrayAttribute2DWrongSize(); + void constructArrayAttribute2DNonContiguous(); + void constructArrayAttributeTypeErased(); + void constructArrayAttributeOffsetOnly(); + void constructArrayAttributeNotAllowed(); + void construct(); void constructZeroIndices(); void constructZeroAttributes(); @@ -128,6 +137,9 @@ struct MeshDataTest: TestSuite::Tester { void implementationSpecificVertexFormatWrongAccess(); void implementationSpecificVertexFormatNotContained(); + void arrayAttribute(); + void arrayAttributeWrongAccess(); + void mutableAccessNotAllowed(); void indicesNotIndexed(); @@ -188,6 +200,15 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructAttributeWrongStride, &MeshDataTest::constructAttributeWrongDataAccess, + &MeshDataTest::constructArrayAttribute, + &MeshDataTest::constructArrayAttributeNonContiguous, + &MeshDataTest::constructArrayAttribute2D, + &MeshDataTest::constructArrayAttribute2DWrongSize, + &MeshDataTest::constructArrayAttribute2DNonContiguous, + &MeshDataTest::constructArrayAttributeTypeErased, + &MeshDataTest::constructArrayAttributeOffsetOnly, + &MeshDataTest::constructArrayAttributeNotAllowed, + &MeshDataTest::construct, &MeshDataTest::constructZeroIndices, &MeshDataTest::constructZeroAttributes, @@ -298,6 +319,9 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::implementationSpecificVertexFormatWrongAccess, &MeshDataTest::implementationSpecificVertexFormatNotContained, + &MeshDataTest::arrayAttribute, + &MeshDataTest::arrayAttributeWrongAccess, + &MeshDataTest::mutableAccessNotAllowed, &MeshDataTest::indicesNotIndexed, @@ -465,6 +489,7 @@ void MeshDataTest::constructAttribute() { const Vector2 positionData[3]; MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(positionData)}; CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_VERIFY(positions.data().data() == positionData); @@ -473,10 +498,12 @@ void MeshDataTest::constructAttribute() { constexpr MeshAttributeData cpositions{MeshAttribute::Position, Containers::arrayView(Positions)}; constexpr bool isOffsetOnly = cpositions.isOffsetOnly(); + constexpr UnsignedShort arraySize = cpositions.arraySize(); constexpr MeshAttribute name = cpositions.name(); constexpr VertexFormat format = cpositions.format(); constexpr Containers::StridedArrayView1D data = cpositions.data(); CORRADE_VERIFY(!isOffsetOnly); + CORRADE_COMPARE(arraySize, 0); CORRADE_COMPARE(name, MeshAttribute::Position); CORRADE_COMPARE(format, VertexFormat::Vector2); CORRADE_COMPARE(data.data(), Positions); @@ -497,6 +524,7 @@ void MeshDataTest::constructAttribute2D() { MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, positionView}; CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_COMPARE(positions.data().data(), positionView.data()); @@ -528,6 +556,7 @@ void MeshDataTest::constructAttributeTypeErased() { const Vector3 positionData[3]{}; MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, Containers::arrayCast(Containers::stridedArrayView(positionData))}; CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector3); CORRADE_VERIFY(positions.data().data() == positionData); @@ -536,6 +565,7 @@ void MeshDataTest::constructAttributeTypeErased() { void MeshDataTest::constructAttributeNullptr() { MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_VERIFY(!positions.data().data()); @@ -544,6 +574,7 @@ void MeshDataTest::constructAttributeNullptr() { void MeshDataTest::constructAttributePadding() { MeshAttributeData padding{-35}; CORRADE_VERIFY(!padding.isOffsetOnly()); + CORRADE_COMPARE(padding.arraySize(), 0); CORRADE_COMPARE(padding.name(), MeshAttribute{}); CORRADE_COMPARE(padding.format(), VertexFormat{}); CORRADE_COMPARE(padding.data().size(), 0); @@ -569,6 +600,7 @@ void MeshDataTest::constructAttributeOffsetOnly() { MeshAttributeData a{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)}; CORRADE_VERIFY(a.isOffsetOnly()); + CORRADE_COMPARE(a.arraySize(), 0); CORRADE_COMPARE(a.name(), MeshAttribute::TextureCoordinates); CORRADE_COMPARE(a.format(), VertexFormat::Vector2); CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), @@ -577,6 +609,7 @@ void MeshDataTest::constructAttributeOffsetOnly() { constexpr MeshAttributeData ca{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)}; CORRADE_VERIFY(ca.isOffsetOnly()); + CORRADE_COMPARE(ca.arraySize(), 0); CORRADE_COMPARE(ca.name(), MeshAttribute::TextureCoordinates); CORRADE_COMPARE(ca.format(), VertexFormat::Vector2); CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), @@ -642,6 +675,140 @@ void MeshDataTest::constructAttributeWrongDataAccess() { "Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array\n"); } +constexpr Vector2 ArrayVertexData[3*4]; + +void MeshDataTest::constructArrayAttribute() { + Vector2 vertexData[3*4]; + Containers::StridedArrayView2D attribute{vertexData, {3, 4}}; + MeshAttributeData data{meshAttributeCustom(35), attribute}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_VERIFY(data.data().data() == vertexData); + CORRADE_COMPARE(data.data().size(), 3); + CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); + + constexpr Containers::StridedArrayView2D cattribute{ArrayVertexData, {3, 4}}; + constexpr MeshAttributeData cdata{meshAttributeCustom(35), cattribute}; + CORRADE_VERIFY(!cdata.isOffsetOnly()); + CORRADE_COMPARE(cdata.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(cdata.format(), VertexFormat::Vector2); + CORRADE_COMPARE(cdata.arraySize(), 4); + CORRADE_VERIFY(cdata.data().data() == ArrayVertexData); + CORRADE_COMPARE(cdata.data().size(), 3); + CORRADE_COMPARE(cdata.data().stride(), sizeof(Vector2)*4); +} + +void MeshDataTest::constructArrayAttributeNonContiguous() { + Vector2 vertexData[4*3]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{meshAttributeCustom(35), + Containers::StridedArrayView2D{vertexData, + {4, 3}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension is not contiguous\n"); +} + +void MeshDataTest::constructArrayAttribute2D() { + char vertexData[3*4*sizeof(Vector2)]; + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, 4, Containers::StridedArrayView2D{vertexData, {3, 4*sizeof(Vector2)}}}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_VERIFY(data.data().data() == vertexData); + CORRADE_COMPARE(data.data().size(), 3); + CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); +} + +void MeshDataTest::constructArrayAttribute2DWrongSize() { + char vertexData[3*4*sizeof(Vector2)]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{meshAttributeCustom(35), VertexFormat::Vector2, 3, + Containers::StridedArrayView2D{vertexData, + {3, 4*sizeof(Vector2)}}}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension size 32 doesn't match VertexFormat::Vector2 and array size 3\n"); +} + +void MeshDataTest::constructArrayAttribute2DNonContiguous() { + char vertexData[4*3*sizeof(Vector2)]{}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{meshAttributeCustom(35), VertexFormat::Vector2, 2, + Containers::StridedArrayView2D{vertexData, + {3, sizeof(Vector2)*4}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), "Trade::MeshAttributeData: second view dimension is not contiguous\n"); +} + +void MeshDataTest::constructArrayAttributeTypeErased() { + Vector2 vertexData[3*4]; + Containers::StridedArrayView1D attribute{vertexData, 3, 4*sizeof(Vector2)}; + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, 4, attribute}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_VERIFY(data.data().data() == vertexData); + CORRADE_COMPARE(data.data().size(), 3); + CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); +} + +void MeshDataTest::constructArrayAttributeOffsetOnly() { + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, sizeof(Vector2), 3, sizeof(Vector2), 4}; + CORRADE_VERIFY(data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + + Vector2 vertexData[1 + 3*4]; + CORRADE_VERIFY(data.data(vertexData).data() == vertexData + 1); + CORRADE_COMPARE(data.data(vertexData).size(), 3); + CORRADE_COMPARE(data.data(vertexData).stride(), sizeof(Vector2)); + + constexpr MeshAttributeData cdata{meshAttributeCustom(35), VertexFormat::Vector2, sizeof(Vector2), 3, sizeof(Vector2), 4}; + CORRADE_VERIFY(cdata.isOffsetOnly()); + CORRADE_COMPARE(cdata.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(cdata.format(), VertexFormat::Vector2); + CORRADE_COMPARE(cdata.arraySize(), 4); +} + +void MeshDataTest::constructArrayAttributeNotAllowed() { + Vector2 positionData[3*3]; + Containers::ArrayView positions{positionData}; + Containers::StridedArrayView2D positions2D{positionData, {3, 3}}; + auto positions2Dchar = Containers::arrayCast<2, const char>(positions2D); + + /* This is all fine */ + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, positions}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, 3, 6*sizeof(Vector2), 0}; + MeshAttributeData{meshAttributeCustom(35), vertexFormatWrap(0xdead), 0, positions}; + MeshAttributeData{meshAttributeCustom(35), positions2D}; + MeshAttributeData{meshAttributeCustom(35), VertexFormat::Vector2, 3, positions2Dchar}; + MeshAttributeData{meshAttributeCustom(35), VertexFormat::Vector2, 0, 3, 6*sizeof(Vector2), 3}; + + /* This is not */ + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 3, Containers::arrayView(positionData)}; + MeshAttributeData{meshAttributeCustom(35), vertexFormatWrap(0xdead), 3, Containers::arrayView(positionData)}; + MeshAttributeData{MeshAttribute::Position, positions2D}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 3, positions2Dchar}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, 3, 6*sizeof(Vector2), 3}; + MeshAttributeData{meshAttributeCustom(35), vertexFormatWrap(0xdead), 0, 3, 6*sizeof(Vector2), 3}; + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData: Trade::MeshAttribute::Position can't be an array attribute\n" + "Trade::MeshAttributeData: array attributes can't have an implementation-specific format\n" + "Trade::MeshAttributeData: Trade::MeshAttribute::Position can't be an array attribute\n" + "Trade::MeshAttributeData: Trade::MeshAttribute::Position can't be an array attribute\n" + "Trade::MeshAttributeData: Trade::MeshAttribute::Position can't be an array attribute\n" + "Trade::MeshAttributeData: array attributes can't have an implementation-specific format\n"); +} + void MeshDataTest::construct() { struct Vertex { Vector3 position; @@ -740,6 +907,11 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attributeStride(1), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(2), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(3), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeArraySize(0), 0); + CORRADE_COMPARE(data.attributeArraySize(1), 0); + CORRADE_COMPARE(data.attributeArraySize(2), 0); + CORRADE_COMPARE(data.attributeArraySize(3), 0); + CORRADE_COMPARE(data.attributeArraySize(4), 0); /* Typeless access by ID with a cast later */ CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( @@ -817,6 +989,11 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 0), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 1), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(meshAttributeCustom(13)), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::Position), 0); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::Normal), 0); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::TextureCoordinates, 0), 0); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::TextureCoordinates, 1), 0); + CORRADE_COMPARE(data.attributeArraySize(meshAttributeCustom(13)), 0); /* Typeless access by name with a cast later */ CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( @@ -1996,6 +2173,99 @@ void MeshDataTest::implementationSpecificVertexFormatNotContained() { "Trade::MeshData: attribute 1 [0xdead:0xdeaf] is not contained in passed vertexData array [0xbadda9:0xbaddac]\n"); } +void MeshDataTest::arrayAttribute() { + Vector2 vertexData[3*4]{ + {1.0f, 2.0f}, {3.0f, 4.0f}, {5.0f, 6.0f}, {7.0f, 8.0f}, + {1.1f, 2.2f}, {3.3f, 4.4f}, {5.5f, 6.6f}, {7.7f, 8.8f}, + {0.1f, 0.2f}, {0.3f, 0.4f}, {0.5f, 0.6f}, {0.7f, 0.8f}, + }; + Containers::StridedArrayView2D positions2D{vertexData, {3, 4}}; + + MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { + MeshAttributeData{meshAttributeCustom(35), positions2D} + }}; + + CORRADE_COMPARE(data.vertexCount(), 3); + CORRADE_COMPARE(data.attributeArraySize(meshAttributeCustom(35)), 4); + + /* Raw access is "as usual" */ + auto attribute = Containers::arrayCast<2, const Vector2>(data.attribute(0)); + auto attributeByName = Containers::arrayCast<2, const Vector2>(data.attribute(meshAttributeCustom(35))); + auto mutableAttribute = Containers::arrayCast<2, Vector2>(data.mutableAttribute(0)); + auto mutableAttributeByName = Containers::arrayCast<2, Vector2>(data.mutableAttribute(meshAttributeCustom(35))); + CORRADE_COMPARE(attribute.size()[0], 3); + CORRADE_COMPARE(attributeByName.size()[0], 3); + CORRADE_COMPARE(mutableAttribute.size()[0], 3); + CORRADE_COMPARE(mutableAttributeByName.size()[0], 3); + for(std::size_t i = 0; i != 3; ++i) { + CORRADE_ITERATION(i); + CORRADE_COMPARE_AS(attribute[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(attributeByName[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mutableAttribute[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mutableAttributeByName[i], positions2D[i], + TestSuite::Compare::Container); + } + + /* Typed access */ + attribute = data.attribute(0); + attributeByName = data.attribute(meshAttributeCustom(35)); + mutableAttribute = data.mutableAttribute(0); + mutableAttributeByName = data.mutableAttribute(meshAttributeCustom(35)); + CORRADE_COMPARE(attribute.size()[0], 3); + CORRADE_COMPARE(attributeByName.size()[0], 3); + CORRADE_COMPARE(mutableAttribute.size()[0], 3); + CORRADE_COMPARE(mutableAttributeByName.size()[0], 3); + for(std::size_t i = 0; i != 3; ++i) { + CORRADE_ITERATION(i); + CORRADE_COMPARE_AS(attribute[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(attributeByName[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mutableAttribute[i], positions2D[i], + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(mutableAttributeByName[i], positions2D[i], + TestSuite::Compare::Container); + } +} + +void MeshDataTest::arrayAttributeWrongAccess() { + Vector2 vertexData[3*4]{ + {1.0f, 2.0f}, {3.0f, 4.0f}, {5.0f, 6.0f}, {7.0f, 8.0f}, + {1.1f, 2.2f}, {3.3f, 4.4f}, {5.5f, 6.6f}, {7.7f, 8.8f}, + {0.1f, 0.2f}, {0.3f, 0.4f}, {0.5f, 0.6f}, {0.7f, 0.8f}, + }; + Containers::StridedArrayView1D positions{vertexData, 3, 4*sizeof(Vector2)}; + Containers::StridedArrayView2D positions2D{vertexData, {3, 4}}; + + MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { + MeshAttributeData{MeshAttribute::Position, positions}, + MeshAttributeData{meshAttributeCustom(35), positions2D} + }}; + + std::ostringstream out; + Error redirectError{&out}; + data.attribute(0); + data.attribute(1); + data.mutableAttribute(0); + data.mutableAttribute(1); + data.attribute(MeshAttribute::Position); + data.attribute(meshAttributeCustom(35)); + data.mutableAttribute(MeshAttribute::Position); + data.mutableAttribute(meshAttributeCustom(35)); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::attribute(): use T[] to access an array attribute\n" + "Trade::MeshData::attribute(): use T[] to access an array attribute\n" + "Trade::MeshData::mutableAttribute(): use T[] to access an array attribute\n" + "Trade::MeshData::mutableAttribute(): use T[] to access an array attribute\n" + "Trade::MeshData::attribute(): use T[] to access an array attribute\n" + "Trade::MeshData::attribute(): use T[] to access an array attribute\n" + "Trade::MeshData::mutableAttribute(): use T[] to access an array attribute\n" + "Trade::MeshData::mutableAttribute(): use T[] to access an array attribute\n"); +} + void MeshDataTest::mutableAccessNotAllowed() { const UnsignedShort indexData[]{0, 1, 0}; const Vector2 vertexData[]{{0.1f, 0.2f}, {0.4f, 0.5f}}; @@ -2073,6 +2343,7 @@ void MeshDataTest::attributeNotFound() { data.attributeFormat(2); data.attributeOffset(2); data.attributeStride(2); + data.attributeArraySize(2); data.attribute(2); data.attribute(2); data.attributeId(MeshAttribute::Position); @@ -2083,6 +2354,8 @@ void MeshDataTest::attributeNotFound() { data.attributeOffset(MeshAttribute::Color, 2); data.attributeStride(MeshAttribute::Position); data.attributeStride(MeshAttribute::Color, 2); + data.attributeArraySize(MeshAttribute::Position); + data.attributeArraySize(MeshAttribute::Color, 2); data.attribute(MeshAttribute::Position); data.attribute(MeshAttribute::Color, 2); data.attribute(MeshAttribute::Position); @@ -2097,6 +2370,7 @@ void MeshDataTest::attributeNotFound() { "Trade::MeshData::attributeFormat(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attributeOffset(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attributeStride(): index 2 out of range for 2 attributes\n" + "Trade::MeshData::attributeArraySize(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 attributes\n" "Trade::MeshData::attributeId(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" @@ -2107,6 +2381,8 @@ void MeshDataTest::attributeNotFound() { "Trade::MeshData::attributeOffset(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" "Trade::MeshData::attributeStride(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attributeStride(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeArraySize(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" + "Trade::MeshData::attributeArraySize(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" From c9634508e3cd2a7aca427e9a81d7b1b674c54c13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 26 Feb 2020 18:25:55 +0100 Subject: [PATCH 083/107] Enlarge MeshPrimitive to four bytes, allow wrapping impl-specific values. And also handle them specially in GL::meshPrimitive() and Vk::vkPrimitiveTopology(). --- doc/changelog.dox | 7 ++++ src/Magnum/GL/Mesh.cpp | 17 +++++++++- src/Magnum/GL/Mesh.h | 21 ++++++++++++ src/Magnum/GL/Test/MeshTest.cpp | 19 ++++++++++- src/Magnum/Magnum.h | 2 +- src/Magnum/Mesh.cpp | 4 +++ src/Magnum/Mesh.h | 54 ++++++++++++++++++++++++++++-- src/Magnum/Test/CMakeLists.txt | 1 + src/Magnum/Test/MeshTest.cpp | 56 ++++++++++++++++++++++++++++++++ src/Magnum/Vk/Enums.cpp | 6 ++++ src/Magnum/Vk/Enums.h | 10 +++++- src/Magnum/Vk/Test/EnumsTest.cpp | 8 +++++ 12 files changed, 199 insertions(+), 6 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 12fd83728c..2c7aa5a5a0 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -196,6 +196,10 @@ See also: @subsection changelog-latest-changes Changes and improvements +- The @ref MeshPrimitive type can now store implementation-specific primitive + types similarly to @ref PixelFormat and the new @ref VertexFormat. + Implementation-specific types are then simply passed through in + @ref GL::meshPrimitive() and @ref Vk::vkPrimitiveTopology(). - The @ref PixelFormat and @ref CompressedPixelFormat enums can now be saved and retrieved from @ref Corrade::Utility::Configuration / @ref Corrade::Utility::Arguments @@ -468,6 +472,9 @@ See also: @ref Shaders::Phong::bindDiffuseTexture(), @ref Shaders::Phong::bindSpecularTexture() and @ref Shaders::Phong::bindTextures() instead +- @ref MeshPrimitive is now four bytes instead of one, to allow wrapping + implementation-specific values using @ref meshPrimitiveWrap() and + @ref meshPrimitiveUnwrap() - @ref MeshPrimitive and @ref MeshIndexType now reserve the zero value to indicate an invalid primitive / type, better catching accidentally forgotten initialization. Valid code shouldn't be affected by this change, diff --git a/src/Magnum/GL/Mesh.cpp b/src/Magnum/GL/Mesh.cpp index 729d407d78..80980bd5e7 100644 --- a/src/Magnum/GL/Mesh.cpp +++ b/src/Magnum/GL/Mesh.cpp @@ -65,10 +65,25 @@ constexpr MeshIndexType IndexTypeMapping[]{ } +bool hasMeshPrimitive(const Magnum::MeshPrimitive primitive) { + if(isMeshPrimitiveImplementationSpecific(primitive)) + return true; + + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveMapping), + "GL::hasPrimitive(): invalid primitive" << primitive, {}); + return UnsignedInt(PrimitiveMapping[UnsignedInt(primitive) - 1]) != ~UnsignedInt{}; +} + MeshPrimitive meshPrimitive(const Magnum::MeshPrimitive primitive) { + if(isMeshPrimitiveImplementationSpecific(primitive)) + return meshPrimitiveUnwrap(primitive); + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveMapping), "GL::meshPrimitive(): invalid primitive" << primitive, {}); - return PrimitiveMapping[UnsignedInt(primitive) - 1]; + const MeshPrimitive out = PrimitiveMapping[UnsignedInt(primitive) - 1]; + CORRADE_ASSERT(out != MeshPrimitive(~UnsignedInt{}), + "GL::meshPrimitive(): unsupported primitive" << primitive, {}); + return out; } MeshIndexType meshIndexType(const Magnum::MeshIndexType type) { diff --git a/src/Magnum/GL/Mesh.h b/src/Magnum/GL/Mesh.h index f3d230f991..9b953f0948 100644 --- a/src/Magnum/GL/Mesh.h +++ b/src/Magnum/GL/Mesh.h @@ -136,9 +136,30 @@ enum class MeshPrimitive: GLenum { #endif }; +/** +@brief Check availability of a generic mesh primitive +@m_since_latest + +Returns @cpp false @ce if OpenGL doesn't support such primitive, @cpp true @ce +otherwise. Moreover, returns @cpp true @ce also for all formats that are +@ref isMeshPrimitiveImplementationSpecific(). The @p primitive value is +expected to be valid. +@see @ref meshPrimitive() +*/ +MAGNUM_GL_EXPORT bool hasMeshPrimitive(Magnum::MeshPrimitive primitive); + /** @brief Convert generic mesh primitive to OpenGL mesh primitive +In case @ref isMeshPrimitiveImplementationSpecific() returns @cpp false @ce for +@p primitive, maps it to a corresponding OpenGL mesh primitive. In case +@ref isMeshPrimitiveImplementationSpecific() returns @cpp true @ce, assumes +@p primitive stores OpenGL-specific mesh primitive and returns +@ref meshPrimitiveUnwrap() cast to @ref GL::MeshPrimitive. + +Not all generic mesh primitives are available in OpenGL and this function +expects that given primitive is available. Use @ref hasMeshPrimitive() to +query availability of given primitive. @see @ref meshIndexType() */ MAGNUM_GL_EXPORT MeshPrimitive meshPrimitive(Magnum::MeshPrimitive primitive); diff --git a/src/Magnum/GL/Test/MeshTest.cpp b/src/Magnum/GL/Test/MeshTest.cpp index ec199c5272..2cbe8422d8 100644 --- a/src/Magnum/GL/Test/MeshTest.cpp +++ b/src/Magnum/GL/Test/MeshTest.cpp @@ -50,7 +50,10 @@ struct MeshTest: TestSuite::Tester { void drawViewCountNotSet(); void mapPrimitive(); + void mapPrimitiveImplementationSpecific(); + void mapPrimitiveUnsupported(); void mapPrimitiveInvalid(); + void mapIndexType(); void mapIndexTypeInvalid(); @@ -69,7 +72,10 @@ MeshTest::MeshTest() { &MeshTest::drawViewCountNotSet, &MeshTest::mapPrimitive, + &MeshTest::mapPrimitiveImplementationSpecific, + &MeshTest::mapPrimitiveUnsupported, &MeshTest::mapPrimitiveInvalid, + &MeshTest::mapIndexType, &MeshTest::mapIndexTypeInvalid, @@ -166,7 +172,8 @@ void MeshTest::mapPrimitive() { switch(primitive) { #define _c(primitive) \ case Magnum::MeshPrimitive::primitive: \ - CORRADE_VERIFY(UnsignedInt(meshPrimitive(Magnum::MeshPrimitive::primitive)) >= 0); \ + if(hasMeshPrimitive(Magnum::MeshPrimitive::primitive)) \ + CORRADE_VERIFY(UnsignedInt(meshPrimitive(Magnum::MeshPrimitive::primitive)) >= 0); \ break; #include "Magnum/Implementation/meshPrimitiveMapping.hpp" #undef _c @@ -177,6 +184,16 @@ void MeshTest::mapPrimitive() { } } +void MeshTest::mapPrimitiveImplementationSpecific() { + CORRADE_VERIFY(hasMeshPrimitive(meshPrimitiveWrap(GL_LINES))); + CORRADE_COMPARE(meshPrimitive(meshPrimitiveWrap(GL_LINES)), + MeshPrimitive::Lines); +} + +void MeshTest::mapPrimitiveUnsupported() { + CORRADE_SKIP("All primitive types are supported."); +} + void MeshTest::mapPrimitiveInvalid() { std::ostringstream out; Error redirectError{&out}; diff --git a/src/Magnum/Magnum.h b/src/Magnum/Magnum.h index 793e009202..8cfd87da3c 100644 --- a/src/Magnum/Magnum.h +++ b/src/Magnum/Magnum.h @@ -860,7 +860,7 @@ typedef BasicMutableCompressedImageView<1> MutableCompressedImageView1D; typedef BasicMutableCompressedImageView<2> MutableCompressedImageView2D; typedef BasicMutableCompressedImageView<3> MutableCompressedImageView3D; -enum class MeshPrimitive: UnsignedByte; +enum class MeshPrimitive: UnsignedInt; enum class MeshIndexType: UnsignedByte; enum class VertexFormat: UnsignedInt; diff --git a/src/Magnum/Mesh.cpp b/src/Magnum/Mesh.cpp index 65fa64b111..6ba49fed2d 100644 --- a/src/Magnum/Mesh.cpp +++ b/src/Magnum/Mesh.cpp @@ -56,6 +56,10 @@ constexpr const char* MeshPrimitiveNames[] { Debug& operator<<(Debug& debug, const MeshPrimitive value) { debug << "MeshPrimitive" << Debug::nospace; + if(isMeshPrimitiveImplementationSpecific(value)) { + return debug << "::ImplementationSpecific(" << Debug::nospace << reinterpret_cast(meshPrimitiveUnwrap(value)) << Debug::nospace << ")"; + } + if(UnsignedInt(value) - 1 < Containers::arraySize(MeshPrimitiveNames)) { return debug << "::" << Debug::nospace << MeshPrimitiveNames[UnsignedInt(value) - 1]; } diff --git a/src/Magnum/Mesh.h b/src/Magnum/Mesh.h index 6427117929..791ba69d66 100644 --- a/src/Magnum/Mesh.h +++ b/src/Magnum/Mesh.h @@ -26,9 +26,10 @@ */ /** @file - * @brief Enum @ref Magnum::MeshPrimitive, @ref Magnum::MeshIndexType, function @ref Magnum::meshIndexTypeSize() + * @brief Enum @ref Magnum::MeshPrimitive, @ref Magnum::MeshIndexType, function @ref Magnum::isMeshPrimitiveImplementationSpecific(), @ref Magnum::meshPrimitiveWrap(), @ref Magnum::meshPrimitiveUnwrap(), @ref Magnum::meshIndexTypeSize() */ +#include #include #include "Magnum/Magnum.h" @@ -39,6 +40,11 @@ namespace Magnum { /** @brief Mesh primitive type +Can act also as a wrapper for implementation-specific mesh primitive types +using @ref meshPrimitiveWrap() and @ref meshPrimitiveUnwrap(). Distinction +between generic and implementation-specific primitive types can be done using +@ref isMeshPrimitiveImplementationSpecific(). + In case of OpenGL, corresponds to @ref GL::MeshPrimitive and is convertible to it using @ref GL::meshPrimitive(). See documentation of each value for more information about the mapping. @@ -52,7 +58,7 @@ For D3D, corresponds to @m_class{m-doc-external} [D3D_PRIMITIVE_TOPOLOGY](https: for Metal, corresponds to @m_class{m-doc-external} [MTLPrimitiveType](https://developer.apple.com/documentation/metal/mtlprimitivetype?language=objc). See documentation of each value for more information about the mapping. */ -enum class MeshPrimitive: UnsignedByte { +enum class MeshPrimitive: UnsignedInt { /* Zero reserved for an invalid type (but not being a named value) */ /** @@ -135,6 +141,50 @@ enum class MeshPrimitive: UnsignedByte { /** @debugoperatorenum{MeshPrimitive} */ MAGNUM_EXPORT Debug& operator<<(Debug& debug, MeshPrimitive value); +/** +@brief Whether a @ref MeshPrimitive value wraps an implementation-specific identifier +@m_since_latest + +Returns @cpp true @ce if value of @p primitive has its highest bit set, +@cpp false @ce otherwise. Use @ref meshPrimitiveWrap() and @ref meshPrimitiveUnwrap() +to wrap/unwrap an implementation-specific indentifier to/from +@ref MeshPrimitive. +*/ +constexpr bool isMeshPrimitiveImplementationSpecific(MeshPrimitive primitive) { + return UnsignedInt(primitive) & (1u << 31); +} + +/** +@brief Wrap an implementation-specific mesh primitive identifier in @ref MeshPrimitive +@m_since_latest + +Sets the highest bit on @p primitive to mark it as implementation-specific. +Expects that @p primitive fits into the remaining bits. Use +@ref meshPrimitiveUnwrap() for the inverse operation. +@see @ref isMeshPrimitiveImplementationSpecific() +*/ +template constexpr MeshPrimitive meshPrimitiveWrap(T implementationSpecific) { + static_assert(sizeof(T) <= 4, "types larger than 32bits are not supported"); + return CORRADE_CONSTEXPR_ASSERT(!(UnsignedInt(implementationSpecific) & (1u << 31)), + "meshPrimitiveWrap(): implementation-specific value" << reinterpret_cast(implementationSpecific) << "already wrapped or too large"), + MeshPrimitive((1u << 31)|UnsignedInt(implementationSpecific)); +} + +/** +@brief Unwrap an implementation-specific mesh primitive identifier from @ref MeshPrimitive +@m_since_latest + +Unsets the highest bit from @p primitive to extract the implementation-specific +value. Expects that @p primitive has it set. Use @ref meshPrimitiveWrap() for +the inverse operation. +@see @ref isMeshPrimitiveImplementationSpecific() +*/ +template constexpr T meshPrimitiveUnwrap(MeshPrimitive primitive) { + return CORRADE_CONSTEXPR_ASSERT(UnsignedInt(primitive) & (1u << 31), + "meshPrimitiveUnwrap():" << primitive << "isn't a wrapped implementation-specific value"), + T(UnsignedInt(primitive) & ~(1u << 31)); +} + /** @brief Mesh index type diff --git a/src/Magnum/Test/CMakeLists.txt b/src/Magnum/Test/CMakeLists.txt index e7cd1e216a..53d29e0b40 100644 --- a/src/Magnum/Test/CMakeLists.txt +++ b/src/Magnum/Test/CMakeLists.txt @@ -49,6 +49,7 @@ set_target_properties( PROPERTIES FOLDER "Magnum/Test") set_property(TARGET + MeshTest PixelFormatTest ResourceManagerTest VertexFormatTest diff --git a/src/Magnum/Test/MeshTest.cpp b/src/Magnum/Test/MeshTest.cpp index 5a492aef80..10e2d9bf4b 100644 --- a/src/Magnum/Test/MeshTest.cpp +++ b/src/Magnum/Test/MeshTest.cpp @@ -39,10 +39,17 @@ struct MeshTest: TestSuite::Tester { void primitiveMapping(); void indexTypeMapping(); + void primitiveIsImplementationSpecific(); + void primitiveWrap(); + void primitiveWrapInvalid(); + void primitiveUnwrap(); + void primitiveUnwrapInvalid(); + void indexTypeSize(); void indexTypeSizeInvalid(); void debugPrimitive(); + void debugPrimitiveImplementationSpecific(); void debugIndexType(); void configurationPrimitive(); @@ -53,10 +60,17 @@ MeshTest::MeshTest() { addTests({&MeshTest::primitiveMapping, &MeshTest::indexTypeMapping, + &MeshTest::primitiveIsImplementationSpecific, + &MeshTest::primitiveWrap, + &MeshTest::primitiveWrapInvalid, + &MeshTest::primitiveUnwrap, + &MeshTest::primitiveUnwrapInvalid, + &MeshTest::indexTypeSize, &MeshTest::indexTypeSizeInvalid, &MeshTest::debugPrimitive, + &MeshTest::debugPrimitiveImplementationSpecific, &MeshTest::debugIndexType, &MeshTest::configurationPrimitive, @@ -139,6 +153,41 @@ void MeshTest::indexTypeMapping() { CORRADE_COMPARE(firstUnhandled, 0xff); } +void MeshTest::primitiveIsImplementationSpecific() { + constexpr bool a = isMeshPrimitiveImplementationSpecific(MeshPrimitive::Lines); + constexpr bool b = isMeshPrimitiveImplementationSpecific(MeshPrimitive(0x8000dead)); + CORRADE_VERIFY(!a); + CORRADE_VERIFY(b); +} + +void MeshTest::primitiveWrap() { + constexpr MeshPrimitive a = meshPrimitiveWrap(0xdead); + CORRADE_COMPARE(UnsignedInt(a), 0x8000dead); +} + +void MeshTest::primitiveWrapInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + meshPrimitiveWrap(0xdeadbeef); + + CORRADE_COMPARE(out.str(), "meshPrimitiveWrap(): implementation-specific value 0xdeadbeef already wrapped or too large\n"); +} + +void MeshTest::primitiveUnwrap() { + constexpr UnsignedInt a = meshPrimitiveUnwrap(MeshPrimitive(0x8000dead)); + CORRADE_COMPARE(a, 0xdead); +} + +void MeshTest::primitiveUnwrapInvalid() { + std::ostringstream out; + Error redirectError{&out}; + + meshPrimitiveUnwrap(MeshPrimitive::Triangles); + + CORRADE_COMPARE(out.str(), "meshPrimitiveUnwrap(): MeshPrimitive::Triangles isn't a wrapped implementation-specific value\n"); +} + void MeshTest::indexTypeSize() { CORRADE_COMPARE(meshIndexTypeSize(MeshIndexType::UnsignedByte), 1); CORRADE_COMPARE(meshIndexTypeSize(MeshIndexType::UnsignedShort), 2); @@ -163,6 +212,13 @@ void MeshTest::debugPrimitive() { CORRADE_COMPARE(o.str(), "MeshPrimitive::TriangleFan MeshPrimitive(0xfe)\n"); } +void MeshTest::debugPrimitiveImplementationSpecific() { + std::ostringstream out; + Debug{&out} << meshPrimitiveWrap(0xdead); + + CORRADE_COMPARE(out.str(), "MeshPrimitive::ImplementationSpecific(0xdead)\n"); +} + void MeshTest::debugIndexType() { std::ostringstream o; Debug(&o) << MeshIndexType::UnsignedShort << MeshIndexType(0xfe); diff --git a/src/Magnum/Vk/Enums.cpp b/src/Magnum/Vk/Enums.cpp index 47e236dd33..cacdb8953f 100644 --- a/src/Magnum/Vk/Enums.cpp +++ b/src/Magnum/Vk/Enums.cpp @@ -102,12 +102,18 @@ constexpr VkSamplerAddressMode SamplerAddressModeMapping[]{ } bool hasVkPrimitiveTopology(const Magnum::MeshPrimitive primitive) { + if(isMeshPrimitiveImplementationSpecific(primitive)) + return true; + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveTopologyMapping), "Vk::hasVkPrimitiveTopology(): invalid primitive" << primitive, {}); return UnsignedInt(PrimitiveTopologyMapping[UnsignedInt(primitive) - 1]) != ~UnsignedInt{}; } VkPrimitiveTopology vkPrimitiveTopology(const Magnum::MeshPrimitive primitive) { + if(isMeshPrimitiveImplementationSpecific(primitive)) + return meshPrimitiveUnwrap(primitive); + CORRADE_ASSERT(UnsignedInt(primitive) - 1 < Containers::arraySize(PrimitiveTopologyMapping), "Vk::vkPrimitiveTopology(): invalid primitive" << primitive, {}); const VkPrimitiveTopology out = PrimitiveTopologyMapping[UnsignedInt(primitive) - 1]; diff --git a/src/Magnum/Vk/Enums.h b/src/Magnum/Vk/Enums.h index 39f4ed7f06..230fd0aaea 100644 --- a/src/Magnum/Vk/Enums.h +++ b/src/Magnum/Vk/Enums.h @@ -40,7 +40,9 @@ namespace Magnum { namespace Vk { In particular, Vulkan doesn't support the @ref MeshPrimitive::LineLoop primitive. Returns @cpp false @ce if Vulkan doesn't support such primitive, -@cpp true @ce otherwise. The @p primitive value is expected to be valid. +@cpp true @ce otherwise. Moreover, returns @cpp true @ce also for all types +that are @ref isMeshPrimitiveImplementationSpecific(). The @p primitive value +is expected to be valid. @see @ref vkPrimitiveTopology() */ MAGNUM_VK_EXPORT bool hasVkPrimitiveTopology(Magnum::MeshPrimitive primitive); @@ -48,6 +50,12 @@ MAGNUM_VK_EXPORT bool hasVkPrimitiveTopology(Magnum::MeshPrimitive primitive); /** @brief Convert generic mesh primitive to Vulkan primitive topology +In case @ref isMeshPrimitiveImplementationSpecific() returns @cpp false @ce for +@p primitive, maps it to a corresponding Vulkan primitive topology. In case +@ref isMeshPrimitiveImplementationSpecific() returns @cpp true @ce, assumes +@p primitive stores a Vulkan-specific primitive topology and returns +@ref meshPrimitiveUnwrap() cast to @type_vk{VkPrimitiveTopology}. + Not all generic mesh primitives are available in Vulkan and this function expects that given primitive is available. Use @ref hasVkPrimitiveTopology() to query availability of given primitive. diff --git a/src/Magnum/Vk/Test/EnumsTest.cpp b/src/Magnum/Vk/Test/EnumsTest.cpp index baea77c7d0..ee33252f9b 100644 --- a/src/Magnum/Vk/Test/EnumsTest.cpp +++ b/src/Magnum/Vk/Test/EnumsTest.cpp @@ -39,6 +39,7 @@ struct EnumsTest: TestSuite::Tester { explicit EnumsTest(); void mapVkPrimitiveTopology(); + void mapVkPrimitiveTopologyImplementationSpecific(); void mapVkPrimitiveTopologyUnsupported(); void mapVkPrimitiveTopologyInvalid(); @@ -75,6 +76,7 @@ struct EnumsTest: TestSuite::Tester { EnumsTest::EnumsTest() { addTests({&EnumsTest::mapVkPrimitiveTopology, + &EnumsTest::mapVkPrimitiveTopologyImplementationSpecific, &EnumsTest::mapVkPrimitiveTopologyUnsupported, &EnumsTest::mapVkPrimitiveTopologyInvalid, @@ -152,6 +154,12 @@ void EnumsTest::mapVkPrimitiveTopology() { } } +void EnumsTest::mapVkPrimitiveTopologyImplementationSpecific() { + CORRADE_VERIFY(hasVkPrimitiveTopology(meshPrimitiveWrap(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY))); + CORRADE_COMPARE(vkPrimitiveTopology(meshPrimitiveWrap(VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY)), + VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY); +} + void EnumsTest::mapVkPrimitiveTopologyUnsupported() { CORRADE_VERIFY(!hasVkPrimitiveTopology(Magnum::MeshPrimitive::LineLoop)); From d096aa68cc239f745043ba061e4126964624140a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 26 Feb 2020 18:13:21 +0100 Subject: [PATCH 084/107] Add MeshPrimitive::Instances, Faces and Edges. To support meshes that are not so GPU-friendly. And also meshlets at a later point. --- doc/changelog.dox | 5 +++ src/Magnum/GL/Mesh.cpp | 5 ++- src/Magnum/GL/Test/MeshTest.cpp | 5 ++- .../Implementation/meshPrimitiveMapping.hpp | 3 ++ src/Magnum/Mesh.h | 43 ++++++++++++++++++- src/Magnum/Vk/Enums.cpp | 5 ++- 6 files changed, 62 insertions(+), 4 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 2c7aa5a5a0..7c942f5862 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -50,6 +50,11 @@ See also: @ref Color3us, @ref Color4us convenience typedefs for half-float, 8- and 16-bit integer vector and color types - New @ref VertexFormat enum for vertex formats and related utilities +- New @ref MeshPrimitive::Instances, @ref MeshPrimitive::Faces and + @ref MeshPrimitive::Edges primitive types for describing per-instance, + per-face and per-edge data. Those don't map to any common GPU API, but can + be used by various importers to provide access to mesh data that is not + necessarily GPU-friendly. @subsubsection changelog-latest-new-audio Audio library diff --git a/src/Magnum/GL/Mesh.cpp b/src/Magnum/GL/Mesh.cpp index 80980bd5e7..d267784349 100644 --- a/src/Magnum/GL/Mesh.cpp +++ b/src/Magnum/GL/Mesh.cpp @@ -54,7 +54,10 @@ constexpr MeshPrimitive PrimitiveMapping[]{ MeshPrimitive::LineStrip, MeshPrimitive::Triangles, MeshPrimitive::TriangleStrip, - MeshPrimitive::TriangleFan + MeshPrimitive::TriangleFan, + MeshPrimitive(~UnsignedInt{}), /* Instances */ + MeshPrimitive(~UnsignedInt{}), /* Faces */ + MeshPrimitive(~UnsignedInt{}) /* Edges */ }; constexpr MeshIndexType IndexTypeMapping[]{ diff --git a/src/Magnum/GL/Test/MeshTest.cpp b/src/Magnum/GL/Test/MeshTest.cpp index 2cbe8422d8..9569b6fcbd 100644 --- a/src/Magnum/GL/Test/MeshTest.cpp +++ b/src/Magnum/GL/Test/MeshTest.cpp @@ -191,7 +191,10 @@ void MeshTest::mapPrimitiveImplementationSpecific() { } void MeshTest::mapPrimitiveUnsupported() { - CORRADE_SKIP("All primitive types are supported."); + std::ostringstream out; + Error redirectError{&out}; + meshPrimitive(Magnum::MeshPrimitive::Instances); + CORRADE_COMPARE(out.str(), "GL::meshPrimitive(): unsupported primitive MeshPrimitive::Instances\n"); } void MeshTest::mapPrimitiveInvalid() { diff --git a/src/Magnum/Implementation/meshPrimitiveMapping.hpp b/src/Magnum/Implementation/meshPrimitiveMapping.hpp index 0f503c8a47..5275a0ead6 100644 --- a/src/Magnum/Implementation/meshPrimitiveMapping.hpp +++ b/src/Magnum/Implementation/meshPrimitiveMapping.hpp @@ -32,4 +32,7 @@ _c(LineStrip) _c(Triangles) _c(TriangleStrip) _c(TriangleFan) +_c(Instances) +_c(Faces) +_c(Edges) #endif diff --git a/src/Magnum/Mesh.h b/src/Magnum/Mesh.h index 791ba69d66..4e80165726 100644 --- a/src/Magnum/Mesh.h +++ b/src/Magnum/Mesh.h @@ -135,7 +135,48 @@ enum class MeshPrimitive: UnsignedInt { * @def_vk_keyword{PRIMITIVE_TOPOLOGY_TRIANGLE_FAN,PrimitiveTopology}. Not * supported on D3D or Metal. */ - TriangleFan + TriangleFan, + + /** + * Per-instance data. + * @m_since_latest + * + * Has no direct mapping to GPU APIs, but can be used to annotate + * @ref Trade::MeshData containing per-instance data (such as colors, + * transformations or texture layers) and then used to populate an instance + * buffer. Index buffer has no defined meaning for instance data. + */ + Instances, + + /** + * Per-face data. + * @m_since_latest + * + * Can be used to annotate @ref Trade::MeshData containing data that are + * per-face, as opposed to per-vertex. Has no direct mapping to common GPU + * APIs, there it either has to be converted to per-vertex (which usually + * involves slightly duplicating the original per-vertex data) or accessed + * via a direct buffer/texture fetch from a shader using e.g. + * @glsl gl_VertexID @ce. Index buffer can be used to deduplicate per-face + * data. + */ + Faces, + + /** + * Per-edge data. + * @m_since_latest + * + * Can be used to annotate @ref Trade::MeshData containing data that are + * per-edge, as opposed to per-vertex. This is different from + * @ref MeshPrimitive::Lines as it has just one entry per line segment, + * instead of two. Has no direct mapping to common GPU APIs, there it has + * to be converted to per-vertex (which usually involves slightly + * duplicating the original per-vertex data). Index buffer can be used to + * deduplicate per-face data. Can also be used for example to describe a + * half-edge mesh representation. + * @see @ref Trade::meshAttributeCustom() + */ + Edges }; /** @debugoperatorenum{MeshPrimitive} */ diff --git a/src/Magnum/Vk/Enums.cpp b/src/Magnum/Vk/Enums.cpp index cacdb8953f..edfdff101a 100644 --- a/src/Magnum/Vk/Enums.cpp +++ b/src/Magnum/Vk/Enums.cpp @@ -43,7 +43,10 @@ constexpr VkPrimitiveTopology PrimitiveTopologyMapping[]{ VK_PRIMITIVE_TOPOLOGY_LINE_STRIP, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP, - VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN, + VkPrimitiveTopology(~UnsignedInt{}), /* Instances */ + VkPrimitiveTopology(~UnsignedInt{}), /* Faces */ + VkPrimitiveTopology(~UnsignedInt{}) /* Edges */ }; constexpr VkIndexType IndexTypeMapping[]{ From 9425c23d0a38c503d2fb48178ddd17aad1ec0843 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 26 Feb 2020 18:19:14 +0100 Subject: [PATCH 085/107] Trade: support mesh level import in AbstractImporter. Similar to image mip level import, but this is largely left to be importer-specific. For example PLY defines per-face data and sometimes one might want to import them as-is, without them being turned into a per-vertex property. --- src/Magnum/Trade/AbstractImporter.cpp | 33 ++++- src/Magnum/Trade/AbstractImporter.h | 55 ++++++-- .../Trade/Test/AbstractImporterTest.cpp | 120 ++++++++++++++++-- 3 files changed, 179 insertions(+), 29 deletions(-) diff --git a/src/Magnum/Trade/AbstractImporter.cpp b/src/Magnum/Trade/AbstractImporter.cpp index 5606fe0841..3486743e63 100644 --- a/src/Magnum/Trade/AbstractImporter.cpp +++ b/src/Magnum/Trade/AbstractImporter.cpp @@ -462,6 +462,16 @@ UnsignedInt AbstractImporter::meshCount() const { UnsignedInt AbstractImporter::doMeshCount() const { return 0; } +UnsignedInt AbstractImporter::meshLevelCount(const UnsignedInt id) { + CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshLevelCount(): no file opened", {}); + CORRADE_ASSERT(id < doMeshCount(), "Trade::AbstractImporter::meshLevelCount(): index" << id << "out of range for" << doMeshCount() << "entries", {}); + const UnsignedInt out = doMeshLevelCount(id); + CORRADE_ASSERT(out, "Trade::AbstractImporter::meshLevelCount(): implementation reported zero levels", {}); + return out; +} + +UnsignedInt AbstractImporter::doMeshLevelCount(UnsignedInt) { return 1; } + Int AbstractImporter::meshForName(const std::string& name) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::meshForName(): no file opened", {}); return doMeshForName(name); @@ -477,10 +487,21 @@ std::string AbstractImporter::meshName(const UnsignedInt id) { std::string AbstractImporter::doMeshName(UnsignedInt) { return {}; } -Containers::Optional AbstractImporter::mesh(const UnsignedInt id) { +Containers::Optional AbstractImporter::mesh(const UnsignedInt id, const UnsignedInt level) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh(): no file opened", {}); CORRADE_ASSERT(id < doMeshCount(), "Trade::AbstractImporter::mesh(): index" << id << "out of range for" << doMeshCount() << "entries", {}); - Containers::Optional mesh = doMesh(id); + #ifndef CORRADE_NO_ASSERT + /* Check for the range only if requested level is nonzero, as + meshLevelCount() is expected to return >= 1. This is done to prevent + random assertions and messages from a doMeshLevelCount() to be printed + (which are unlikely, but let's be consistent with what image*D() does). */ + if(level) { + const UnsignedInt levelCount = doMeshLevelCount(id); + CORRADE_ASSERT(levelCount, "Trade::AbstractImporter::mesh(): implementation reported zero levels", {}); + CORRADE_ASSERT(level < levelCount, "Trade::AbstractImporter::mesh(): level" << level << "out of range for" << levelCount << "entries", {}); + } + #endif + Containers::Optional mesh = doMesh(id, level); CORRADE_ASSERT(!mesh || ( (!mesh->_indexData.deleter() || mesh->_indexData.deleter() == Implementation::nonOwnedArrayDeleter || mesh->_indexData.deleter() == ArrayAllocator::deleter) && (!mesh->_vertexData.deleter() || mesh->_vertexData.deleter() == Implementation::nonOwnedArrayDeleter || mesh->_vertexData.deleter() == ArrayAllocator::deleter) && @@ -489,15 +510,15 @@ Containers::Optional AbstractImporter::mesh(const UnsignedInt id) { return mesh; } -Containers::Optional AbstractImporter::doMesh(UnsignedInt) { +Containers::Optional AbstractImporter::doMesh(UnsignedInt, UnsignedInt) { CORRADE_ASSERT(false, "Trade::AbstractImporter::mesh(): not implemented", {}); } -Containers::Optional AbstractImporter::mesh(const std::string& name) { +Containers::Optional AbstractImporter::mesh(const std::string& name, const UnsignedInt level) { CORRADE_ASSERT(isOpened(), "Trade::AbstractImporter::mesh(): no file opened", {}); const Int id = doMeshForName(name); if(id == -1) return {}; - return mesh(id); /* not doMesh(), so we get the checks also */ + return mesh(id, level); /* not doMesh(), so we get the checks also */ } MeshAttribute AbstractImporter::meshAttributeForName(const std::string& name) { @@ -602,7 +623,7 @@ Containers::Optional AbstractImporter::mesh3D(const UnsignedInt id) } Containers::Optional AbstractImporter::doMesh3D(const UnsignedInt id) { - Containers::Optional out = doMesh(id); + Containers::Optional out = doMesh(id, 0); if(out) return MeshData3D{*out}; return Containers::NullOpt; } diff --git a/src/Magnum/Trade/AbstractImporter.h b/src/Magnum/Trade/AbstractImporter.h index 58ccdb742d..8e0fe8881e 100644 --- a/src/Magnum/Trade/AbstractImporter.h +++ b/src/Magnum/Trade/AbstractImporter.h @@ -245,9 +245,9 @@ checked by the implementation: there is any file opened. - All `do*()` implementations taking data ID as parameter are called only if the ID is from valid range. -- For `doImage*()` and @p level parameter being nonzero, implementations are - called only if it is from valid range. Level zero is always expected to be - present and thus no check is done in that case. +- For @ref doMesh() and `doImage*()` and @p level parameter being nonzero, + implementations are called only if it is from valid range. Level zero is + always expected to be present and thus no check is done in that case. @m_class{m-block m-warning} @@ -760,16 +760,27 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @m_since_latest * * Expects that a file is opened. + * @see @ref meshLevelCount() */ UnsignedInt meshCount() const; + /** + * @brief Mesh level count + * @param id Mesh ID, from range [0, @ref meshCount()). + * @m_since_latest + * + * Always returns at least one level, import failures are deferred to + * @ref mesh(). Expects that a file is opened. + */ + UnsignedInt meshLevelCount(UnsignedInt id); + /** * @brief Mesh ID for given name * @m_since_latest * * If no mesh for given name exists, returns @cpp -1 @ce. Expects that * a file is opened. - * @see @ref meshName(), @ref mesh(const std::string&) + * @see @ref meshName(), @ref mesh(const std::string&, UnsignedInt) */ Int meshForName(const std::string& name); @@ -786,24 +797,30 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi /** * @brief Mesh * @param id Mesh ID, from range [0, @ref meshCount()). + * @param level Mesh level, from range [0, @ref meshLevelCount()) * @m_since_latest * * Returns given mesh or @ref Containers::NullOpt if importing failed. - * Expects that a file is opened. - * @see @ref mesh(const std::string&) + * The @p level parameter allows access to additional data and is + * largely left as importer-specific --- for example allowing access to + * per-instance, per-face or per-edge data. Expects that a file is + * opened. + * @see @ref mesh(const std::string&, UnsignedInt), + * @ref MeshPrimitive::Instances, @ref MeshPrimitive::Faces, + * @ref MeshPrimitive::Edges */ - Containers::Optional mesh(UnsignedInt id); + Containers::Optional mesh(UnsignedInt id, UnsignedInt level = 0); /** * @brief Mesh for given name * @m_since_latest * * A convenience API combining @ref meshForName() and - * @ref mesh(UnsignedInt). Returns @ref Containers::NullOpt either if - * @ref meshForName() returns @cpp -1 @ce or if importing fails. - * Expects that a file is opened. + * @ref mesh(UnsignedInt, UnsignedInt). Returns + * @ref Containers::NullOpt either if @ref meshForName() returns + * @cpp -1 @ce or if importing fails. Expects that a file is opened. */ - Containers::Optional mesh(const std::string& name); + Containers::Optional mesh(const std::string& name, UnsignedInt level = 0); /** * @brief Mesh attribute for given name @@ -1403,6 +1420,20 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi */ virtual UnsignedInt doMeshCount() const; + /** + * @brief Implementation for @ref meshLevelCount() + * @m_since_latest + * + * Default implementation returns @cpp 1 @ce. Similarly to all other + * `*Count()` functions, this function isn't expected to fail --- if an + * import error occus, this function should return @cpp 1 @ce and the + * error state should be returned from @ref mesh() instead. + * + * Deliberately not @cpp const @ce to allow plugins cache decoded + * data. + */ + virtual UnsignedInt doMeshLevelCount(UnsignedInt id); + /** * @brief Implementation for @ref meshForName() * @m_since_latest @@ -1423,7 +1454,7 @@ class MAGNUM_TRADE_EXPORT AbstractImporter: public PluginManager::AbstractManagi * @brief Implementation for @ref mesh() * @m_since_latest */ - virtual Containers::Optional doMesh(UnsignedInt id); + virtual Containers::Optional doMesh(UnsignedInt id, UnsignedInt level); /** * @brief Implementation for @ref meshAttributeForName() diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index ed02d1a9e8..7c948f2bf4 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -171,6 +171,10 @@ struct AbstractImporterTest: TestSuite::Tester { #endif void meshCountNotImplemented(); void meshCountNoFile(); + void meshLevelCountNotImplemented(); + void meshLevelCountNoFile(); + void meshLevelCountOutOfRange(); + void meshLevelCountZero(); void meshForNameNotImplemented(); void meshForNameNoFile(); void meshNameNotImplemented(); @@ -179,6 +183,7 @@ struct AbstractImporterTest: TestSuite::Tester { void meshNotImplemented(); void meshNoFile(); void meshOutOfRange(); + void meshLevelOutOfRange(); void meshNonOwningDeleters(); void meshGrowableDeleters(); void meshCustomIndexDataDeleter(); @@ -420,6 +425,10 @@ AbstractImporterTest::AbstractImporterTest() { #endif &AbstractImporterTest::meshCountNotImplemented, &AbstractImporterTest::meshCountNoFile, + &AbstractImporterTest::meshLevelCountNotImplemented, + &AbstractImporterTest::meshLevelCountNoFile, + &AbstractImporterTest::meshLevelCountOutOfRange, + &AbstractImporterTest::meshLevelCountZero, &AbstractImporterTest::meshForNameNotImplemented, &AbstractImporterTest::meshForNameNoFile, &AbstractImporterTest::meshNameNotImplemented, @@ -428,6 +437,7 @@ AbstractImporterTest::AbstractImporterTest() { &AbstractImporterTest::meshNotImplemented, &AbstractImporterTest::meshNoFile, &AbstractImporterTest::meshOutOfRange, + &AbstractImporterTest::meshLevelOutOfRange, &AbstractImporterTest::meshNonOwningDeleters, &AbstractImporterTest::meshGrowableDeleters, &AbstractImporterTest::meshCustomIndexDataDeleter, @@ -2239,6 +2249,10 @@ void AbstractImporterTest::mesh() { void doClose() override {} UnsignedInt doMeshCount() const override { return 8; } + UnsignedInt doMeshLevelCount(UnsignedInt id) override { + if(id == 7) return 3; + else return {}; + } Int doMeshForName(const std::string& name) override { if(name == "eighth") return 7; else return -1; @@ -2247,10 +2261,10 @@ void AbstractImporterTest::mesh() { if(id == 7) return "eighth"; else return {}; } - Containers::Optional doMesh(UnsignedInt id) override { + Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override { /* Verify that initializer list is converted to an array with the default deleter and not something disallowed */ - if(id == 7) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, &state}; + if(id == 7 && level == 2) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, &state}; else return {}; } } importer; @@ -2260,11 +2274,11 @@ void AbstractImporterTest::mesh() { CORRADE_COMPARE(importer.meshName(7), "eighth"); { - auto data = importer.mesh(7); + auto data = importer.mesh(7, 2); CORRADE_VERIFY(data); CORRADE_COMPARE(data->importerState(), &state); } { - auto data = importer.mesh("eighth"); + auto data = importer.mesh("eighth", 2); CORRADE_VERIFY(data); CORRADE_COMPARE(data->importerState(), &state); } { @@ -2289,8 +2303,8 @@ void AbstractImporterTest::meshDeprecatedFallback() { if(id == 7) return "eighth"; else return {}; } - Containers::Optional doMesh(UnsignedInt id) override { - if(id == 7) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, &state}; + Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override { + if(id == 7 && level == 0) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, &state}; else return {}; } } importer; @@ -2336,6 +2350,70 @@ void AbstractImporterTest::meshCountNoFile() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshCount(): no file opened\n"); } +void AbstractImporterTest::meshLevelCountNotImplemented() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + CORRADE_COMPARE(importer.meshLevelCount(7), 1); +} + +void AbstractImporterTest::meshLevelCountNoFile() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return false; } + void doClose() override {} + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.meshLevelCount(7); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshLevelCount(): no file opened\n"); +} + +void AbstractImporterTest::meshLevelCountOutOfRange() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.meshLevelCount(8); + CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::meshLevelCount(): index 8 out of range for 8 entries\n"); +} + +void AbstractImporterTest::meshLevelCountZero() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + Int doMeshForName(const std::string&) override { return 0; } + UnsignedInt doMeshLevelCount(UnsignedInt) override { return 0; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.meshLevelCount(7); + /* This should print a similar message instead of a confusing + "level 1 out of range for 0 entries" */ + importer.mesh(7, 1); + importer.mesh("", 1); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::meshLevelCount(): implementation reported zero levels\n" + "Trade::AbstractImporter::mesh(): implementation reported zero levels\n" + "Trade::AbstractImporter::mesh(): implementation reported zero levels\n"); +} + void AbstractImporterTest::meshForNameNotImplemented() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -2451,6 +2529,26 @@ void AbstractImporterTest::meshOutOfRange() { CORRADE_COMPARE(out.str(), "Trade::AbstractImporter::mesh(): index 8 out of range for 8 entries\n"); } +void AbstractImporterTest::meshLevelOutOfRange() { + struct: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return true; } + void doClose() override {} + + UnsignedInt doMeshCount() const override { return 8; } + Int doMeshForName(const std::string&) override { return 0; } + UnsignedInt doMeshLevelCount(UnsignedInt) override { return 3; } + } importer; + + std::ostringstream out; + Error redirectError{&out}; + importer.mesh(7, 3); + importer.mesh("", 3); + CORRADE_COMPARE(out.str(), + "Trade::AbstractImporter::mesh(): level 3 out of range for 3 entries\n" + "Trade::AbstractImporter::mesh(): level 3 out of range for 3 entries\n"); +} + void AbstractImporterTest::meshNonOwningDeleters() { struct: AbstractImporter { ImporterFeatures doFeatures() const override { return {}; } @@ -2458,7 +2556,7 @@ void AbstractImporterTest::meshNonOwningDeleters() { void doClose() override {} UnsignedInt doMeshCount() const override { return 1; } - Containers::Optional doMesh(UnsignedInt) override { + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { return MeshData{MeshPrimitive::Triangles, Containers::Array{indexData, 1, Implementation::nonOwnedArrayDeleter}, MeshIndexData{MeshIndexType::UnsignedByte, indexData}, Containers::Array{nullptr, 0, Implementation::nonOwnedArrayDeleter}, @@ -2483,7 +2581,7 @@ void AbstractImporterTest::meshGrowableDeleters() { void doClose() override {} UnsignedInt doMeshCount() const override { return 1; } - Containers::Optional doMesh(UnsignedInt) override { + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { Containers::Array indexData; Containers::arrayAppend(indexData, '\xab'); Containers::Array vertexData; @@ -2511,7 +2609,7 @@ void AbstractImporterTest::meshCustomIndexDataDeleter() { UnsignedInt doMeshCount() const override { return 1; } Int doMeshForName(const std::string&) override { return 0; } - Containers::Optional doMesh(UnsignedInt) override { + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { return MeshData{MeshPrimitive::Triangles, Containers::Array{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}}; } @@ -2536,7 +2634,7 @@ void AbstractImporterTest::meshCustomVertexDataDeleter() { UnsignedInt doMeshCount() const override { return 1; } Int doMeshForName(const std::string&) override { return 0; } - Containers::Optional doMesh(UnsignedInt) override { + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { return MeshData{MeshPrimitive::Triangles, Containers::Array{nullptr, 0, [](char*, std::size_t) {}}, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}}; } } importer; @@ -2559,7 +2657,7 @@ void AbstractImporterTest::meshCustomAttributesDeleter() { UnsignedInt doMeshCount() const override { return 1; } Int doMeshForName(const std::string&) override { return 0; } - Containers::Optional doMesh(UnsignedInt) override { + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { return MeshData{MeshPrimitive::Triangles, nullptr, Containers::Array{&positions, 1, [](MeshAttributeData*, std::size_t) {}}}; } From 8f5639e3859b69461bd3e50bc83536b042e5a603 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 27 Feb 2020 01:24:14 +0100 Subject: [PATCH 086/107] MeshTools: added removeDuplicatesInPlaceInto(). I need to put the resulting index array into a pre-existing allocation. --- src/Magnum/MeshTools/RemoveDuplicates.cpp | 19 +++++++++++++------ src/Magnum/MeshTools/RemoveDuplicates.h | 13 +++++++++++++ .../MeshTools/Test/RemoveDuplicatesTest.cpp | 16 +++++++++++++++- 3 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/Magnum/MeshTools/RemoveDuplicates.cpp b/src/Magnum/MeshTools/RemoveDuplicates.cpp index 6f7a526a6d..52f30afcbe 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.cpp +++ b/src/Magnum/MeshTools/RemoveDuplicates.cpp @@ -44,20 +44,21 @@ struct ArrayHash { } }; -std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView2D& data) { +std::size_t removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices) { /* Assuming the second dimension is contiguous so we can calculate the hashes easily */ CORRADE_ASSERT(data.empty()[0] || data.isContiguous<1>(), - "MeshTools::removeDuplicatesInPlace(): second data view dimension is not contiguous", {}); + "MeshTools::removeDuplicatesInPlaceInto(): second data view dimension is not contiguous", {}); const std::size_t dataSize = data.size()[0]; + CORRADE_ASSERT(indices.size() == dataSize, + "MeshTools::removeDuplicatesInPlaceInto(): output index array has" << indices.size() << "elements but expected" << dataSize, {}); + /* Table containing index of first occurence for each unique entry. Reserving more buckets than necessary (i.e. as if each entry was unique). */ std::unordered_map, UnsignedInt, ArrayHash, ArrayEqual> table{dataSize}; - Containers::Array remapping{Containers::NoInit, dataSize}; - /* Go through all entries */ for(std::size_t i = 0; i != dataSize; ++i) { /* Try to insert new entry into the table */ @@ -65,7 +66,7 @@ std::pair, std::size_t> removeDuplicatesInPlace(c const auto result = table.emplace(entry, table.size()); /* Add the (either new or already existing) index into the array */ - remapping[i] = result.first->second; + indices[i] = result.first->second; /* If this is a new combination, copy the data to new (earlier) position in the array. Data in [table.size()-1, i) are already @@ -76,7 +77,13 @@ std::pair, std::size_t> removeDuplicatesInPlace(c } CORRADE_INTERNAL_ASSERT(dataSize >= table.size()); - return {std::move(remapping), table.size()}; + return table.size(); +} + +std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView2D& data) { + Containers::Array indices{Containers::NoInit, data.size()[0]}; + const std::size_t size = removeDuplicatesInPlaceInto(data, indices); + return {std::move(indices), size}; } namespace { diff --git a/src/Magnum/MeshTools/RemoveDuplicates.h b/src/Magnum/MeshTools/RemoveDuplicates.h index 504b992a34..a259c1e800 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.h +++ b/src/Magnum/MeshTools/RemoveDuplicates.h @@ -74,6 +74,19 @@ instead. Usage example: */ MAGNUM_MESHTOOLS_EXPORT std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView2D& data); +/** +@brief Remove duplicate data from given array in-place +@param[in,out] data Data array, duplicate items will be cut away with order + preserved +@param[out] indices Where to put the resulting index array +@return Size of unique prefix in the cleaned up @p data array +@m_since_latest + +Same as above, except that the index array is not allocated but put into +@p indices instead. Expects that @p indices has the same size as @p data. +*/ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices); + /** @brief Remove duplicates from indexed data in-place @param[in,out] indices Index array, which will get remapped to list just diff --git a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp index 1891083f3f..59109cf7d6 100644 --- a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp +++ b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp @@ -38,6 +38,7 @@ struct RemoveDuplicatesTest: TestSuite::Tester { void removeDuplicatesInPlace(); void removeDuplicatesInPlaceNonContiguous(); + void removeDuplicatesInPlaceIntoWrongOutputSize(); template void removeDuplicatesIndexedInPlace(); void removeDuplicatesIndexedInPlaceSmallType(); void removeDuplicatesIndexedInPlaceEmptyIndices(); @@ -56,6 +57,7 @@ struct RemoveDuplicatesTest: TestSuite::Tester { RemoveDuplicatesTest::RemoveDuplicatesTest() { addTests({&RemoveDuplicatesTest::removeDuplicatesInPlace, &RemoveDuplicatesTest::removeDuplicatesInPlaceNonContiguous, + &RemoveDuplicatesTest::removeDuplicatesInPlaceIntoWrongOutputSize, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, @@ -92,7 +94,19 @@ void RemoveDuplicatesTest::removeDuplicatesInPlaceNonContiguous() { std::ostringstream out; Error redirectError{&out}; MeshTools::removeDuplicatesInPlace(Containers::arrayCast<2, char>(Containers::arrayView(data)).every({1, 2})); - CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesInPlace(): second data view dimension is not contiguous\n"); + CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesInPlaceInto(): second data view dimension is not contiguous\n"); +} + +void RemoveDuplicatesTest::removeDuplicatesInPlaceIntoWrongOutputSize() { + Int data[8]{}; + UnsignedInt output[7]; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::removeDuplicatesInPlaceInto( + Containers::arrayCast<2, char>(Containers::arrayView(data)), + output); + CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesInPlaceInto(): output index array has 7 elements but expected 8\n"); } template void RemoveDuplicatesTest::removeDuplicatesIndexedInPlace() { From 4269c1303be7f6c1cf594992316df66553078e59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 22 Jan 2020 18:31:48 +0100 Subject: [PATCH 087/107] ObjImporter: port away from MeshDataXD. Not the tests yet -- those will get done in the next round. --- src/MagnumPlugins/ObjImporter/ObjImporter.cpp | 214 +++++++++++------- src/MagnumPlugins/ObjImporter/ObjImporter.h | 16 +- .../ObjImporter/Test/ObjImporterTest.cpp | 58 ++--- 3 files changed, 166 insertions(+), 122 deletions(-) diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp index e81778c785..af0b8f39a4 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.cpp +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.cpp @@ -29,16 +29,18 @@ #include #include #include -#include +#include #include +#include #include #include #include "Magnum/Mesh.h" -#include "Magnum/MeshTools/CombineIndexedArrays.h" +#include "Magnum/MeshTools/CompressIndices.h" +#include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/Math/Color.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" namespace Magnum { namespace Trade { @@ -58,7 +60,7 @@ void ignoreLine(std::istream& in) { template Math::Vector extractFloatData(const std::string& str, Float* extra = nullptr) { std::vector data = Utility::String::splitWithoutEmptyParts(str, ' '); if(data.size() < size || data.size() > size + (extra ? 1 : 0)) { - Error() << "Trade::ObjImporter::mesh3D(): invalid float array size"; + Error() << "Trade::ObjImporter::mesh(): invalid float array size"; throw 0; } @@ -78,16 +80,6 @@ template Math::Vector extractFloatData(const std: return output; } -template void reindex(const std::vector& indices, std::vector& data) { - /* Check that indices are in range */ - for(UnsignedInt i: indices) if(i >= data.size()) { - Error() << "Trade::ObjImporter::mesh3D(): index out of range"; - throw 0; - } - - data = MeshTools::duplicate(indices, data); -} - } ObjImporter::ObjImporter() = default; @@ -214,18 +206,34 @@ void ObjImporter::parseMeshNames() { std::get<1>(_file->meshes.back()) = _file->in->tellg(); } -UnsignedInt ObjImporter::doMesh3DCount() const { return _file->meshes.size(); } +UnsignedInt ObjImporter::doMeshCount() const { return _file->meshes.size(); } -Int ObjImporter::doMesh3DForName(const std::string& name) { +Int ObjImporter::doMeshForName(const std::string& name) { const auto it = _file->meshesForName.find(name); return it == _file->meshesForName.end() ? -1 : it->second; } -std::string ObjImporter::doMesh3DName(UnsignedInt id) { +std::string ObjImporter::doMeshName(UnsignedInt id) { return _file->meshNames[id]; } -Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { +namespace { + +template bool checkAndDuplicateInto(const Containers::StridedArrayView1D& indices, const Containers::Array& data, const Containers::StridedArrayView1D& out, UnsignedInt offset) { + /* Check that indices are in range. Add back the original index offset for + easier data debugging. */ + for(UnsignedInt i: indices) if(i >= data.size()) { + Error{} << "Trade::ObjImporter::mesh(): index" << (i + offset) << "out of range for" << data.size() << "vertices"; + return false; + } + + MeshTools::duplicateInto(indices, stridedArrayView(data), out); + return true; +} + +} + +Containers::Optional ObjImporter::doMesh(UnsignedInt id, UnsignedInt) { /* Seek the file, set mesh parsing parameters */ std::streampos begin, end; UnsignedInt positionIndexOffset, textureCoordinateIndexOffset, normalIndexOffset; @@ -233,12 +241,13 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { _file->in->seekg(begin); Containers::Optional primitive; - std::vector positions; - std::vector> textureCoordinates; - std::vector> normals; - std::vector positionIndices; - std::vector textureCoordinateIndices; - std::vector normalIndices; + Containers::Array positions; + Containers::Array normals; + Containers::Array textureCoordinates; + /* Taking a shortcut as there's fortunately nothing else than just 3 types + of data. First positions, then normals, then texture coordinates. */ + Containers::Array indices; + std::size_t textureCoordinateIndexCount = 0, normalIndexCount = 0; try { while(_file->in->good() && _file->in->tellg() < end) { /* Ignore comments */ @@ -266,28 +275,26 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { Float extra{1.0f}; const Vector3 data = extractFloatData<3>(contents, &extra); if(!Math::TypeTraits::equals(extra, 1.0f)) { - Error() << "Trade::ObjImporter::mesh3D(): homogeneous coordinates are not supported"; + Error() << "Trade::ObjImporter::mesh(): homogeneous coordinates are not supported"; return Containers::NullOpt; } - positions.push_back(data); + arrayAppend(positions, data); /* Texture coordinate */ } else if(keyword == "vt") { Float extra{0.0f}; - const auto data = extractFloatData<2>(contents, &extra); + const Vector2 data = extractFloatData<2>(contents, &extra); if(!Math::TypeTraits::equals(extra, 0.0f)) { - Error() << "Trade::ObjImporter::mesh3D(): 3D texture coordinates are not supported"; + Error() << "Trade::ObjImporter::mesh(): 3D texture coordinates are not supported"; return Containers::NullOpt; } - if(textureCoordinates.empty()) textureCoordinates.emplace_back(); - textureCoordinates.front().emplace_back(data); + arrayAppend(textureCoordinates, data); /* Normal */ } else if(keyword == "vn") { - if(normals.empty()) normals.emplace_back(); - normals.front().emplace_back(extractFloatData<3>(contents)); + arrayAppend(normals, Vector3{extractFloatData<3>(contents)}); /* Indices */ } else if(keyword == "p" || keyword == "l" || keyword == "f") { @@ -297,13 +304,13 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { if(keyword == "p") { /* Check that we don't mix the primitives in one mesh */ if(primitive && primitive != MeshPrimitive::Points) { - Error() << "Trade::ObjImporter::mesh3D(): mixed primitive" << *primitive << "and" << MeshPrimitive::Points; + Error() << "Trade::ObjImporter::mesh(): mixed primitive" << *primitive << "and" << MeshPrimitive::Points; return Containers::NullOpt; } /* Check vertex count per primitive */ if(indexTuples.size() != 1) { - Error() << "Trade::ObjImporter::mesh3D(): wrong index count for point"; + Error() << "Trade::ObjImporter::mesh(): wrong index count for point"; return Containers::NullOpt; } @@ -313,13 +320,13 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { } else if(keyword == "l") { /* Check that we don't mix the primitives in one mesh */ if(primitive && primitive != MeshPrimitive::Lines) { - Error() << "Trade::ObjImporter::mesh3D(): mixed primitive" << *primitive << "and" << MeshPrimitive::Lines; + Error() << "Trade::ObjImporter::mesh(): mixed primitive" << *primitive << "and" << MeshPrimitive::Lines; return Containers::NullOpt; } /* Check vertex count per primitive */ if(indexTuples.size() != 2) { - Error() << "Trade::ObjImporter::mesh3D(): wrong index count for line"; + Error() << "Trade::ObjImporter::mesh(): wrong index count for line"; return Containers::NullOpt; } @@ -329,16 +336,16 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { } else if(keyword == "f") { /* Check that we don't mix the primitives in one mesh */ if(primitive && primitive != MeshPrimitive::Triangles) { - Error() << "Trade::ObjImporter::mesh3D(): mixed primitive" << *primitive << "and" << MeshPrimitive::Triangles; + Error() << "Trade::ObjImporter::mesh(): mixed primitive" << *primitive << "and" << MeshPrimitive::Triangles; return Containers::NullOpt; } /* Check vertex count per primitive */ if(indexTuples.size() < 3) { - Error() << "Trade::ObjImporter::mesh3D(): wrong index count for triangle"; + Error() << "Trade::ObjImporter::mesh(): wrong index count for triangle"; return Containers::NullOpt; } else if(indexTuples.size() != 3) { - Error() << "Trade::ObjImporter::mesh3D(): polygons are not supported"; + Error() << "Trade::ObjImporter::mesh(): polygons are not supported"; return Containers::NullOpt; } @@ -347,22 +354,30 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { } else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ for(const std::string& indexTuple: indexTuples) { - std::vector indices = Utility::String::split(indexTuple, '/'); - if(indices.size() > 3) { - Error() << "Trade::ObjImporter::mesh3D(): invalid index data"; + std::vector indexStrings = Utility::String::split(indexTuple, '/'); + if(indexStrings.size() > 3) { + Error() << "Trade::ObjImporter::mesh(): invalid index data"; return Containers::NullOpt; } + Vector3ui index; + /* Position indices */ - positionIndices.push_back(std::stoul(indices[0]) - positionIndexOffset); + index[0] = std::stoul(indexStrings[0]) - positionIndexOffset; /* Texture coordinates */ - if(indices.size() == 2 || (indices.size() == 3 && !indices[1].empty())) - textureCoordinateIndices.push_back(std::stoul(indices[1]) - textureCoordinateIndexOffset); + if(indexStrings.size() == 2 || (indexStrings.size() == 3 && !indexStrings[1].empty())) { + index[2] = std::stoul(indexStrings[1]) - textureCoordinateIndexOffset; + ++textureCoordinateIndexCount; + } /* Normal indices */ - if(indices.size() == 3) - normalIndices.push_back(std::stoul(indices[2]) - normalIndexOffset); + if(indexStrings.size() == 3) { + index[1] = std::stoul(indexStrings[2]) - normalIndexOffset; + ++normalIndexCount; + } + + arrayAppend(indices, index); } /* Ignore unsupported keywords, error out on unknown keywords */ @@ -372,12 +387,12 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { if(keyword == expected) return true; return false; }()) { - Error() << "Trade::ObjImporter::mesh3D(): unknown keyword" << keyword; + Error() << "Trade::ObjImporter::mesh(): unknown keyword" << keyword; return Containers::NullOpt; } }} catch(const std::exception&) { - Error() << "Trade::ObjImporter::mesh3D(): error while converting numeric data"; + Error() << "Trade::ObjImporter::mesh(): error while converting numeric data"; return Containers::NullOpt; } catch(...) { /* Error message already printed */ @@ -385,64 +400,87 @@ Containers::Optional ObjImporter::doMesh3D(UnsignedInt id) { } /* There should be at least indexed position data */ - if(positions.empty() || positionIndices.empty()) { - Error() << "Trade::ObjImporter::mesh3D(): incomplete position data"; + if(positions.empty() || indices.empty()) { + Error() << "Trade::ObjImporter::mesh(): incomplete position data"; return Containers::NullOpt; } /* If there are index data, there should be also vertex data (and also the other way) */ - if(normals.empty() != normalIndices.empty()) { - Error() << "Trade::ObjImporter::mesh3D(): incomplete normal data"; + if(normals.empty() != (normalIndexCount == 0)) { + Error() << "Trade::ObjImporter::mesh(): incomplete normal data"; return Containers::NullOpt; } - if(textureCoordinates.empty() != textureCoordinateIndices.empty()) { - Error() << "Trade::ObjImporter::mesh3D(): incomplete texture coordinate data"; + if(textureCoordinates.empty() != (textureCoordinateIndexCount == 0)) { + Error() << "Trade::ObjImporter::mesh(): incomplete texture coordinate data"; return Containers::NullOpt; } /* All index arrays should have the same length */ - if(!normalIndices.empty() && normalIndices.size() != positionIndices.size()) { - CORRADE_INTERNAL_ASSERT(normalIndices.size() < positionIndices.size()); - Error() << "Trade::ObjImporter::mesh3D(): some normal indices are missing"; + if(normalIndexCount && normalIndexCount != indices.size()) { + CORRADE_INTERNAL_ASSERT(normalIndexCount < indices.size()); + Error() << "Trade::ObjImporter::mesh(): some normal indices are missing"; return Containers::NullOpt; } - if(!textureCoordinates.empty() && textureCoordinateIndices.size() != positionIndices.size()) { - CORRADE_INTERNAL_ASSERT(textureCoordinateIndices.size() < positionIndices.size()); - Error() << "Trade::ObjImporter::mesh3D(): some texture coordinate indices are missing"; + if(textureCoordinateIndexCount && textureCoordinateIndexCount != indices.size()) { + CORRADE_INTERNAL_ASSERT(textureCoordinateIndexCount < indices.size()); + Error() << "Trade::ObjImporter::mesh(): some texture coordinate indices are missing"; return Containers::NullOpt; } - /* Merge index arrays, if there aren't just the positions */ - std::vector indices; - if(!normalIndices.empty() || !textureCoordinateIndices.empty()) { - std::vector>> arrays; - arrays.reserve(3); - arrays.emplace_back(positionIndices); - if(!normalIndices.empty()) arrays.emplace_back(normalIndices); - if(!textureCoordinateIndices.empty()) arrays.emplace_back(textureCoordinateIndices); - indices = MeshTools::combineIndexArrays(arrays); - - /* Reindex data arrays */ - try { - reindex(positionIndices, positions); - if(!normalIndices.empty()) reindex(normalIndices, normals.front()); - if(!textureCoordinateIndices.empty()) reindex(textureCoordinateIndices, textureCoordinates.front()); - } catch(...) { - /* Error message already printed */ + /* Merge index arrays. If any of the attributes was not there, the whole + index array has zeros, not affecting the uniqueness in any way. */ + Containers::Array indexData{Containers::NoInit, indices.size()*sizeof(UnsignedInt)}; + const auto indexDataI = Containers::arrayCast(indexData); + const std::size_t vertexCount = MeshTools::removeDuplicatesInPlaceInto( + Containers::arrayCast<2, char>(arrayView(indices)), indexDataI); + + /* Allocate attribute and vertex data */ + std::size_t attributeCount = 1; + UnsignedInt stride = sizeof(Vector3); + if(normalIndexCount) { + ++attributeCount; + stride += sizeof(Vector3); + } + if(textureCoordinateIndexCount) { + ++attributeCount; + stride += sizeof(Vector2); + } + Containers::Array attributeData{attributeCount}; + Containers::Array vertexData{Containers::NoInit, vertexCount*stride}; + + /* Duplicate the vertices into the output */ + const auto indicesPerAttribute = Containers::arrayCast<2, const UnsignedInt>(stridedArrayView(indices)).transposed<0, 1>(); + std::size_t attributeIndex = 0; + std::size_t offset = 0; + { + Containers::StridedArrayView1D view{vertexData, + reinterpret_cast(vertexData.data()), vertexCount, stride}; + if(!checkAndDuplicateInto(indicesPerAttribute[0].prefix(vertexCount), positions, view, positionIndexOffset)) return Containers::NullOpt; - } - - /* Otherwise just use the original position index array. Don't forget to - check range */ - } else { - indices = std::move(positionIndices); - for(UnsignedInt i: indices) if(i >= positions.size()) { - Error() << "Trade::ObjImporter::mesh3D(): index out of range"; + attributeData[attributeIndex++] = MeshAttributeData{MeshAttribute::Position, view}; + offset += sizeof(Vector3); + } + if(normalIndexCount) { + Containers::StridedArrayView1D view{vertexData, + reinterpret_cast(vertexData.data() + offset), vertexCount, stride}; + if(!checkAndDuplicateInto(indicesPerAttribute[1].prefix(vertexCount), normals, view, normalIndexOffset)) return Containers::NullOpt; - } + attributeData[attributeIndex++] = MeshAttributeData{MeshAttribute::Normal, view}; + offset += sizeof(Vector3); + } + if(textureCoordinateIndexCount) { + Containers::StridedArrayView1D view{vertexData, + reinterpret_cast(vertexData.data() + offset), vertexCount, stride}; + if(!checkAndDuplicateInto(indicesPerAttribute[2].prefix(vertexCount), textureCoordinates, view, textureCoordinateIndexOffset)) + return Containers::NullOpt; + attributeData[attributeIndex++] = MeshAttributeData{MeshAttribute::TextureCoordinates, view}; + offset += sizeof(Vector2); } + CORRADE_INTERNAL_ASSERT(offset == stride && attributeIndex == attributeCount); - return MeshData3D{*primitive, std::move(indices), {std::move(positions)}, std::move(normals), std::move(textureCoordinates), {}, nullptr}; + return MeshData{*primitive, + std::move(indexData), Trade::MeshIndexData{indexDataI}, + std::move(vertexData), std::move(attributeData)}; } }} diff --git a/src/MagnumPlugins/ObjImporter/ObjImporter.h b/src/MagnumPlugins/ObjImporter/ObjImporter.h index 18987589da..0f49c2590d 100644 --- a/src/MagnumPlugins/ObjImporter/ObjImporter.h +++ b/src/MagnumPlugins/ObjImporter/ObjImporter.h @@ -91,8 +91,12 @@ See @ref building, @ref cmake and @ref plugins for more information. @section Trade-ObjImporter-limitations Behavior and limitations -Polygons (quads etc.), automatic normal generation and material properties are -currently not supported. +Meshes are imported as @ref MeshPrimitive::Triangles with +@ref MeshIndexType::UnsignedInt indices, interleaved @ref VertexFormat::Vector3 +positions with optional @ref VertexFormat::Vector3 normals and +@ref VertexFormat::Vector2 texture coordinates, if present in the source file. + +Polygons (quads etc.) and material properties are currently not supported. */ class MAGNUM_OBJIMPORTER_EXPORT ObjImporter: public AbstractImporter { public: @@ -114,10 +118,10 @@ class MAGNUM_OBJIMPORTER_EXPORT ObjImporter: public AbstractImporter { MAGNUM_OBJIMPORTER_LOCAL void doOpenFile(const std::string& filename) override; MAGNUM_OBJIMPORTER_LOCAL void doClose() override; - MAGNUM_OBJIMPORTER_LOCAL UnsignedInt doMesh3DCount() const override; - MAGNUM_OBJIMPORTER_LOCAL Int doMesh3DForName(const std::string& name) override; - MAGNUM_OBJIMPORTER_LOCAL std::string doMesh3DName(UnsignedInt id) override; - MAGNUM_OBJIMPORTER_LOCAL Containers::Optional doMesh3D(UnsignedInt id) override; + MAGNUM_OBJIMPORTER_LOCAL UnsignedInt doMeshCount() const override; + MAGNUM_OBJIMPORTER_LOCAL Int doMeshForName(const std::string& name) override; + MAGNUM_OBJIMPORTER_LOCAL std::string doMeshName(UnsignedInt id) override; + MAGNUM_OBJIMPORTER_LOCAL Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override; MAGNUM_OBJIMPORTER_LOCAL void parseMeshNames(); diff --git a/src/MagnumPlugins/ObjImporter/Test/ObjImporterTest.cpp b/src/MagnumPlugins/ObjImporter/Test/ObjImporterTest.cpp index 311da3a9ab..d1d760e0b0 100644 --- a/src/MagnumPlugins/ObjImporter/Test/ObjImporterTest.cpp +++ b/src/MagnumPlugins/ObjImporter/Test/ObjImporterTest.cpp @@ -160,13 +160,15 @@ void ObjImporterTest::pointMesh() { CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Points); CORRADE_COMPARE(data->positionArrayCount(), 1); + /* The points get reordered according to the index buffer. Might not be a + problem in general but it is when relying on the order */ CORRADE_COMPARE(data->positions(0), (std::vector{ {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {2.0f, 3.0f, 5.0f} + {2.0f, 3.0f, 5.0f}, + {0.0f, 1.5f, 1.0f} })); CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 2, 1, 0 + 0, 1, 2, 0 })); } @@ -217,7 +219,7 @@ void ObjImporterTest::mixedPrimitives() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(0)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): mixed primitive MeshPrimitive::Points and MeshPrimitive::Lines\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): mixed primitive MeshPrimitive::Points and MeshPrimitive::Lines\n"); } void ObjImporterTest::positionsOnly() { @@ -417,7 +419,7 @@ void ObjImporterTest::wrongFloat() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): error while converting numeric data\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): error while converting numeric data\n"); } void ObjImporterTest::wrongInteger() { @@ -429,7 +431,7 @@ void ObjImporterTest::wrongInteger() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): error while converting numeric data\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): error while converting numeric data\n"); } void ObjImporterTest::unmergedIndexOutOfRange() { @@ -441,7 +443,7 @@ void ObjImporterTest::unmergedIndexOutOfRange() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): index out of range\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): index 1 out of range for 1 vertices\n"); } void ObjImporterTest::mergedIndexOutOfRange() { @@ -453,7 +455,7 @@ void ObjImporterTest::mergedIndexOutOfRange() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): index out of range\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): index 2 out of range for 1 vertices\n"); } void ObjImporterTest::zeroIndex() { @@ -465,7 +467,7 @@ void ObjImporterTest::zeroIndex() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): index out of range\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): index 0 out of range for 1 vertices\n"); } void ObjImporterTest::explicitOptionalPositionCoordinate() { @@ -505,7 +507,7 @@ void ObjImporterTest::unsupportedOptionalPositionCoordinate() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): homogeneous coordinates are not supported\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): homogeneous coordinates are not supported\n"); } void ObjImporterTest::unsupportedOptionalTextureCoordinate() { @@ -517,7 +519,7 @@ void ObjImporterTest::unsupportedOptionalTextureCoordinate() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): 3D texture coordinates are not supported\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): 3D texture coordinates are not supported\n"); } void ObjImporterTest::shortFloatData() { @@ -529,7 +531,7 @@ void ObjImporterTest::shortFloatData() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): invalid float array size\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): invalid float array size\n"); } void ObjImporterTest::longFloatData() { @@ -541,7 +543,7 @@ void ObjImporterTest::longFloatData() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): invalid float array size\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): invalid float array size\n"); } void ObjImporterTest::longOptionalFloatData() { @@ -553,7 +555,7 @@ void ObjImporterTest::longOptionalFloatData() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): invalid float array size\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): invalid float array size\n"); } void ObjImporterTest::longIndexData() { @@ -565,7 +567,7 @@ void ObjImporterTest::longIndexData() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): invalid index data\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): invalid index data\n"); } void ObjImporterTest::wrongPointIndexData() { @@ -577,7 +579,7 @@ void ObjImporterTest::wrongPointIndexData() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): wrong index count for point\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): wrong index count for point\n"); } void ObjImporterTest::wrongLineIndexData() { @@ -589,7 +591,7 @@ void ObjImporterTest::wrongLineIndexData() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): wrong index count for line\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): wrong index count for line\n"); } void ObjImporterTest::wrongTriangleIndexData() { @@ -601,7 +603,7 @@ void ObjImporterTest::wrongTriangleIndexData() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): wrong index count for triangle\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): wrong index count for triangle\n"); } void ObjImporterTest::polygonIndexData() { @@ -613,7 +615,7 @@ void ObjImporterTest::polygonIndexData() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): polygons are not supported\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): polygons are not supported\n"); } void ObjImporterTest::missingPositionData() { @@ -625,7 +627,7 @@ void ObjImporterTest::missingPositionData() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): incomplete position data\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete position data\n"); } void ObjImporterTest::missingPositionIndices() { @@ -637,7 +639,7 @@ void ObjImporterTest::missingPositionIndices() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): incomplete position data\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete position data\n"); } void ObjImporterTest::missingNormalData() { @@ -649,7 +651,7 @@ void ObjImporterTest::missingNormalData() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): incomplete normal data\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete normal data\n"); } void ObjImporterTest::missingNormalIndices() { @@ -661,7 +663,7 @@ void ObjImporterTest::missingNormalIndices() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): incomplete normal data\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete normal data\n"); } void ObjImporterTest::missingTextureCoordinateData() { @@ -673,7 +675,7 @@ void ObjImporterTest::missingTextureCoordinateData() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): incomplete texture coordinate data\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete texture coordinate data\n"); } void ObjImporterTest::missingTextureCoordinateIndices() { @@ -685,7 +687,7 @@ void ObjImporterTest::missingTextureCoordinateIndices() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): incomplete texture coordinate data\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete texture coordinate data\n"); } void ObjImporterTest::wrongNormalIndexCount() { @@ -697,7 +699,7 @@ void ObjImporterTest::wrongNormalIndexCount() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): some normal indices are missing\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): some normal indices are missing\n"); } void ObjImporterTest::wrongTextureCoordinateIndexCount() { @@ -709,7 +711,7 @@ void ObjImporterTest::wrongTextureCoordinateIndexCount() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): some texture coordinate indices are missing\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): some texture coordinate indices are missing\n"); } void ObjImporterTest::unsupportedKeyword() { @@ -738,7 +740,7 @@ void ObjImporterTest::unknownKeyword() { std::ostringstream out; Error redirectError{&out}; CORRADE_VERIFY(!importer->mesh3D(id)); - CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh3D(): unknown keyword bleh\n"); + CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): unknown keyword bleh\n"); } }}}} From f782caaf523a971c96deabafc53d73b994f4dce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 23 Jan 2020 17:10:36 +0100 Subject: [PATCH 088/107] ObjImporter: port the test away from MeshData3D. --- .../ObjImporter/Test/ObjImporterTest.cpp | 508 ++++++++++-------- 1 file changed, 281 insertions(+), 227 deletions(-) diff --git a/src/MagnumPlugins/ObjImporter/Test/ObjImporterTest.cpp b/src/MagnumPlugins/ObjImporter/Test/ObjImporterTest.cpp index d1d760e0b0..c8a02b302a 100644 --- a/src/MagnumPlugins/ObjImporter/Test/ObjImporterTest.cpp +++ b/src/MagnumPlugins/ObjImporter/Test/ObjImporterTest.cpp @@ -26,13 +26,14 @@ #include #include #include +#include #include #include #include "Magnum/Mesh.h" #include "Magnum/Math/Vector3.h" #include "Magnum/Trade/AbstractImporter.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "configure.h" @@ -154,592 +155,645 @@ ObjImporterTest::ObjImporterTest() { void ObjImporterTest::pointMesh() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "pointMesh.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Points); - CORRADE_COMPARE(data->positionArrayCount(), 1); + CORRADE_COMPARE(data->attributeCount(), 1); /* The points get reordered according to the index buffer. Might not be a problem in general but it is when relying on the order */ - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {2.0f, 3.0f, 5.0f}, - {0.0f, 1.5f, 1.0f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 2, 0 - })); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {2.0f, 3.0f, 5.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1, 2, 0}), + TestSuite::Compare::Container); } void ObjImporterTest::lineMesh() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "lineMesh.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {2.0f, 3.0f, 5.0f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 1, 2 - })); + CORRADE_COMPARE(data->attributeCount(), 1); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {2.0f, 3.0f, 5.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1, 1, 2}), + TestSuite::Compare::Container); } void ObjImporterTest::triangleMesh() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "triangleMesh.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Triangles); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {2.0f, 3.0f, 5.0f}, - {2.5f, 0.0f, 1.0f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 2, 3, 1, 0 - })); + CORRADE_COMPARE(data->attributeCount(), 1); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {2.0f, 3.0f, 5.0f}, + {2.5f, 0.0f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1, 2, 3, 1, 0}), + TestSuite::Compare::Container); } void ObjImporterTest::mixedPrimitives() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "mixedPrimitives.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(0)); + CORRADE_VERIFY(!importer->mesh(0)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): mixed primitive MeshPrimitive::Points and MeshPrimitive::Lines\n"); } void ObjImporterTest::positionsOnly() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "triangleMesh.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_VERIFY(!data->hasNormals()); - CORRADE_VERIFY(!data->hasTextureCoords2D()); + CORRADE_COMPARE(data->attributeCount(), 1); + CORRADE_VERIFY(data->hasAttribute(MeshAttribute::Position)); } void ObjImporterTest::textureCoordinates() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "textureCoordinates.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_VERIFY(!data->hasNormals()); - CORRADE_COMPARE(data->textureCoords2DArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f} - })); - CORRADE_COMPARE(data->textureCoords2D(0), (std::vector{ - {1.0f, 0.5f}, - {1.0f, 0.5f}, - {0.5f, 1.0f}, - {0.5f, 1.0f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 2, 3, 1, 0 - })); + CORRADE_COMPARE(data->attributeCount(), 2); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {1.0f, 0.5f}, + {1.0f, 0.5f}, + {0.5f, 1.0f}, + {0.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1, 2, 3, 1, 0}), + TestSuite::Compare::Container); } void ObjImporterTest::normals() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "normals.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_VERIFY(!data->hasTextureCoords2D()); - CORRADE_COMPARE(data->normalArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f} - })); - CORRADE_COMPARE(data->normals(0), (std::vector{ - {1.0f, 0.5f, 3.5f}, - {1.0f, 0.5f, 3.5f}, - {0.5f, 1.0f, 0.5f}, - {0.5f, 1.0f, 0.5f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 2, 3, 1, 0 - })); + CORRADE_COMPARE(data->attributeCount(), 2); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Normal), + Containers::arrayView({ + {1.0f, 0.5f, 3.5f}, + {1.0f, 0.5f, 3.5f}, + {0.5f, 1.0f, 0.5f}, + {0.5f, 1.0f, 0.5f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1, 2, 3, 1, 0}), + TestSuite::Compare::Container); } void ObjImporterTest::textureCoordinatesNormals() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "textureCoordinatesNormals.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); - const Containers::Optional data = importer->mesh3D(0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->textureCoords2DArrayCount(), 1); - CORRADE_COMPARE(data->normalArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {0.0f, 1.5f, 1.0f} - })); - CORRADE_COMPARE(data->textureCoords2D(0), (std::vector{ - {1.0f, 0.5f}, - {1.0f, 0.5f}, - {0.5f, 1.0f}, - {0.5f, 1.0f}, - {0.5f, 1.0f} - })); - CORRADE_COMPARE(data->normals(0), (std::vector{ - {1.0f, 0.5f, 3.5f}, - {0.5f, 1.0f, 0.5f}, - {0.5f, 1.0f, 0.5f}, - {1.0f, 0.5f, 3.5f}, - {0.5f, 1.0f, 0.5f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1, 2, 3, 1, 0, 4, 2 - })); + CORRADE_COMPARE(data->attributeCount(), 3); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {1.0f, 0.5f}, + {1.0f, 0.5f}, + {0.5f, 1.0f}, + {0.5f, 1.0f}, + {0.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Normal), + Containers::arrayView({ + {1.0f, 0.5f, 3.5f}, + {0.5f, 1.0f, 0.5f}, + {0.5f, 1.0f, 0.5f}, + {1.0f, 0.5f, 3.5f}, + {0.5f, 1.0f, 0.5f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1, 2, 3, 1, 0, 4, 2}), + TestSuite::Compare::Container); } void ObjImporterTest::emptyFile() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "emptyFile.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); + CORRADE_COMPARE(importer->meshCount(), 1); } void ObjImporterTest::unnamedMesh() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "emptyFile.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); - CORRADE_COMPARE(importer->mesh3DName(0), ""); - CORRADE_COMPARE(importer->mesh3DForName(""), -1); + CORRADE_COMPARE(importer->meshCount(), 1); + CORRADE_COMPARE(importer->meshName(0), ""); + CORRADE_COMPARE(importer->meshForName(""), -1); } void ObjImporterTest::namedMesh() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "namedMesh.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 1); - CORRADE_COMPARE(importer->mesh3DName(0), "MyMesh"); - CORRADE_COMPARE(importer->mesh3DForName("MyMesh"), 0); + CORRADE_COMPARE(importer->meshCount(), 1); + CORRADE_COMPARE(importer->meshName(0), "MyMesh"); + CORRADE_COMPARE(importer->meshForName("MyMesh"), 0); } void ObjImporterTest::moreMeshes() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "moreMeshes.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 3); + CORRADE_COMPARE(importer->meshCount(), 3); - CORRADE_COMPARE(importer->mesh3DName(0), "PointMesh"); - CORRADE_COMPARE(importer->mesh3DForName("PointMesh"), 0); - const Containers::Optional data = importer->mesh3D(0); + CORRADE_COMPARE(importer->meshName(0), "PointMesh"); + CORRADE_COMPARE(importer->meshForName("PointMesh"), 0); + const Containers::Optional data = importer->mesh(0); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Points); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f} - })); - CORRADE_COMPARE(data->indices(), (std::vector{ - 0, 1 - })); - - CORRADE_COMPARE(importer->mesh3DName(1), "LineMesh"); - CORRADE_COMPARE(importer->mesh3DForName("LineMesh"), 1); - const Containers::Optional data1 = importer->mesh3D(1); + CORRADE_COMPARE(data->attributeCount(), 2); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Normal), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0, 1}), + TestSuite::Compare::Container); + + CORRADE_COMPARE(importer->meshName(1), "LineMesh"); + CORRADE_COMPARE(importer->meshForName("LineMesh"), 1); + const Containers::Optional data1 = importer->mesh(1); CORRADE_VERIFY(data1); CORRADE_COMPARE(data1->primitive(), MeshPrimitive::Lines); - CORRADE_COMPARE(data1->positionArrayCount(), 1); - CORRADE_COMPARE(data1->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f} - })); - CORRADE_COMPARE(data1->indices(), (std::vector{ - 0, 1, 1, 0 - })); - - CORRADE_COMPARE(importer->mesh3DName(2), "TriangleMesh"); - CORRADE_COMPARE(importer->mesh3DForName("TriangleMesh"), 2); - const Containers::Optional data2 = importer->mesh3D(2); + CORRADE_COMPARE(data1->attributeCount(), 2); + CORRADE_COMPARE_AS(data1->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data1->attribute(MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {0.5f, 2.0f}, + {0.0f, 1.5f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data1->isIndexed()); + CORRADE_COMPARE(data1->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data1->indices(), + Containers::arrayView({0, 1, 1, 0}), + TestSuite::Compare::Container); + + CORRADE_COMPARE(importer->meshName(2), "TriangleMesh"); + CORRADE_COMPARE(importer->meshForName("TriangleMesh"), 2); + const Containers::Optional data2 = importer->mesh(2); CORRADE_VERIFY(data2); CORRADE_COMPARE(data2->primitive(), MeshPrimitive::Triangles); - CORRADE_COMPARE(data2->positionArrayCount(), 1); - CORRADE_COMPARE(data2->positions(0), (std::vector{ - {0.5f, 2.0f, 3.0f}, - {0.0f, 1.5f, 1.0f}, - {2.0f, 3.0f, 5.5f} - })); - CORRADE_COMPARE(data2->indices(), (std::vector{ - 0, 1, 2, 2, 1, 0 - })); + CORRADE_COMPARE(data2->attributeCount(), 3); + CORRADE_COMPARE_AS(data2->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.0f, 1.5f, 1.0f}, + {2.0f, 3.0f, 5.5f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data2->attribute(MeshAttribute::Normal), + Containers::arrayView({ + {0.5f, 2.0f, 3.0f}, + {0.5f, 2.0f, 3.0f}, + {0.5f, 2.0f, 3.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(data2->attribute(MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {0.5f, 2.0f}, + {0.5f, 2.0f}, + {0.5f, 2.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data2->isIndexed()); + CORRADE_COMPARE(data2->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data2->indices(), + Containers::arrayView({0, 1, 2, 2, 1, 0}), + TestSuite::Compare::Container); } void ObjImporterTest::unnamedFirstMesh() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "unnamedFirstMesh.obj"))); - CORRADE_COMPARE(importer->mesh3DCount(), 2); + CORRADE_COMPARE(importer->meshCount(), 2); - CORRADE_COMPARE(importer->mesh3DName(0), ""); - CORRADE_COMPARE(importer->mesh3DForName(""), -1); + CORRADE_COMPARE(importer->meshName(0), ""); + CORRADE_COMPARE(importer->meshForName(""), -1); - CORRADE_COMPARE(importer->mesh3DName(1), "SecondMesh"); - CORRADE_COMPARE(importer->mesh3DForName("SecondMesh"), 1); + CORRADE_COMPARE(importer->meshName(1), "SecondMesh"); + CORRADE_COMPARE(importer->meshForName("SecondMesh"), 1); } void ObjImporterTest::wrongFloat() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumbers.obj"))); - const Int id = importer->mesh3DForName("WrongFloat"); + const Int id = importer->meshForName("WrongFloat"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): error while converting numeric data\n"); } void ObjImporterTest::wrongInteger() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumbers.obj"))); - const Int id = importer->mesh3DForName("WrongInteger"); + const Int id = importer->meshForName("WrongInteger"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): error while converting numeric data\n"); } void ObjImporterTest::unmergedIndexOutOfRange() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumbers.obj"))); - const Int id = importer->mesh3DForName("PositionIndexOutOfRange"); + const Int id = importer->meshForName("PositionIndexOutOfRange"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): index 1 out of range for 1 vertices\n"); } void ObjImporterTest::mergedIndexOutOfRange() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumbers.obj"))); - const Int id = importer->mesh3DForName("TextureIndexOutOfRange"); + const Int id = importer->meshForName("TextureIndexOutOfRange"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): index 2 out of range for 1 vertices\n"); } void ObjImporterTest::zeroIndex() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumbers.obj"))); - const Int id = importer->mesh3DForName("ZeroIndex"); + const Int id = importer->meshForName("ZeroIndex"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): index 0 out of range for 1 vertices\n"); } void ObjImporterTest::explicitOptionalPositionCoordinate() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "optionalCoordinates.obj"))); - const Int id = importer->mesh3DForName("SupportedPositionW"); + const Int id = importer->meshForName("SupportedPositionW"); CORRADE_VERIFY(id > -1); - const Containers::Optional data = importer->mesh3D(id); + const Containers::Optional data = importer->mesh(id); CORRADE_VERIFY(data); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {1.5f, 2.0f, 3.0f} - })); + CORRADE_COMPARE(data->attributeCount(), 1); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {1.5f, 2.0f, 3.0f} + }), TestSuite::Compare::Container); } void ObjImporterTest::explicitOptionalTextureCoordinate() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "optionalCoordinates.obj"))); - const Int id = importer->mesh3DForName("SupportedTextureW"); + const Int id = importer->meshForName("SupportedTextureW"); CORRADE_VERIFY(id > -1); - const Containers::Optional data = importer->mesh3D(id); + const Containers::Optional data = importer->mesh(id); CORRADE_VERIFY(data); - CORRADE_COMPARE(data->textureCoords2DArrayCount(), 1); - CORRADE_COMPARE(data->textureCoords2D(0), (std::vector{ - {0.5f, 0.7f} - })); + CORRADE_COMPARE(data->attributeCount(MeshAttribute::TextureCoordinates), 1); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {0.5f, 0.7f} + }), TestSuite::Compare::Container); } void ObjImporterTest::unsupportedOptionalPositionCoordinate() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "optionalCoordinates.obj"))); - const Int id = importer->mesh3DForName("UnsupportedPositionW"); + const Int id = importer->meshForName("UnsupportedPositionW"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): homogeneous coordinates are not supported\n"); } void ObjImporterTest::unsupportedOptionalTextureCoordinate() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "optionalCoordinates.obj"))); - const Int id = importer->mesh3DForName("UnsupportedTextureW"); + const Int id = importer->meshForName("UnsupportedTextureW"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): 3D texture coordinates are not supported\n"); } void ObjImporterTest::shortFloatData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("ShortFloat"); + const Int id = importer->meshForName("ShortFloat"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): invalid float array size\n"); } void ObjImporterTest::longFloatData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("LongFloat"); + const Int id = importer->meshForName("LongFloat"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): invalid float array size\n"); } void ObjImporterTest::longOptionalFloatData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("LongOptionalFloat"); + const Int id = importer->meshForName("LongOptionalFloat"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): invalid float array size\n"); } void ObjImporterTest::longIndexData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("InvalidIndices"); + const Int id = importer->meshForName("InvalidIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): invalid index data\n"); } void ObjImporterTest::wrongPointIndexData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("WrongPointIndices"); + const Int id = importer->meshForName("WrongPointIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): wrong index count for point\n"); } void ObjImporterTest::wrongLineIndexData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("WrongLineIndices"); + const Int id = importer->meshForName("WrongLineIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): wrong index count for line\n"); } void ObjImporterTest::wrongTriangleIndexData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("WrongTriangleIndices"); + const Int id = importer->meshForName("WrongTriangleIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): wrong index count for triangle\n"); } void ObjImporterTest::polygonIndexData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongNumberCount.obj"))); - const Int id = importer->mesh3DForName("PolygonIndices"); + const Int id = importer->meshForName("PolygonIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): polygons are not supported\n"); } void ObjImporterTest::missingPositionData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "missingData.obj"))); - const Int id = importer->mesh3DForName("MissingPositionData"); + const Int id = importer->meshForName("MissingPositionData"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete position data\n"); } void ObjImporterTest::missingPositionIndices() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "missingData.obj"))); - const Int id = importer->mesh3DForName("MissingPositionIndices"); + const Int id = importer->meshForName("MissingPositionIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete position data\n"); } void ObjImporterTest::missingNormalData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "missingData.obj"))); - const Int id = importer->mesh3DForName("MissingNormalData"); + const Int id = importer->meshForName("MissingNormalData"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete normal data\n"); } void ObjImporterTest::missingNormalIndices() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "missingData.obj"))); - const Int id = importer->mesh3DForName("MissingNormalIndices"); + const Int id = importer->meshForName("MissingNormalIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete normal data\n"); } void ObjImporterTest::missingTextureCoordinateData() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "missingData.obj"))); - const Int id = importer->mesh3DForName("MissingTextureData"); + const Int id = importer->meshForName("MissingTextureData"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete texture coordinate data\n"); } void ObjImporterTest::missingTextureCoordinateIndices() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "missingData.obj"))); - const Int id = importer->mesh3DForName("MissingTextureIndices"); + const Int id = importer->meshForName("MissingTextureIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): incomplete texture coordinate data\n"); } void ObjImporterTest::wrongNormalIndexCount() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongIndexCount.obj"))); - const Int id = importer->mesh3DForName("ShortNormalIndices"); + const Int id = importer->meshForName("ShortNormalIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): some normal indices are missing\n"); } void ObjImporterTest::wrongTextureCoordinateIndexCount() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "wrongIndexCount.obj"))); - const Int id = importer->mesh3DForName("ShortTextureIndices"); + const Int id = importer->meshForName("ShortTextureIndices"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): some texture coordinate indices are missing\n"); } void ObjImporterTest::unsupportedKeyword() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "keywords.obj"))); - const Int id = importer->mesh3DForName("UnsupportedKeyword"); + const Int id = importer->meshForName("UnsupportedKeyword"); CORRADE_VERIFY(id > -1); /* Everything should be parsed properly */ - const Containers::Optional data = importer->mesh3D(id); + const Containers::Optional data = importer->mesh(id); CORRADE_VERIFY(data); CORRADE_COMPARE(data->primitive(), MeshPrimitive::Points); - CORRADE_COMPARE(data->positionArrayCount(), 1); - CORRADE_COMPARE(data->positions(0), (std::vector{ - {0.0f, 1.0f, 2.0f} - })); - CORRADE_COMPARE(data->indices(), std::vector{0}); + CORRADE_COMPARE(data->attributeCount(), 1); + CORRADE_COMPARE_AS(data->attribute(MeshAttribute::Position), + Containers::arrayView({ + {0.0f, 1.0f, 2.0f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(data->isIndexed()); + CORRADE_COMPARE(data->indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(data->indices(), + Containers::arrayView({0}), + TestSuite::Compare::Container); } void ObjImporterTest::unknownKeyword() { Containers::Pointer importer = _manager.instantiate("ObjImporter"); CORRADE_VERIFY(importer->openFile(Utility::Directory::join(OBJIMPORTER_TEST_DIR, "keywords.obj"))); - const Int id = importer->mesh3DForName("UnknownKeyword"); + const Int id = importer->meshForName("UnknownKeyword"); CORRADE_VERIFY(id > -1); std::ostringstream out; Error redirectError{&out}; - CORRADE_VERIFY(!importer->mesh3D(id)); + CORRADE_VERIFY(!importer->mesh(id)); CORRADE_COMPARE(out.str(), "Trade::ObjImporter::mesh(): unknown keyword bleh\n"); } From ab86b4c5818707c494a6bd314143edb295b5a171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 21 Nov 2019 17:30:31 +0100 Subject: [PATCH 089/107] AnySceneImporter: adapt to AbstractImporter changes. --- .../AnySceneImporter/AnySceneImporter.cpp | 22 ++++++++++-- .../AnySceneImporter/AnySceneImporter.h | 14 ++++++++ .../Test/AnySceneImporterTest.cpp | 35 +++++++++++++++++++ 3 files changed, 69 insertions(+), 2 deletions(-) diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp index 18c86b14a8..0e7faa2b78 100644 --- a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp +++ b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.cpp @@ -36,13 +36,19 @@ #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" -#include "Magnum/Trade/MeshData2D.h" -#include "Magnum/Trade/MeshData3D.h" +#include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/ObjectData2D.h" #include "Magnum/Trade/ObjectData3D.h" #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/TextureData.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + +#include "Magnum/Trade/MeshData2D.h" +#include "Magnum/Trade/MeshData3D.h" +#endif + namespace Magnum { namespace Trade { AnySceneImporter::AnySceneImporter(PluginManager::Manager& manager): AbstractImporter{manager} {} @@ -175,6 +181,16 @@ Int AnySceneImporter::doObject3DForName(const std::string& name) { return _in->o std::string AnySceneImporter::doObject3DName(const UnsignedInt id) { return _in->object3DName(id); } Containers::Pointer AnySceneImporter::doObject3D(const UnsignedInt id) { return _in->object3D(id); } +UnsignedInt AnySceneImporter::doMeshCount() const { return _in->meshCount(); } +Int AnySceneImporter::doMeshForName(const std::string& name) { return _in->meshForName(name); } +std::string AnySceneImporter::doMeshName(const UnsignedInt id) { return _in->meshName(id); } +Containers::Optional AnySceneImporter::doMesh(const UnsignedInt id, const UnsignedInt level) { return _in->mesh(id, level); } + +MeshAttribute AnySceneImporter::doMeshAttributeForName(const std::string& name) { return _in->meshAttributeForName(name); } +std::string AnySceneImporter::doMeshAttributeName(const UnsignedShort id) { return _in->meshAttributeName(meshAttributeCustom(id)); } + +#ifdef MAGNUM_BUILD_DEPRECATED +CORRADE_IGNORE_DEPRECATED_PUSH UnsignedInt AnySceneImporter::doMesh2DCount() const { return _in->mesh2DCount(); } Int AnySceneImporter::doMesh2DForName(const std::string& name) { return _in->mesh2DForName(name); } std::string AnySceneImporter::doMesh2DName(const UnsignedInt id) { return _in->mesh2DName(id); } @@ -184,6 +200,8 @@ UnsignedInt AnySceneImporter::doMesh3DCount() const { return _in->mesh3DCount(); Int AnySceneImporter::doMesh3DForName(const std::string& name) { return _in->mesh3DForName(name); } std::string AnySceneImporter::doMesh3DName(const UnsignedInt id) { return _in->mesh3DName(id); } Containers::Optional AnySceneImporter::doMesh3D(const UnsignedInt id) { return _in->mesh3D(id); } +CORRADE_IGNORE_DEPRECATED_POP +#endif UnsignedInt AnySceneImporter::doMaterialCount() const { return _in->materialCount(); } Int AnySceneImporter::doMaterialForName(const std::string& name) { return _in->materialForName(name); } diff --git a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h index 1cb4868b0b..743bd86a40 100644 --- a/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h +++ b/src/MagnumPlugins/AnySceneImporter/AnySceneImporter.h @@ -175,15 +175,29 @@ class MAGNUM_ANYSCENEIMPORTER_EXPORT AnySceneImporter: public AbstractImporter { MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doObject3DName(UnsignedInt id) override; MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Pointer doObject3D(UnsignedInt id) override; + MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doMeshCount() const override; + MAGNUM_ANYSCENEIMPORTER_LOCAL Int doMeshForName(const std::string& name) override; + MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doMeshName(UnsignedInt id) override; + MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override; + + MAGNUM_ANYSCENEIMPORTER_LOCAL MeshAttribute doMeshAttributeForName(const std::string& name) override; + MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doMeshAttributeName(UnsignedShort id) override; + + #ifdef MAGNUM_BUILD_DEPRECATED MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doMesh2DCount() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL Int doMesh2DForName(const std::string& name) override; MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doMesh2DName(UnsignedInt id) override; + CORRADE_IGNORE_DEPRECATED_PUSH MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Optional doMesh2D(UnsignedInt id) override; + CORRADE_IGNORE_DEPRECATED_POP MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doMesh3DCount() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL Int doMesh3DForName(const std::string& name) override; MAGNUM_ANYSCENEIMPORTER_LOCAL std::string doMesh3DName(UnsignedInt id) override; + CORRADE_IGNORE_DEPRECATED_PUSH MAGNUM_ANYSCENEIMPORTER_LOCAL Containers::Optional doMesh3D(UnsignedInt id) override; + CORRADE_IGNORE_DEPRECATED_POP + #endif MAGNUM_ANYSCENEIMPORTER_LOCAL UnsignedInt doMaterialCount() const override; MAGNUM_ANYSCENEIMPORTER_LOCAL Int doMaterialForName(const std::string& name) override; diff --git a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp index 325f4cf2bc..722754bb96 100644 --- a/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp +++ b/src/MagnumPlugins/AnySceneImporter/Test/AnySceneImporterTest.cpp @@ -33,7 +33,13 @@ #include "Magnum/Math/Vector3.h" #include "Magnum/Trade/AbstractImporter.h" +#include "Magnum/Trade/MeshData.h" + +#ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ + #include "Magnum/Trade/MeshData3D.h" +#endif #include "configure.h" @@ -43,6 +49,9 @@ struct AnySceneImporterTest: TestSuite::Tester { explicit AnySceneImporterTest(); void load(); + #ifdef MAGNUM_BUILD_DEPRECATED + void loadDeprecatedMeshData(); + #endif void detect(); void unknown(); @@ -79,6 +88,10 @@ AnySceneImporterTest::AnySceneImporterTest() { addInstancedTests({&AnySceneImporterTest::load}, Containers::arraySize(LoadData)); + #ifdef MAGNUM_BUILD_DEPRECATED + addTests({&AnySceneImporterTest::loadDeprecatedMeshData}); + #endif + addInstancedTests({&AnySceneImporterTest::detect}, Containers::arraySize(DetectData)); @@ -106,13 +119,35 @@ void AnySceneImporterTest::load() { CORRADE_VERIFY(importer->openFile(data.filename)); /* Check only size, as it is good enough proof that it is working */ + Containers::Optional mesh = importer->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_COMPARE(mesh->vertexCount(), 3); + + importer->close(); + CORRADE_VERIFY(!importer->isOpened()); +} + +#ifdef MAGNUM_BUILD_DEPRECATED +void AnySceneImporterTest::loadDeprecatedMeshData() { + if(!(_manager.loadState("ObjImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("ObjImporter plugin not enabled, cannot test"); + + Containers::Pointer importer = _manager.instantiate("AnySceneImporter"); + CORRADE_VERIFY(importer->openFile(OBJ_FILE)); + + /* Check only size, as it is good enough proof that it is working */ + + /* MSVC warns also on positions() */ + CORRADE_IGNORE_DEPRECATED_PUSH Containers::Optional mesh = importer->mesh3D(0); CORRADE_VERIFY(mesh); CORRADE_COMPARE(mesh->positions(0).size(), 3); + CORRADE_IGNORE_DEPRECATED_POP importer->close(); CORRADE_VERIFY(!importer->isOpened()); } +#endif void AnySceneImporterTest::detect() { auto&& data = DetectData[testCaseInstanceId()]; From ec739c19ca7ea04a2d0d7dd59e3e1b54de6390e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 28 Feb 2020 14:30:10 +0100 Subject: [PATCH 090/107] MeshTools: implemented combineIndexedAttributes(). Replaces the STL-heavy combineIndexedArrays(), but in a less extremely horrendous way. --- doc/changelog.dox | 2 + src/Magnum/MeshTools/CMakeLists.txt | 2 + src/Magnum/MeshTools/Combine.cpp | 143 +++++++++++++++++ src/Magnum/MeshTools/Combine.h | 94 ++++++++++++ src/Magnum/MeshTools/Test/CMakeLists.txt | 2 + src/Magnum/MeshTools/Test/CombineTest.cpp | 179 ++++++++++++++++++++++ 6 files changed, 422 insertions(+) create mode 100644 src/Magnum/MeshTools/Combine.cpp create mode 100644 src/Magnum/MeshTools/Combine.h create mode 100644 src/Magnum/MeshTools/Test/CombineTest.cpp diff --git a/doc/changelog.dox b/doc/changelog.dox index 7c942f5862..aa6e4b914c 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -128,6 +128,8 @@ See also: subdivision - 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 @subsubsection changelog-latest-new-platform Platform libraries diff --git a/src/Magnum/MeshTools/CMakeLists.txt b/src/Magnum/MeshTools/CMakeLists.txt index efbf610b1d..ebc59e94ff 100644 --- a/src/Magnum/MeshTools/CMakeLists.txt +++ b/src/Magnum/MeshTools/CMakeLists.txt @@ -29,6 +29,7 @@ set(MagnumMeshTools_SRCS # Files compiled with different flags for main library and unit test library set(MagnumMeshTools_GracefulAssert_SRCS + Combine.cpp CombineIndexedArrays.cpp CompressIndices.cpp Duplicate.cpp @@ -38,6 +39,7 @@ set(MagnumMeshTools_GracefulAssert_SRCS RemoveDuplicates.cpp) set(MagnumMeshTools_HEADERS + Combine.h CombineIndexedArrays.h CompressIndices.h Duplicate.h diff --git a/src/Magnum/MeshTools/Combine.cpp b/src/Magnum/MeshTools/Combine.cpp new file mode 100644 index 0000000000..d099a90e3a --- /dev/null +++ b/src/Magnum/MeshTools/Combine.cpp @@ -0,0 +1,143 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Combine.h" + +#include +#include +#include +#include + +#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})); + + /* 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; + 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) + 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); + const std::size_t vertexCount = removeDuplicatesInPlaceInto( + Containers::StridedArrayView2D{combinedIndices, {indexCount, indexStride}}, + indexDataI); + + /* Allocate resulting attribute and vertex data and duplicate the + attributes there according to the combined index buffer */ + Containers::Array vertexData{Containers::NoInit, + vertexStride*vertexCount}; + Containers::Array attributeData{attributeCount}; + { + std::size_t indexOffset = 0; + std::size_t attributeOffset = 0; + std::size_t vertexOffset = 0; + for(const Trade::MeshData& mesh: data) { + const UnsignedInt indexSize = mesh.isIndexed() ? + meshIndexTypeSize(mesh.indexType()) : 4; + Containers::StridedArrayView2D indices{combinedIndices, + combinedIndices.data() + indexOffset, + {vertexCount, indexSize}, + {std::ptrdiff_t(indexStride), 1}}; + + for(UnsignedInt i = 0; i != mesh.attributeCount(); ++i) { + const UnsignedInt attributeSize = vertexFormatSize(mesh.attributeFormat(i)); + Containers::StridedArrayView2D dst{vertexData, + vertexData.data() + vertexOffset, + {vertexCount, attributeSize}, + {std::ptrdiff_t(vertexStride), 1}}; + duplicateInto(indices, mesh.attribute(i), dst); + vertexOffset += attributeSize; + attributeData[attributeOffset++] = Trade::MeshAttributeData{ + mesh.attributeName(i), mesh.attributeFormat(i), dst}; + } + + indexOffset += indexSize; + } + + /* Check we pre-calculated correctly */ + CORRADE_INTERNAL_ASSERT(attributeOffset == attributeCount && vertexOffset == vertexStride); + } + + return Trade::MeshData{primitive, + std::move(indexData), Trade::MeshIndexData{indexDataI}, + std::move(vertexData), std::move(attributeData)}; +} + +Trade::MeshData combineIndexedAttributes(std::initializer_list> data) { + return combineIndexedAttributes(Containers::arrayView(data)); +} + +}} diff --git a/src/Magnum/MeshTools/Combine.h b/src/Magnum/MeshTools/Combine.h new file mode 100644 index 0000000000..a2c5441714 --- /dev/null +++ b/src/Magnum/MeshTools/Combine.h @@ -0,0 +1,94 @@ +#ifndef Magnum_MeshTools_Combine_h +#define Magnum_MeshTools_Combine_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Function @ref Magnum::MeshTools::combineIndexedAttributes() + * @m_since_latest + */ + +#include + +#include "Magnum/MeshTools/visibility.h" +#include "Magnum/Trade/Trade.h" + +namespace Magnum { namespace MeshTools { + +/** +@brief Combine differently indexed attributes into a single mesh +@m_since_latest + +Assuming each @p data contains only unique vertex data, creates an indexed mesh +that contains all attributes from @p data combined, with duplicate vertices +removed. For example, when you have a position and a normal array, each indexed +with separate indices like this: + +@code{.cpp} +{pA, pB, pC, pD, pE, pF} // positions +{nA, nB, nC, nD, nE, nF, nG} // normals + +{0, 2, 5, 0, 0, 1, 3, 2, 2} // position indices +{1, 3, 4, 1, 4, 6, 1, 3, 1} // normal indices +@endcode + +Then the first triangle in the mesh is defined as +@cb{.cpp} {pA, nB}, {pC, nD}, {pF, nE} @ce. When combined together using this +function, the resulting mesh stays the same but there's just one index array, +indexing both positions and normals: + +@code{.cpp} +{{pA, nB}, {pC, nD}, {pF, nE}, {pA, nE}, {pB, nG}, {pD, nB}, {pC, nB}} + // unique pairs of positions and normals + +{0, 1, 2, 0, 3, 4, 5, 1, 6} // unified indices +@endcode + +The function preserves all vertex data including repeated or custom attributes. +The resulting mesh is interleaved, with all attributes packed tightly together. +If you need to add specific padding for alignment preservation, pass the result +to @ref interleave() and specify the paddings between attributes manually. +Similarly, for simplicity the resulting mesh has always +@ref MeshIndexType::UnsignedInt --- use @ref compressIndices(const Trade::MeshData&, MeshIndexType) +if you want to have it compressed to a smaller type. + +Expects that @p data is non-empty and all data have the same primitive and +index count. All inputs have to be indexed, although the particular +@ref MeshIndexType doesn't matter. For non-indexed attributes combining can be +done much more efficiently using @ref duplicate(const Trade::MeshData&, Containers::ArrayView), +alternatively you can turn a non-indexed attribute to an indexed one first +using @ref removeDuplicatesInPlace() and then call this function. +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(const Containers::ArrayView> data); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(std::initializer_list> data); + +}} + +#endif diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index 31939f6eb5..29b84aba4d 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -23,6 +23,7 @@ # DEALINGS IN THE SOFTWARE. # +corrade_add_test(MeshToolsCombineTest CombineTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsCombineIndexedArraysTest CombineIndexedArraysTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsCompressIndicesTest CompressIndicesTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp LIBRARIES MagnumMeshToolsTestLib) @@ -45,6 +46,7 @@ set_property(TARGET APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") set_target_properties( + MeshToolsCombineTest MeshToolsCombineIndexedArraysTest MeshToolsCompressIndicesTest MeshToolsDuplicateTest diff --git a/src/Magnum/MeshTools/Test/CombineTest.cpp b/src/Magnum/MeshTools/Test/CombineTest.cpp new file mode 100644 index 0000000000..db91dbd099 --- /dev/null +++ b/src/Magnum/MeshTools/Test/CombineTest.cpp @@ -0,0 +1,179 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include +#include + +#include "Magnum/MeshTools/Combine.h" +#include "Magnum/Trade/MeshData.h" + +namespace Magnum { namespace MeshTools { namespace Test { namespace { + +struct CombineTest: TestSuite::Tester { + explicit CombineTest(); + + void combineIndexedAttributes(); + void combineIndexedAttributesIndicesOnly(); + + void combineIndexedAttributesNoMeshes(); + void combineIndexedAttributesNotIndexed(); + void combineIndexedAttributesDifferentPrimitive(); + void combineIndexedAttributesDifferentIndexCount(); +}; + +CombineTest::CombineTest() { + addTests({&CombineTest::combineIndexedAttributes, + &CombineTest::combineIndexedAttributesIndicesOnly, + + &CombineTest::combineIndexedAttributesNoMeshes, + &CombineTest::combineIndexedAttributesNotIndexed, + &CombineTest::combineIndexedAttributesDifferentPrimitive, + &CombineTest::combineIndexedAttributesDifferentIndexCount}); +} + +void CombineTest::combineIndexedAttributes() { + const UnsignedInt indicesA[]{2, 1, 2, 0}; + const Int dataA[]{2, 1, 0}; + const UnsignedByte indicesB[]{3, 4, 3, 2}; + const Short dataB[]{4, 3, 2, 1, 0}; + const UnsignedShort indicesC[]{7, 6, 7, 5}; + const Float dataC[]{0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f}; + Trade::MeshData a{MeshPrimitive::LineLoop, + {}, indicesA, Trade::MeshIndexData{indicesA}, + {}, dataA, {Trade::MeshAttributeData{ + Trade::meshAttributeCustom(2), Containers::arrayView(dataA)}}}; + Trade::MeshData b{MeshPrimitive::LineLoop, + {}, indicesB, Trade::MeshIndexData{indicesB}, + {}, dataB, {Trade::MeshAttributeData{ + Trade::meshAttributeCustom(17), Containers::arrayView(dataB)}}}; + Trade::MeshData c{MeshPrimitive::LineLoop, + {}, indicesC, Trade::MeshIndexData{indicesC}, + {}, dataC, {Trade::MeshAttributeData{ + Trade::meshAttributeCustom(22), Containers::arrayView(dataC)}}}; + + Trade::MeshData result = MeshTools::combineIndexedAttributes({a, b, c}); + CORRADE_COMPARE(result.primitive(), MeshPrimitive::LineLoop); + CORRADE_VERIFY(result.isIndexed()); + CORRADE_COMPARE(result.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(result.indices(), + Containers::arrayView({0, 1, 0, 2}), + TestSuite::Compare::Container); + + CORRADE_COMPARE(result.attributeCount(), 3); + CORRADE_COMPARE(result.attributeName(0), Trade::meshAttributeCustom(2)); + CORRADE_COMPARE(result.attributeFormat(0), VertexFormat::Int); + CORRADE_COMPARE_AS(result.attribute(0), + Containers::arrayView({0, 1, 2}), + TestSuite::Compare::Container); + CORRADE_COMPARE(result.attributeName(1), Trade::meshAttributeCustom(17)); + CORRADE_COMPARE(result.attributeFormat(1), VertexFormat::Short); + CORRADE_COMPARE_AS(result.attribute(1), + Containers::arrayView({1, 0, 2}), + TestSuite::Compare::Container); + CORRADE_COMPARE(result.attributeName(2), Trade::meshAttributeCustom(22)); + CORRADE_COMPARE(result.attributeFormat(2), VertexFormat::Float); + CORRADE_COMPARE_AS(result.attribute(2), + Containers::arrayView({7.0f, 6.0f, 5.0f}), + TestSuite::Compare::Container); +} + +void CombineTest::combineIndexedAttributesIndicesOnly() { + const UnsignedInt indicesA[]{2, 1, 2}; + const UnsignedShort indicesB[]{3, 4, 3}; + const UnsignedByte indicesC[]{7, 6, 7}; + Trade::MeshData a{MeshPrimitive::LineLoop, {}, indicesA, + Trade::MeshIndexData{indicesA}}; + Trade::MeshData b{MeshPrimitive::LineLoop, {}, indicesB, + Trade::MeshIndexData{indicesB}}; + Trade::MeshData c{MeshPrimitive::LineLoop, {}, indicesC, + Trade::MeshIndexData{indicesC}}; + + Trade::MeshData result = MeshTools::combineIndexedAttributes({a, b, c}); + CORRADE_COMPARE(result.primitive(), MeshPrimitive::LineLoop); + CORRADE_VERIFY(result.isIndexed()); + CORRADE_COMPARE(result.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(result.indices(), + Containers::arrayView({0, 1, 0}), + TestSuite::Compare::Container); + CORRADE_COMPARE(result.attributeCount(), 0); + CORRADE_COMPARE(result.vertexCount(), 0); +} + +void CombineTest::combineIndexedAttributesNoMeshes() { + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineIndexedAttributes({}); + CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): no meshes passed\n"); +} + +void CombineTest::combineIndexedAttributesNotIndexed() { + const UnsignedShort indices[5]{}; + Trade::MeshData a{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}}; + Trade::MeshData b{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}}; + Trade::MeshData c{MeshPrimitive::Lines, 5}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineIndexedAttributes({a, b, c}); + CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): data 2 is not indexed\n"); +} + +void CombineTest::combineIndexedAttributesDifferentPrimitive() { + const UnsignedShort indices[5]{}; + Trade::MeshData a{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}}; + Trade::MeshData b{MeshPrimitive::Points, + {}, indices, Trade::MeshIndexData{indices}}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineIndexedAttributes({a, b}); + CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): data 1 is MeshPrimitive::Points but expected MeshPrimitive::Lines\n"); +} + +void CombineTest::combineIndexedAttributesDifferentIndexCount() { + const UnsignedShort indices[5]{}; + Trade::MeshData a{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}}; + Trade::MeshData b{MeshPrimitive::Lines, + {}, indices, Trade::MeshIndexData{indices}}; + Trade::MeshData c{MeshPrimitive::Lines, + {}, indices, + Trade::MeshIndexData{Containers::arrayView(indices).prefix(4)}}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::combineIndexedAttributes({a, b, c}); + CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): data 2 has 4 indices but expected 5\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::MeshTools::Test::CombineTest) From ec02341c84414d4f71912d237c5c0a22a158245f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 2 Mar 2020 09:27:48 +0100 Subject: [PATCH 091/107] MeshTools: deprecate remaining STL-ridden APIs. The combineIndexArrays() and combineIndexedArrays() API is replaced with a more generic combineIndexedAttributes(), and thanks to that we also don't need STL-based duplicate() and removeDuplicates(). --- doc/changelog-old.dox | 6 ++-- doc/changelog.dox | 16 ++++++++-- doc/snippets/MagnumMeshTools.cpp | 18 ++++++++++- doc/snippets/MagnumShaders.cpp | 12 ++----- src/Magnum/MeshTools/CMakeLists.txt | 8 +++-- src/Magnum/MeshTools/CombineIndexedArrays.cpp | 4 +++ src/Magnum/MeshTools/CombineIndexedArrays.h | 31 ++++++++++++++++--- src/Magnum/MeshTools/CompressIndices.cpp | 2 +- src/Magnum/MeshTools/CompressIndices.h | 14 ++++----- src/Magnum/MeshTools/Duplicate.h | 18 ++++++----- src/Magnum/MeshTools/GenerateNormals.cpp | 2 ++ src/Magnum/MeshTools/RemoveDuplicates.h | 15 ++++++--- src/Magnum/MeshTools/Subdivide.h | 14 ++++++--- src/Magnum/MeshTools/Test/CMakeLists.txt | 13 ++++++-- .../Test/CombineIndexedArraysTest.cpp | 4 +++ .../MeshTools/Test/CompressIndicesTest.cpp | 11 ++++++- src/Magnum/MeshTools/Test/DuplicateTest.cpp | 8 +++++ .../MeshTools/Test/RemoveDuplicatesTest.cpp | 8 +++++ src/Magnum/MeshTools/Test/SubdivideTest.cpp | 12 +++++-- 19 files changed, 162 insertions(+), 54 deletions(-) diff --git a/doc/changelog-old.dox b/doc/changelog-old.dox index 40a962470e..4121ed192d 100644 --- a/doc/changelog-old.dox +++ b/doc/changelog-old.dox @@ -343,7 +343,7 @@ for a high-level overview. - @cpp Buffer::invalidateData() @ce, @cpp Buffer::invalidateSubData() @ce and @cpp Renderer::resetNotificationStrategy() @ce functions are enabled on OpenGL ES as a no-op -- Added @ref std::vector overload of @ref MeshTools::combineIndexArrays() +- Added @ref std::vector overload of @cpp MeshTools::combineIndexArrays() @ce for greater runtime-usage flexibility - @ref Platform::Sdl2Application now defaults to non-resizable window, you can change the behavior using @ref Platform::Sdl2Application::Configuration::setWindowFlags() @@ -391,9 +391,9 @@ for a high-level overview. which filled @cpp Mesh @ce and @cpp Buffer @ce directly are deprecated as they had undesired side-effects in some cases, use the data-returning versions instead and then configure mesh and buffer manually -- @ref MeshTools::combineIndexedArrays() taking @ref std::tuple is +- @cpp MeshTools::combineIndexedArrays() @ce taking @ref std::tuple is deprecated, use version taking @ref std::pair instead -- @ref MeshTools::removeDuplicates() taking also list of indices is +- @cpp MeshTools::removeDuplicates() @ce taking also list of indices is deprecated, use the function in conjunction with @ref MeshTools::duplicate(). See function documentation for more information. - Parameter-less @cpp Mesh::draw() @ce and @cpp MeshView::draw() @ce are diff --git a/doc/changelog.dox b/doc/changelog.dox index aa6e4b914c..92f9386bb5 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -429,6 +429,17 @@ See also: @ref MeshTools::flipFaceWindingInPlace() and @ref MeshTools::tipsifyInPlace() that accept a @ref Corrade::Containers::StridedArrayView instead of a @ref std::vector and work with 8- and 16-byte index types as well. +- `Magnum/MeshTools/CombineIndexedArrays.h`, + @cpp MeshTools::combineIndexArrays() @ce and + @cpp MeshTools::combineIndexedArrays() @ce are deprecated in favor of a more + flexible @ref MeshTools::combineIndexedAttributes() in the + @ref Magnum/MeshTools/Combine.h header +- @cpp MeshTools::compressIndicesAs() @ce, @cpp MeshTools::duplicate() @ce, + @cpp MeshTools::removeDuplicates() @ce and @cpp MeshTools::subdivide() @ce + operating on a @ref std::vector are deprecated, use the STL-free + @ref MeshTools::compressIndices(), @ref MeshTools::duplicate(), + @ref MeshTools::removeDuplicatesInPlace() and @ref MeshTools::subdivide() / + @ref MeshTools::subdivideInPlace() overloads instead @subsection changelog-latest-compatibility Potential compatibility breakages, removed APIs @@ -3214,9 +3225,10 @@ a high-level overview. - Removed deprecated `*Texture::maxLayers()` functions, use @ref GL::Shader::maxCombinedTextureImageUnits() "Shader::maxCombinedTextureImageUnits()" instead -- Removed deprecated @ref MeshTools::combineIndexedArrays(), +- Removed deprecated @cpp MeshTools::combineIndexedArrays() @ce, @ref MeshTools::compressIndices(), @ref MeshTools::interleave() and - @ref MeshTools::removeDuplicates() overloads, use the general ones instead + @cpp MeshTools::removeDuplicates() @ce overloads, use the general ones + instead - Removed deprecated `Mesh*::set*{Range,Count}()` functions, use @ref GL::Mesh::setCount() "Mesh*::setCount()" and @ref GL::MeshView::setIndexRange() "MeshView::setIndexRange()" instead diff --git a/doc/snippets/MagnumMeshTools.cpp b/doc/snippets/MagnumMeshTools.cpp index 824fcf821f..bfbab6fc08 100644 --- a/doc/snippets/MagnumMeshTools.cpp +++ b/doc/snippets/MagnumMeshTools.cpp @@ -25,7 +25,6 @@ #include "Magnum/Math/Color.h" #include "Magnum/Math/FunctionsBatch.h" -#include "Magnum/MeshTools/CombineIndexedArrays.h" #include "Magnum/MeshTools/CompressIndices.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/GenerateNormals.h" @@ -34,12 +33,19 @@ #include "Magnum/MeshTools/Transform.h" #include "Magnum/Trade/MeshData.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#define _MAGNUM_NO_DEPRECATED_COMBINEINDEXEDARRAYS +#include "Magnum/MeshTools/CombineIndexedArrays.h" +#endif + using namespace Magnum; using namespace Magnum::Math::Literals; int main() { +#ifdef MAGNUM_BUILD_DEPRECATED { +CORRADE_IGNORE_DEPRECATED_PUSH /* [combineIndexedArrays] */ std::vector vertexIndices; std::vector positions; @@ -53,7 +59,9 @@ std::vector indices = MeshTools::combineIndexedArrays( std::make_pair(std::cref(normalTextureIndices), std::ref(textureCoordinates)) ); /* [combineIndexedArrays] */ +CORRADE_IGNORE_DEPRECATED_POP } +#endif { /* [compressIndices-offset] */ @@ -66,13 +74,17 @@ std::pair, MeshIndexType> result = /* [compressIndices-offset] */ } +#ifdef MAGNUM_BUILD_DEPRECATED { +CORRADE_IGNORE_DEPRECATED_PUSH /* [compressIndicesAs] */ std::vector indices; Containers::Array indexData = MeshTools::compressIndicesAs(indices); /* [compressIndicesAs] */ +CORRADE_IGNORE_DEPRECATED_POP } +#endif { /* [generateFlatNormals] */ @@ -143,7 +155,9 @@ data = data.prefix(size); /* [removeDuplicates] */ } +#ifdef MAGNUM_BUILD_DEPRECATED { +CORRADE_IGNORE_DEPRECATED_PUSH /* [removeDuplicates-multiple] */ std::vector positions; std::vector texCoords; @@ -156,7 +170,9 @@ std::vector indices = MeshTools::combineIndexedArrays( std::make_pair(std::cref(texCoordIndices), std::ref(texCoords)) ); /* [removeDuplicates-multiple] */ +CORRADE_IGNORE_DEPRECATED_POP } +#endif { /* [transformVectors] */ diff --git a/doc/snippets/MagnumShaders.cpp b/doc/snippets/MagnumShaders.cpp index b5e51e8a42..670c68721a 100644 --- a/doc/snippets/MagnumShaders.cpp +++ b/doc/snippets/MagnumShaders.cpp @@ -290,17 +290,11 @@ mesh.addVertexBuffer(vertexIndices, 0, Shaders::MeshVisualizer::VertexIndex{}); { /* [MeshVisualizer-usage-no-geom1] */ -std::vector indices{ - // ... -}; -std::vector indexedPositions{ - // ... -}; +Containers::StridedArrayView1D indices; +Containers::StridedArrayView1D indexedPositions; /* De-indexing the position array */ -GL::Buffer vertices; -vertices.setData(MeshTools::duplicate(indices, indexedPositions), - GL::BufferUsage::StaticDraw); +GL::Buffer vertices{MeshTools::duplicate(indices, indexedPositions)}; GL::Mesh mesh; mesh.addVertexBuffer(vertices, 0, Shaders::MeshVisualizer::Position{}); diff --git a/src/Magnum/MeshTools/CMakeLists.txt b/src/Magnum/MeshTools/CMakeLists.txt index ebc59e94ff..8c3ca0e9c6 100644 --- a/src/Magnum/MeshTools/CMakeLists.txt +++ b/src/Magnum/MeshTools/CMakeLists.txt @@ -30,7 +30,6 @@ set(MagnumMeshTools_SRCS # Files compiled with different flags for main library and unit test library set(MagnumMeshTools_GracefulAssert_SRCS Combine.cpp - CombineIndexedArrays.cpp CompressIndices.cpp Duplicate.cpp FlipNormals.cpp @@ -40,7 +39,6 @@ set(MagnumMeshTools_GracefulAssert_SRCS set(MagnumMeshTools_HEADERS Combine.h - CombineIndexedArrays.h CompressIndices.h Duplicate.h FlipNormals.h @@ -57,7 +55,11 @@ set(MagnumMeshTools_INTERNAL_HEADERS Implementation/Tipsify.h) if(BUILD_DEPRECATED) - list(APPEND MagnumMeshTools_HEADERS GenerateFlatNormals.h) + list(APPEND MagnumMeshTools_GracefulAssert_SRCS + CombineIndexedArrays.cpp) + list(APPEND MagnumMeshTools_HEADERS + CombineIndexedArrays.h + GenerateFlatNormals.h) endif() if(TARGET_GL) diff --git a/src/Magnum/MeshTools/CombineIndexedArrays.cpp b/src/Magnum/MeshTools/CombineIndexedArrays.cpp index b023918167..09d8f367b9 100644 --- a/src/Magnum/MeshTools/CombineIndexedArrays.cpp +++ b/src/Magnum/MeshTools/CombineIndexedArrays.cpp @@ -23,6 +23,8 @@ DEALINGS IN THE SOFTWARE. */ +#define _MAGNUM_NO_DEPRECATED_COMBINEINDEXEDARRAYS + #include "CombineIndexedArrays.h" #include @@ -56,7 +58,9 @@ std::pair, std::vector> interleaveAndCombi /* Combine them */ std::vector combinedIndices; + CORRADE_IGNORE_DEPRECATED_PUSH std::tie(combinedIndices, interleavedArrays) = MeshTools::combineIndexArrays(interleavedArrays, stride); + CORRADE_IGNORE_DEPRECATED_POP return {combinedIndices, interleavedArrays}; } diff --git a/src/Magnum/MeshTools/CombineIndexedArrays.h b/src/Magnum/MeshTools/CombineIndexedArrays.h index daa8f122af..d946eea167 100644 --- a/src/Magnum/MeshTools/CombineIndexedArrays.h +++ b/src/Magnum/MeshTools/CombineIndexedArrays.h @@ -25,18 +25,30 @@ DEALINGS IN THE SOFTWARE. */ +#ifdef MAGNUM_BUILD_DEPRECATED /** @file * @brief Function @ref Magnum::MeshTools::combineIndexArrays(), @ref Magnum::MeshTools::combineIndexedArrays() + * @m_deprecated_since_latest Use @ref Magnum/MeshTools/Combine.h and + * @ref Magnum::MeshTools::combineIndexedAttributes() instead. */ +#endif + +#include "Magnum/configure.h" +#ifdef MAGNUM_BUILD_DEPRECATED #include #include #include #include +#include #include "Magnum/Types.h" #include "Magnum/MeshTools/visibility.h" +#ifndef _MAGNUM_NO_DEPRECATED_COMBINEINDEXEDARRAYS +CORRADE_DEPRECATED_FILE("use Magnum/Trade/MeshData.h and combineIndexedAttributes() instead") +#endif + namespace Magnum { namespace MeshTools { /** @@ -44,6 +56,7 @@ namespace Magnum { namespace MeshTools { @param[in,out] arrays Index arrays to combine. These arrays are updated in-place to contain unique combinations of the original indices. @return Resulting combined index array +@m_deprecated_since_latest Use @ref combineIndexedAttributes() instead. Creates new combined index array and updates the original ones with translation to new ones. For example, when you have position and normal array, each indexed @@ -81,13 +94,17 @@ This function calls @ref combineIndexArrays(const std::vector&, Uns internally. See also @ref combineIndexedArrays() which does the vertex data reordering automatically. */ -MAGNUM_MESHTOOLS_EXPORT std::vector combineIndexArrays(const std::vector>>& arrays); +CORRADE_DEPRECATED("use combineIndexedAttributes() instead") MAGNUM_MESHTOOLS_EXPORT std::vector combineIndexArrays(const std::vector>>& arrays); -/** @overload */ -MAGNUM_MESHTOOLS_EXPORT std::vector combineIndexArrays(std::initializer_list>> arrays); +/** +@overload +@m_deprecated_since_latest Use @ref combineIndexedAttributes() instead. +*/ +CORRADE_DEPRECATED("use combineIndexedAttributes() instead") MAGNUM_MESHTOOLS_EXPORT std::vector combineIndexArrays(std::initializer_list>> arrays); /** @brief Combine interleaved index arrays +@m_deprecated_since_latest Use @ref combineIndexedAttributes() instead. Unlike above, this function takes one interleaved array instead of separate index arrays. Continuing with the above example, you would call this function @@ -107,7 +124,7 @@ And second pair value is the cleaned up interleaved array: @see @ref combineIndexedArrays() */ -MAGNUM_MESHTOOLS_EXPORT std::pair, std::vector> combineIndexArrays(const std::vector& interleavedArrays, UnsignedInt stride); +CORRADE_DEPRECATED("use combineIndexedAttributes() instead") MAGNUM_MESHTOOLS_EXPORT std::pair, std::vector> combineIndexArrays(const std::vector& interleavedArrays, UnsignedInt stride); namespace Implementation { @@ -140,6 +157,7 @@ template inline void writeCombinedArrays(UnsignedInt stride @brief Combine indexed arrays @param[in,out] indexedArrays Index and attribute arrays @return Array with resulting indices +@m_deprecated_since_latest Use @ref combineIndexedAttributes() instead. Creates new combined index array and reorders original attribute arrays so they can be indexed with the new single index array. @@ -160,7 +178,7 @@ procedure. /* Implementation note: It's done using tuples because it is more clear which parameter is index array and which is attribute array, mainly when both are of the same type. */ -template std::vector combineIndexedArrays(const std::pair&, std::vector&>&... indexedArrays) { +template CORRADE_DEPRECATED("use combineIndexedAttributes() instead") std::vector combineIndexedArrays(const std::pair&, std::vector&>&... indexedArrays) { /* Interleave and combine index arrays */ std::vector combinedIndices; std::vector interleavedCombinedIndexArrays; @@ -174,5 +192,8 @@ template std::vector combineIndexedArrays(const std::pa } }} +#else +#error use functions in Combine.h instead +#endif #endif diff --git a/src/Magnum/MeshTools/CompressIndices.cpp b/src/Magnum/MeshTools/CompressIndices.cpp index 81ecbd47b7..b37342a584 100644 --- a/src/Magnum/MeshTools/CompressIndices.cpp +++ b/src/Magnum/MeshTools/CompressIndices.cpp @@ -178,7 +178,6 @@ std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> com std::tie(data, type) = compressIndices(indices, MeshIndexType::UnsignedByte); return std::make_tuple(std::move(data), type, minmax.first, minmax.second); } -#endif template Containers::Array compressIndicesAs(const std::vector& indices) { #if !defined(CORRADE_NO_ASSERT) || defined(CORRADE_GRACEFUL_ASSERT) @@ -196,5 +195,6 @@ template Containers::Array compressIndicesAs(const std::vector compressIndicesAs(const std::vector&); template Containers::Array compressIndicesAs(const std::vector&); template Containers::Array compressIndicesAs(const std::vector&); +#endif }} diff --git a/src/Magnum/MeshTools/CompressIndices.h b/src/Magnum/MeshTools/CompressIndices.h index 091e838290..0911a3ad0a 100644 --- a/src/Magnum/MeshTools/CompressIndices.h +++ b/src/Magnum/MeshTools/CompressIndices.h @@ -26,12 +26,11 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::compressIndices(), @ref Magnum::MeshTools::compressIndicesAs() + * @brief Function @ref Magnum::MeshTools::compressIndices() */ #include #include -#include #include "Magnum/Mesh.h" #include "Magnum/MeshTools/visibility.h" @@ -40,6 +39,7 @@ #ifdef MAGNUM_BUILD_DEPRECATED #include #include +#include #endif namespace Magnum { namespace MeshTools { @@ -178,14 +178,13 @@ sufficient. Example usage: @snippet MagnumMeshTools-gl.cpp compressIndices-stl - -@see @ref compressIndicesAs() */ CORRADE_DEPRECATED("use compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) instead") MAGNUM_MESHTOOLS_EXPORT std::tuple, MeshIndexType, UnsignedInt, UnsignedInt> compressIndices(const std::vector& indices); -#endif /** @brief Compress vertex indices as given type +@m_deprecated_since_latest Use @ref compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) + instead. The type can be either @ref Magnum::UnsignedByte "UnsignedByte", @ref Magnum::UnsignedShort "UnsignedShort" or @ref Magnum::UnsignedInt "UnsignedInt". @@ -194,16 +193,15 @@ Values in the index array are expected to be representable with given type. Example usage: @snippet MagnumMeshTools.cpp compressIndicesAs - -@see @ref compressIndices() */ -template MAGNUM_MESHTOOLS_EXPORT Containers::Array compressIndicesAs(const std::vector& indices); +template CORRADE_DEPRECATED("use compressIndices(const Containers::StridedArrayView1D&, MeshIndexType, Long) instead") MAGNUM_MESHTOOLS_EXPORT Containers::Array compressIndicesAs(const std::vector& indices); #if defined(CORRADE_TARGET_WINDOWS) && !defined(__MINGW32__) extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array compressIndicesAs(const std::vector&); extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array compressIndicesAs(const std::vector&); extern template MAGNUM_MESHTOOLS_EXPORT Containers::Array compressIndicesAs(const std::vector&); #endif +#endif }} diff --git a/src/Magnum/MeshTools/Duplicate.h b/src/Magnum/MeshTools/Duplicate.h index e5458dfabd..577bf81ee1 100644 --- a/src/Magnum/MeshTools/Duplicate.h +++ b/src/Magnum/MeshTools/Duplicate.h @@ -29,15 +29,18 @@ * @brief Function @ref Magnum::MeshTools::duplicate(), @ref Magnum::MeshTools::duplicateInto() */ -#include #include -#include #include #include "Magnum/Magnum.h" #include "Magnum/MeshTools/visibility.h" #include "Magnum/Trade/Trade.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#endif + namespace Magnum { namespace MeshTools { #ifndef DOXYGEN_GENERATING_OUTPUT @@ -56,7 +59,7 @@ indices are in range for the @p data array. If you want to fill an existing memory (or, for example a @ref std::vector), use @ref duplicateInto(). -@see @ref removeDuplicates(), @ref combineIndexedArrays() +@see @ref removeDuplicatesInPlace(), @ref combineIndexedAttributes() */ template Containers::Array duplicate(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& data) { Containers::Array out{Containers::NoInit, indices.size()}; @@ -64,17 +67,18 @@ template Containers::Array duplicate(const Containe return out; } +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief Duplicate data using given index array - -Like @ref duplicate(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&), -but putting the result into a @ref std::vector. +@m_deprecated_since_latest Use @ref duplicate(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + or @ref duplicateInto() instead. */ -template std::vector duplicate(const std::vector& indices, const std::vector& data) { +template CORRADE_DEPRECATED("use duplicate() taking a StridedArrayView instead") std::vector duplicate(const std::vector& indices, const std::vector& data) { std::vector out(indices.size()); duplicateInto(indices, data, out); return out; } +#endif /** @brief Duplicate data using an index array into given output array diff --git a/src/Magnum/MeshTools/GenerateNormals.cpp b/src/Magnum/MeshTools/GenerateNormals.cpp index 7aacd35ec2..ce6e11151c 100644 --- a/src/Magnum/MeshTools/GenerateNormals.cpp +++ b/src/Magnum/MeshTools/GenerateNormals.cpp @@ -81,7 +81,9 @@ std::pair, std::vector> generateFlatNormals(co } /* Remove duplicate normals and return */ + CORRADE_IGNORE_DEPRECATED_PUSH normalIndices = MeshTools::duplicate(normalIndices, MeshTools::removeDuplicates(normals)); + CORRADE_IGNORE_DEPRECATED_POP return {std::move(normalIndices), std::move(normals)}; } #endif diff --git a/src/Magnum/MeshTools/RemoveDuplicates.h b/src/Magnum/MeshTools/RemoveDuplicates.h index a259c1e800..512f478385 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.h +++ b/src/Magnum/MeshTools/RemoveDuplicates.h @@ -26,7 +26,7 @@ */ /** @file - * @brief Function @ref Magnum::MeshTools::removeDuplicatesInPlace(), @ref Magnum::MeshTools::removeDuplicatesIndexedInPlace(), @ref Magnum::MeshTools::removeDuplicates() + * @brief Function @ref Magnum::MeshTools::removeDuplicatesInPlace(), @ref Magnum::MeshTools::removeDuplicatesIndexedInPlace() */ #include @@ -133,11 +133,14 @@ instead. If you want to remove duplicate data from an already indexed array, use @ref removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, typename Vector::Type) instead. -See also @ref removeDuplicates(std::vector&, typename Vector::Type) for -a variant operating on a STL vector. + +If you want to remove duplicates in multiple incidental arrays, first remove +duplicates in each array separately and then combine the resulting index arrays +back into a single one using @ref combineIndexedAttributes(). */ template std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView1D& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()); +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief Remove duplicate floating-point vector data from a STL vector in-place @param[in,out] data Data array, duplicate items will be cut away with order @@ -145,6 +148,7 @@ template std::pair, std::size_t> re @param[in] epsilon Epsilon value, vertices closer than this distance will be melt together @return Resulting index array +@m_deprecated_since_latest Use @ref removeDuplicatesInPlace() instead. Similar to the above, except that it's operating on a @ref std::vector, which gets shrunk as a result (instead of the prefix size being returned). This @@ -155,7 +159,8 @@ array, and reorder the data accordingly: @snippet MagnumMeshTools.cpp removeDuplicates-multiple */ -template std::vector removeDuplicates(std::vector& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()); +template CORRADE_DEPRECATED("use removeDuplicatesInPlace() instead") std::vector removeDuplicates(std::vector& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()); +#endif /** @brief Remove duplicates from indexed floating-point vector data in-place @@ -245,6 +250,7 @@ template std::pair, std::size_t> re return {std::move(indices), size}; } +#ifdef MAGNUM_BUILD_DEPRECATED template std::vector removeDuplicates(std::vector& data, typename Vector::Type epsilon) { /* A trivial index array that'll be remapped and returned after */ std::vector indices(data.size()); @@ -253,6 +259,7 @@ template std::vector removeDuplicates(std::vector #include #include #include @@ -37,6 +36,10 @@ #include "Magnum/Magnum.h" +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#endif + namespace Magnum { namespace MeshTools { #ifndef DOXYGEN_GENERATING_OUTPUT @@ -66,19 +69,20 @@ template void subdivide(Conta subdivideInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(vertices), interpolator); } +#ifdef MAGNUM_BUILD_DEPRECATED /** @brief Subdivide a mesh - -Same as @ref subdivide(Containers::Array&, Containers::Array&vertices, Interpolator), only -working on a @ref std::vector. +@m_deprecated_since_latest Use @ref subdivide(Containers::Array&, Containers::Array&vertices, Interpolator) + or @ref subdivideInPlace() instead. */ -template void subdivide(std::vector& indices, std::vector& vertices, Interpolator interpolator) { +template CORRADE_DEPRECATED("use subdivide(Containers::Array&, Containers::Array&vertices, Interpolator) or subdivideInPlace() instead") void subdivide(std::vector& indices, std::vector& vertices, Interpolator interpolator) { CORRADE_ASSERT(!(indices.size()%3), "MeshTools::subdivide(): index count is not divisible by 3", ); vertices.resize(vertices.size() + indices.size()); indices.resize(indices.size()*4); subdivideInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(vertices), interpolator); } +#endif /** @brief Subdivide a mesh in-place diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index 29b84aba4d..cc9d558f8a 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -24,7 +24,6 @@ # corrade_add_test(MeshToolsCombineTest CombineTest.cpp LIBRARIES MagnumMeshToolsTestLib) -corrade_add_test(MeshToolsCombineIndexedArraysTest CombineIndexedArraysTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsCompressIndicesTest CompressIndicesTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsFlipNormalsTest FlipNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib) @@ -38,7 +37,6 @@ corrade_add_test(MeshToolsSubdivideRemov___Benchmark SubdivideRemoveDuplicatesBe # Graceful assert for testing set_property(TARGET - MeshToolsCombineIndexedArraysTest MeshToolsDuplicateTest MeshToolsInterleaveTest MeshToolsRemoveDuplicatesTest @@ -47,7 +45,6 @@ set_property(TARGET set_target_properties( MeshToolsCombineTest - MeshToolsCombineIndexedArraysTest MeshToolsCompressIndicesTest MeshToolsDuplicateTest MeshToolsFlipNormalsTest @@ -60,6 +57,16 @@ set_target_properties( MeshToolsSubdivideRemov___Benchmark PROPERTIES FOLDER "Magnum/MeshTools/Test") +if(BUILD_DEPRECATED) + corrade_add_test(MeshToolsCombineIndexedArraysTest CombineIndexedArraysTest.cpp LIBRARIES MagnumMeshToolsTestLib) + set_property(TARGET + MeshToolsCombineIndexedArraysTest + APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") + set_target_properties( + MeshToolsCombineIndexedArraysTest + PROPERTIES FOLDER "Magnum/MeshTools/Test") +endif() + if(BUILD_GL_TESTS) # Otherwise CMake complains that Corrade::PluginManager is not found find_package(Corrade REQUIRED PluginManager) diff --git a/src/Magnum/MeshTools/Test/CombineIndexedArraysTest.cpp b/src/Magnum/MeshTools/Test/CombineIndexedArraysTest.cpp index 7bd33f0d01..24fe08f98f 100644 --- a/src/Magnum/MeshTools/Test/CombineIndexedArraysTest.cpp +++ b/src/Magnum/MeshTools/Test/CombineIndexedArraysTest.cpp @@ -28,6 +28,8 @@ #include #include +#define _MAGNUM_NO_DEPRECATED_COMBINEINDEXEDARRAYS + #include "Magnum/Magnum.h" #include "Magnum/MeshTools/CombineIndexedArrays.h" @@ -47,6 +49,7 @@ CombineIndexedArraysTest::CombineIndexedArraysTest() { &CombineIndexedArraysTest::indexedArrays}); } +CORRADE_IGNORE_DEPRECATED_PUSH void CombineIndexedArraysTest::wrongIndexCount() { std::stringstream ss; Error redirectError{&ss}; @@ -87,6 +90,7 @@ void CombineIndexedArraysTest::indexedArrays() { CORRADE_COMPARE(array2, (std::vector{3, 4})); CORRADE_COMPARE(array3, (std::vector{6, 7})); } +CORRADE_IGNORE_DEPRECATED_POP }}}} diff --git a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp index d05320743d..476475f896 100644 --- a/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp +++ b/src/Magnum/MeshTools/Test/CompressIndicesTest.cpp @@ -58,7 +58,9 @@ struct CompressIndicesTest: TestSuite::Tester { void compressMeshDataMove(); void compressMeshDataNonIndexed(); + #ifdef MAGNUM_BUILD_DEPRECATED void compressAsShort(); + #endif }; CompressIndicesTest::CompressIndicesTest() { @@ -85,7 +87,10 @@ CompressIndicesTest::CompressIndicesTest() { &CompressIndicesTest::compressMeshDataMove, &CompressIndicesTest::compressMeshDataNonIndexed, - &CompressIndicesTest::compressAsShort}); + #ifdef MAGNUM_BUILD_DEPRECATED + &CompressIndicesTest::compressAsShort + #endif + }); } template void CompressIndicesTest::compressUnsignedByte() { @@ -318,7 +323,9 @@ void CompressIndicesTest::compressMeshDataNonIndexed() { "MeshTools::compressIndices(): mesh data not indexed\n"); } +#ifdef MAGNUM_BUILD_DEPRECATED void CompressIndicesTest::compressAsShort() { + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE_AS(MeshTools::compressIndicesAs({123, 456}), Containers::arrayView({123, 456}), TestSuite::Compare::Container); @@ -327,7 +334,9 @@ void CompressIndicesTest::compressAsShort() { Error redirectError{&out}; MeshTools::compressIndicesAs({65536}); CORRADE_COMPARE(out.str(), "MeshTools::compressIndicesAs(): type too small to represent value 65536\n"); + CORRADE_IGNORE_DEPRECATED_POP } +#endif }}}} diff --git a/src/Magnum/MeshTools/Test/DuplicateTest.cpp b/src/Magnum/MeshTools/Test/DuplicateTest.cpp index c438b61780..058373e050 100644 --- a/src/Magnum/MeshTools/Test/DuplicateTest.cpp +++ b/src/Magnum/MeshTools/Test/DuplicateTest.cpp @@ -42,7 +42,9 @@ struct DuplicateTest: TestSuite::Tester { void duplicate(); void duplicateOutOfBounds(); + #ifdef MAGNUM_BUILD_DEPRECATED void duplicateStl(); + #endif void duplicateInto(); void duplicateIntoWrongSize(); @@ -67,7 +69,9 @@ struct DuplicateTest: TestSuite::Tester { DuplicateTest::DuplicateTest() { addTests({&DuplicateTest::duplicate, &DuplicateTest::duplicateOutOfBounds, + #ifdef MAGNUM_BUILD_DEPRECATED &DuplicateTest::duplicateStl, + #endif &DuplicateTest::duplicateInto, &DuplicateTest::duplicateIntoWrongSize, @@ -116,10 +120,14 @@ void DuplicateTest::duplicateOutOfBounds() { "MeshTools::duplicateInto(): index 4 out of bounds for 4 elements\n"); } +#ifdef MAGNUM_BUILD_DEPRECATED void DuplicateTest::duplicateStl() { + CORRADE_IGNORE_DEPRECATED_PUSH CORRADE_COMPARE(MeshTools::duplicate({1, 1, 0, 3, 2, 2}, std::vector{-7, 35, 12, -18}), (std::vector{35, 35, -7, -18, 12, 12})); + CORRADE_IGNORE_DEPRECATED_POP } +#endif void DuplicateTest::duplicateInto() { constexpr UnsignedByte indices[]{1, 1, 0, 3, 2, 2}; diff --git a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp index 59109cf7d6..5d80a732bc 100644 --- a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp +++ b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp @@ -45,7 +45,9 @@ struct RemoveDuplicatesTest: TestSuite::Tester { void removeDuplicatesIndexedInPlaceEmptyIndicesVertices(); void removeDuplicatesFuzzyInPlace(); + #ifdef MAGNUM_BUILD_DEPRECATED void removeDuplicatesFuzzyStl(); + #endif template void removeDuplicatesFuzzyIndexedInPlace(); void removeDuplicatesFuzzyIndexedInPlaceSmallType(); void removeDuplicatesFuzzyIndexedInPlaceEmptyIndices(); @@ -66,7 +68,9 @@ RemoveDuplicatesTest::RemoveDuplicatesTest() { &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceEmptyIndicesVertices, &RemoveDuplicatesTest::removeDuplicatesFuzzyInPlace, + #ifdef MAGNUM_BUILD_DEPRECATED &RemoveDuplicatesTest::removeDuplicatesFuzzyStl, + #endif &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, @@ -173,6 +177,7 @@ void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlace() { TestSuite::Compare::Container); } +#ifdef MAGNUM_BUILD_DEPRECATED void RemoveDuplicatesTest::removeDuplicatesFuzzyStl() { /* Same but with implicit bloat. HEH HEH */ std::vector data{ @@ -182,7 +187,9 @@ void RemoveDuplicatesTest::removeDuplicatesFuzzyStl() { {1, 5} }; + CORRADE_IGNORE_DEPRECATED_PUSH const std::vector indices = MeshTools::removeDuplicates(data, 2); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE_AS(indices, (std::vector{0, 0, 1, 1}), TestSuite::Compare::Container); @@ -190,6 +197,7 @@ void RemoveDuplicatesTest::removeDuplicatesFuzzyStl() { (std::vector{{1, 0}, {0, 4}}), TestSuite::Compare::Container); } +#endif template void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace() { setTestCaseTemplateName(Math::TypeTraits::name()); diff --git a/src/Magnum/MeshTools/Test/SubdivideTest.cpp b/src/Magnum/MeshTools/Test/SubdivideTest.cpp index 865d1299ea..f6f804a51c 100644 --- a/src/Magnum/MeshTools/Test/SubdivideTest.cpp +++ b/src/Magnum/MeshTools/Test/SubdivideTest.cpp @@ -37,7 +37,9 @@ struct SubdivideTest: TestSuite::Tester { explicit SubdivideTest(); void subdivide(); + #ifdef MAGNUM_BUILD_DEPRECATED void subdivideStl(); + #endif void subdivideWrongIndexCount(); template void subdivideInPlace(); void subdivideInPlaceWrongIndexCount(); @@ -52,7 +54,9 @@ inline Vector1 interpolator(Vector1 a, Vector1 b) { return (a[0]+b[0])/2; } SubdivideTest::SubdivideTest() { addTests({&SubdivideTest::subdivide, + #ifdef MAGNUM_BUILD_DEPRECATED &SubdivideTest::subdivideStl, + #endif &SubdivideTest::subdivideWrongIndexCount, &SubdivideTest::subdivideInPlace, &SubdivideTest::subdivideInPlace, @@ -74,10 +78,13 @@ void SubdivideTest::subdivide() { }), TestSuite::Compare::Container); } +#ifdef MAGNUM_BUILD_DEPRECATED void SubdivideTest::subdivideStl() { std::vector positions{0, 2, 6, 8}; std::vector indices{0, 1, 2, 1, 2, 3}; + CORRADE_IGNORE_DEPRECATED_PUSH MeshTools::subdivide(indices, positions, interpolator); + CORRADE_IGNORE_DEPRECATED_POP CORRADE_COMPARE_AS(indices, (std::vector{4, 5, 6, 7, 8, 9, 0, 4, 6, 4, 1, 5, 6, 5, 2, 1, 7, 9, 7, 2, 8, 9, 8, 3}), @@ -86,13 +93,14 @@ void SubdivideTest::subdivideStl() { (std::vector{0, 2, 6, 8, 1, 4, 3, 4, 7, 5}), TestSuite::Compare::Container); } +#endif void SubdivideTest::subdivideWrongIndexCount() { std::stringstream out; Error redirectError{&out}; - std::vector positions; - std::vector indices{0, 1}; + Containers::Array positions; + Containers::Array indices{2}; MeshTools::subdivide(indices, positions, interpolator); CORRADE_COMPARE(out.str(), "MeshTools::subdivide(): index count is not divisible by 3\n"); } From 8a6cceab6c356427bdc8eddc7859a4e5c2921245 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 2 Mar 2020 12:13:09 +0100 Subject: [PATCH 092/107] MeshTools: added removeDuplicates() / removeDuplicatesInto(). Those work without modifying the input data. The *Indexed and fuzzy variants are missing as I don't need those right now, but might get added later. --- src/Magnum/MeshTools/RemoveDuplicates.cpp | 43 ++++++++++++++++++- src/Magnum/MeshTools/RemoveDuplicates.h | 37 ++++++++++++++-- .../MeshTools/Test/RemoveDuplicatesTest.cpp | 40 +++++++++++------ 3 files changed, 102 insertions(+), 18 deletions(-) diff --git a/src/Magnum/MeshTools/RemoveDuplicates.cpp b/src/Magnum/MeshTools/RemoveDuplicates.cpp index 52f30afcbe..965ee0a283 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.cpp +++ b/src/Magnum/MeshTools/RemoveDuplicates.cpp @@ -44,6 +44,43 @@ struct ArrayHash { } }; +std::size_t removeDuplicatesInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices) { + /* Assuming the second dimension is contiguous so we can calculate the + hashes easily */ + CORRADE_ASSERT(data.empty()[0] || data.isContiguous<1>(), + "MeshTools::removeDuplicatesInto(): second data view dimension is not contiguous", {}); + + const std::size_t dataSize = data.size()[0]; + CORRADE_ASSERT(indices.size() == dataSize, + "MeshTools::removeDuplicatesInto(): output index array has" << indices.size() << "elements but expected" << dataSize, {}); + + /* Table containing index of first occurence for each unique entry. + Reserving more buckets than necessary (i.e. as if each entry was + unique). */ + std::unordered_map, UnsignedInt, ArrayHash, ArrayEqual> table{dataSize}; + + /* Go through all entries */ + for(std::size_t i = 0; i != dataSize; ++i) { + /* Try to insert new entry into the table. The inserted index points + into the original unchanged data array. */ + const Containers::ArrayView entry = data[i].asContiguous(); + const auto result = table.emplace(entry, i); + + /* Put the (either new or already existing) index into the output + index array */ + indices[i] = result.first->second; + } + + CORRADE_INTERNAL_ASSERT(dataSize >= table.size()); + return table.size(); +} + +std::pair, std::size_t> removeDuplicates(const Containers::StridedArrayView2D& data) { + Containers::Array indices{Containers::NoInit, data.size()[0]}; + const std::size_t size = removeDuplicatesInto(data, indices); + return {std::move(indices), size}; +} + std::size_t removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices) { /* Assuming the second dimension is contiguous so we can calculate the hashes easily */ @@ -61,11 +98,13 @@ std::size_t removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D entry = data[i].asContiguous(); const auto result = table.emplace(entry, table.size()); - /* Add the (either new or already existing) index into the array */ + /* Put the (either new or already existing) index into the output index + array */ indices[i] = result.first->second; /* If this is a new combination, copy the data to new (earlier) diff --git a/src/Magnum/MeshTools/RemoveDuplicates.h b/src/Magnum/MeshTools/RemoveDuplicates.h index 512f478385..d45debfc77 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.h +++ b/src/Magnum/MeshTools/RemoveDuplicates.h @@ -56,8 +56,8 @@ namespace Implementation { @brief Remove duplicate data from given array in-place @param[in,out] data Data array, duplicate items will be cut away with order preserved -@return Size of unique prefix in the cleaned up @p data array and the resulting - index array +@return The resulting index array and size of unique prefix in the cleaned up + @p data array @m_since_latest Removes duplicate data from given array by comparing the second dimension of @@ -70,7 +70,11 @@ instead. Usage example: @snippet MagnumMeshTools.cpp removeDuplicates -@see @ref Corrade::Containers::StridedArrayView::isContiguous() +See @ref removeDuplicates(const Containers::StridedArrayView2D&) +for a variant that doesn't modify the input data in any way but instead returns +an index array pointing to original data locations. +@see @ref Corrade::Containers::StridedArrayView::isContiguous(), + @ref removeDuplicatesInPlaceInto() */ MAGNUM_MESHTOOLS_EXPORT std::pair, std::size_t> removeDuplicatesInPlace(const Containers::StridedArrayView2D& data); @@ -84,9 +88,36 @@ MAGNUM_MESHTOOLS_EXPORT std::pair, std::size_t> r Same as above, except that the index array is not allocated but put into @p indices instead. Expects that @p indices has the same size as @p data. +@see @ref removeDuplicatesInto() */ MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices); +/** +@brief Remove duplicate data from given array +@param[in] data Data array +@return The resulting index array and count of unique items in the original + @p data array +@m_since_latest + +Compared to @ref removeDuplicatesInPlace(const Containers::StridedArrayView2D&) +this function doesn't modify the input data array in any way but instead +returns an index array pointing to original data locations. +*/ +MAGNUM_MESHTOOLS_EXPORT std::pair, std::size_t> removeDuplicates(const Containers::StridedArrayView2D& data); + +/** +@brief Remove duplicate data from given array +@param[in] data Data array +@param[out] indices Where to put the resulting index array +@return Count of unique items in the original @p data array +@m_since_latest + +Compared to @ref removeDuplicatesInPlaceInto(const Containers::StridedArrayView2D&, const Containers::StridedArrayView1D&) +this function doesn't modify the input data array in any way but instead +makes an index array pointing to original data locations. +*/ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices); + /** @brief Remove duplicates from indexed data in-place @param[in,out] indices Index array, which will get remapped to list just diff --git a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp index 5d80a732bc..64a6cba7cc 100644 --- a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp +++ b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp @@ -36,9 +36,9 @@ namespace Magnum { namespace MeshTools { namespace Test { namespace { struct RemoveDuplicatesTest: TestSuite::Tester { explicit RemoveDuplicatesTest(); - void removeDuplicatesInPlace(); - void removeDuplicatesInPlaceNonContiguous(); - void removeDuplicatesInPlaceIntoWrongOutputSize(); + void removeDuplicates(); + void removeDuplicatesNonContiguous(); + void removeDuplicatesIntoWrongOutputSize(); template void removeDuplicatesIndexedInPlace(); void removeDuplicatesIndexedInPlaceSmallType(); void removeDuplicatesIndexedInPlaceEmptyIndices(); @@ -57,9 +57,9 @@ struct RemoveDuplicatesTest: TestSuite::Tester { }; RemoveDuplicatesTest::RemoveDuplicatesTest() { - addTests({&RemoveDuplicatesTest::removeDuplicatesInPlace, - &RemoveDuplicatesTest::removeDuplicatesInPlaceNonContiguous, - &RemoveDuplicatesTest::removeDuplicatesInPlaceIntoWrongOutputSize, + addTests({&RemoveDuplicatesTest::removeDuplicates, + &RemoveDuplicatesTest::removeDuplicatesNonContiguous, + &RemoveDuplicatesTest::removeDuplicatesIntoWrongOutputSize, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlace, @@ -79,38 +79,52 @@ RemoveDuplicatesTest::RemoveDuplicatesTest() { &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices}); } -void RemoveDuplicatesTest::removeDuplicatesInPlace() { +void RemoveDuplicatesTest::removeDuplicates() { Int data[]{-15, 32, 24, -15, 15, 7541, 24, 32}; std::pair, std::size_t> result = - MeshTools::removeDuplicatesInPlace(Containers::arrayCast<2, char>(Containers::arrayView(data))); + MeshTools::removeDuplicates(Containers::arrayCast<2, char>(Containers::arrayView(data))); CORRADE_COMPARE_AS(Containers::arrayView(result.first), + Containers::arrayView({0, 1, 2, 0, 4, 5, 2, 1}), + TestSuite::Compare::Container); + + std::pair, std::size_t> resultInPlace = + MeshTools::removeDuplicatesInPlace(Containers::arrayCast<2, char>(Containers::arrayView(data))); + CORRADE_COMPARE_AS(Containers::arrayView(resultInPlace.first), Containers::arrayView({0, 1, 2, 0, 3, 4, 2, 1}), TestSuite::Compare::Container); - CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(result.second), + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(resultInPlace.second), Containers::arrayView({-15, 32, 24, 15, 7541}), TestSuite::Compare::Container); } -void RemoveDuplicatesTest::removeDuplicatesInPlaceNonContiguous() { +void RemoveDuplicatesTest::removeDuplicatesNonContiguous() { Int data[8]{}; std::ostringstream out; Error redirectError{&out}; + MeshTools::removeDuplicates(Containers::arrayCast<2, const char>(Containers::arrayView(data)).every({1, 2})); MeshTools::removeDuplicatesInPlace(Containers::arrayCast<2, char>(Containers::arrayView(data)).every({1, 2})); - CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesInPlaceInto(): second data view dimension is not contiguous\n"); + CORRADE_COMPARE(out.str(), + "MeshTools::removeDuplicatesInto(): second data view dimension is not contiguous\n" + "MeshTools::removeDuplicatesInPlaceInto(): second data view dimension is not contiguous\n"); } -void RemoveDuplicatesTest::removeDuplicatesInPlaceIntoWrongOutputSize() { +void RemoveDuplicatesTest::removeDuplicatesIntoWrongOutputSize() { Int data[8]{}; UnsignedInt output[7]; std::ostringstream out; Error redirectError{&out}; + MeshTools::removeDuplicatesInto( + Containers::arrayCast<2, const char>(Containers::arrayView(data)), + output); MeshTools::removeDuplicatesInPlaceInto( Containers::arrayCast<2, char>(Containers::arrayView(data)), output); - CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesInPlaceInto(): output index array has 7 elements but expected 8\n"); + CORRADE_COMPARE(out.str(), + "MeshTools::removeDuplicatesInto(): output index array has 7 elements but expected 8\n" + "MeshTools::removeDuplicatesInPlaceInto(): output index array has 7 elements but expected 8\n"); } template void RemoveDuplicatesTest::removeDuplicatesIndexedInPlace() { From 0c76896458eabfff2bb52aedc0073325b76fd8e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 2 Mar 2020 21:55:10 +0100 Subject: [PATCH 093/107] Trade: various MSVC workarounds. --- src/Magnum/Trade/Test/MeshDataTest.cpp | 30 +++++++++++++++----------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index a72aaee30e..70448ce10f 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -675,7 +675,12 @@ void MeshDataTest::constructAttributeWrongDataAccess() { "Trade::MeshAttributeData::data(): the attribute is a relative offset, supply a data array\n"); } -constexpr Vector2 ArrayVertexData[3*4]; +constexpr Vector2 ArrayVertexData[3*4] + /* MSVC 2015 needs an explicit initializer. GCC 4.8 *doesn't*. */ + #ifdef CORRADE_MSVC2015_COMPATIBILITY + {} + #endif + ; void MeshDataTest::constructArrayAttribute() { Vector2 vertexData[3*4]; @@ -2063,18 +2068,22 @@ void MeshDataTest::colorsIntoArrayInvalidSize() { "Trade::MeshData::colorsInto(): expected a view with 3 elements but got 2\n"); } +/* MSVC 2015 doesn't like anonymous bitfields in inline structs, so putting the + declaration outside */ +struct VertexWithImplementationSpecificData { + Long:64; + long double thing; +}; + void MeshDataTest::implementationSpecificVertexFormat() { - struct Vertex { - Long:64; - long double thing; - } vertexData[] { + VertexWithImplementationSpecificData vertexData[] { {456.0l}, {456.0l} }; /* Constructing should work w/o asserts */ Containers::StridedArrayView1D attribute{vertexData, - &vertexData[0].thing, 2, sizeof(Vertex)}; + &vertexData[0].thing, 2, sizeof(VertexWithImplementationSpecificData)}; MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { MeshAttributeData{MeshAttribute::Position, vertexFormatWrap(0xdead1), attribute}, @@ -2095,7 +2104,7 @@ void MeshDataTest::implementationSpecificVertexFormat() { CORRADE_COMPARE(data.attributeFormat(name), vertexFormatWrap(format++)); /* The actual type size is unknown, so this will use the full stride */ - CORRADE_COMPARE(data.attribute(name).size()[1], sizeof(Vertex)); + CORRADE_COMPARE(data.attribute(name).size()[1], sizeof(VertexWithImplementationSpecificData)); CORRADE_COMPARE_AS((Containers::arrayCast<1, const long double>( data.attribute(name).prefix({2, sizeof(long double)}))), @@ -2107,16 +2116,13 @@ void MeshDataTest::implementationSpecificVertexFormat() { } void MeshDataTest::implementationSpecificVertexFormatWrongAccess() { - struct Vertex { - Long:64; - long double thing; - } vertexData[] { + VertexWithImplementationSpecificData vertexData[] { {456.0l}, {456.0l} }; Containers::StridedArrayView1D attribute{vertexData, - &vertexData[0].thing, 2, sizeof(Vertex)}; + &vertexData[0].thing, 2, sizeof(VertexWithImplementationSpecificData)}; MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { MeshAttributeData{MeshAttribute::Position, vertexFormatWrap(0xdead1), attribute}, From 12044b8c4da89c1f5648856c790d9048703fdba1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 3 Mar 2020 13:04:18 +0100 Subject: [PATCH 094/107] MeshTools: add type-erased flipNormals() and flipFaceWinding(). --- src/Magnum/MeshTools/FlipNormals.cpp | 12 ++++++ src/Magnum/MeshTools/FlipNormals.h | 27 +++++++++++++ src/Magnum/MeshTools/Test/FlipNormalsTest.cpp | 40 ++++++++++++++++++- 3 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/Magnum/MeshTools/FlipNormals.cpp b/src/Magnum/MeshTools/FlipNormals.cpp index 05089b1325..6adde3a47f 100644 --- a/src/Magnum/MeshTools/FlipNormals.cpp +++ b/src/Magnum/MeshTools/FlipNormals.cpp @@ -56,6 +56,18 @@ void flipFaceWindingInPlace(const Containers::StridedArrayView1D& flipFaceWindingInPlaceImplementation(indices); } +void flipFaceWindingInPlace(const Containers::StridedArrayView2D& indices) { + CORRADE_ASSERT(indices.isContiguous<1>(), "MeshTools::flipFaceWindingInPlace(): second index view dimension is not contiguous", ); + if(indices.size()[1] == 4) + return flipFaceWindingInPlaceImplementation(Containers::arrayCast<1, UnsignedInt>(indices)); + else if(indices.size()[1] == 2) + return flipFaceWindingInPlaceImplementation(Containers::arrayCast<1, UnsignedShort>(indices)); + else { + CORRADE_ASSERT(indices.size()[1] == 1, "MeshTools::flipFaceWindingInPlace(): expected index type size 1, 2 or 4 but got" << indices.size()[1], ); + return flipFaceWindingInPlaceImplementation(Containers::arrayCast<1, UnsignedByte>(indices)); + } +} + void flipNormalsInPlace(const Containers::StridedArrayView1D& normals) { for(Vector3& normal: normals) normal = -normal; diff --git a/src/Magnum/MeshTools/FlipNormals.h b/src/Magnum/MeshTools/FlipNormals.h index de9e863ec9..65e67084c2 100644 --- a/src/Magnum/MeshTools/FlipNormals.h +++ b/src/Magnum/MeshTools/FlipNormals.h @@ -67,6 +67,17 @@ void flipNormalsInPlace(const Containers::StridedArrayView1D& ind */ void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals); +/** +@brief Flip mesh normals and face winding in-place on a type-erased index array +@m_since_latest + +Expects that the second dimension of @p indices is contiguous and represents +the actual 1/2/4-byte index type. Based on its size then calls one of the +@ref flipNormalsInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) +etc. overloads. +*/ +void flipNormalsInPlace(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView1D& normals); + #ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief flipNormalsInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) @@ -99,6 +110,17 @@ void MAGNUM_MESHTOOLS_EXPORT flipFaceWindingInPlace(const Containers::StridedArr */ void MAGNUM_MESHTOOLS_EXPORT flipFaceWindingInPlace(const Containers::StridedArrayView1D& indices); +/** +@brief Flip face winding in-place on a type-erased index array +@m_since_latest + +Expects that the second dimension of @p indices is contiguous and represents +the actual 1/2/4-byte index type. Based on its size then calls one of the +@ref flipFaceWindingInPlace(const Containers::StridedArrayView1D&) +etc. overloads. +*/ +void MAGNUM_MESHTOOLS_EXPORT flipFaceWindingInPlace(const Containers::StridedArrayView2D& indices); + #ifdef MAGNUM_BUILD_DEPRECATED /** @brief @copybrief flipFaceWindingInPlace(const Containers::StridedArrayView1D&) @@ -141,6 +163,11 @@ inline void flipNormalsInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& normals) { + flipFaceWindingInPlace(indices); + flipNormalsInPlace(normals); +} + }} #endif diff --git a/src/Magnum/MeshTools/Test/FlipNormalsTest.cpp b/src/Magnum/MeshTools/Test/FlipNormalsTest.cpp index 9f2049fc4e..626aa6040b 100644 --- a/src/Magnum/MeshTools/Test/FlipNormalsTest.cpp +++ b/src/Magnum/MeshTools/Test/FlipNormalsTest.cpp @@ -39,9 +39,11 @@ struct FlipNormalsTest: TestSuite::Tester { void wrongIndexCount(); template void flipFaceWinding(); + template void flipFaceWindingErased(); void flipNormals(); template void flipNormalsFaceWinding(); + template void flipNormalsFaceWindingErased(); }; FlipNormalsTest::FlipNormalsTest() { @@ -49,11 +51,18 @@ FlipNormalsTest::FlipNormalsTest() { &FlipNormalsTest::flipFaceWinding, &FlipNormalsTest::flipFaceWinding, &FlipNormalsTest::flipFaceWinding, + &FlipNormalsTest::flipFaceWindingErased, + &FlipNormalsTest::flipFaceWindingErased, + &FlipNormalsTest::flipFaceWindingErased, &FlipNormalsTest::flipNormals, &FlipNormalsTest::flipNormalsFaceWinding, &FlipNormalsTest::flipNormalsFaceWinding, - &FlipNormalsTest::flipNormalsFaceWinding}); + &FlipNormalsTest::flipNormalsFaceWinding, + &FlipNormalsTest::flipNormalsFaceWindingErased, + &FlipNormalsTest::flipNormalsFaceWindingErased, + &FlipNormalsTest::flipNormalsFaceWindingErased, + }); } void FlipNormalsTest::wrongIndexCount() { @@ -77,6 +86,17 @@ template void FlipNormalsTest::flipFaceWinding() { TestSuite::Compare::Container); } +template void FlipNormalsTest::flipFaceWindingErased() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[]{0, 1, 2, 3, 4, 5}; + MeshTools::flipFaceWindingInPlace(indices); + + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({0, 2, 1, 3, 5, 4}), + TestSuite::Compare::Container); +} + void FlipNormalsTest::flipNormals() { Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}; MeshTools::flipNormalsInPlace(normals); @@ -103,6 +123,24 @@ template void FlipNormalsTest::flipNormalsFaceWinding() { }), TestSuite::Compare::Container); } +template void FlipNormalsTest::flipNormalsFaceWindingErased() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + T indices[]{0, 1, 2, 3, 4, 5}; + Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}; + MeshTools::flipNormalsInPlace( + Containers::arrayCast<2, char>(Containers::stridedArrayView(indices)), + normals); + + CORRADE_COMPARE_AS(Containers::arrayView(indices), + Containers::arrayView({0, 2, 1, 3, 5, 4}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(normals), + Containers::arrayView({ + -Vector3::xAxis(), -Vector3::yAxis(), -Vector3::zAxis() + }), TestSuite::Compare::Container); +} + }}}} CORRADE_TEST_MAIN(Magnum::MeshTools::Test::FlipNormalsTest) From e30d9de7afbbfff91e36a22ef3d1ddb974eb176e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 3 Mar 2020 13:33:45 +0100 Subject: [PATCH 095/107] MeshTools: ensure a corner case in interleave() is tested as well. --- src/Magnum/MeshTools/Test/InterleaveTest.cpp | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Magnum/MeshTools/Test/InterleaveTest.cpp b/src/Magnum/MeshTools/Test/InterleaveTest.cpp index e417ac3fd3..adebe44b2b 100644 --- a/src/Magnum/MeshTools/Test/InterleaveTest.cpp +++ b/src/Magnum/MeshTools/Test/InterleaveTest.cpp @@ -44,8 +44,9 @@ struct InterleaveTest: Corrade::TestSuite::Tester { void attributeCountGaps(); void stride(); void strideGaps(); - void write(); - void writeGaps(); + void interleave(); + void interleaveGaps(); + void interleaveEmpty(); void interleaveInto(); @@ -84,8 +85,9 @@ InterleaveTest::InterleaveTest() { &InterleaveTest::attributeCountGaps, &InterleaveTest::stride, &InterleaveTest::strideGaps, - &InterleaveTest::write, - &InterleaveTest::writeGaps, + &InterleaveTest::interleave, + &InterleaveTest::interleaveGaps, + &InterleaveTest::interleaveEmpty, &InterleaveTest::interleaveInto, @@ -148,7 +150,7 @@ void InterleaveTest::strideGaps() { CORRADE_COMPARE((Implementation::Stride{}(2, std::vector(), 1, std::vector(), 12)), std::size_t(20)); } -void InterleaveTest::write() { +void InterleaveTest::interleave() { const Containers::Array data = MeshTools::interleave( std::vector{0, 1, 2}, std::vector{3, 4, 5}, @@ -169,7 +171,7 @@ void InterleaveTest::write() { } } -void InterleaveTest::writeGaps() { +void InterleaveTest::interleaveGaps() { const Containers::Array data = MeshTools::interleave( std::vector{0, 1, 2}, 3, std::vector{3, 4, 5}, @@ -192,6 +194,11 @@ void InterleaveTest::writeGaps() { } } +void InterleaveTest::interleaveEmpty() { + const Containers::Array data = MeshTools::interleave(std::vector{}, 2); + CORRADE_COMPARE(data.size(), 0); +} + void InterleaveTest::interleaveInto() { Containers::Array data{Containers::InPlaceInit, { 0x11, 0x33, 0x55, 0x77, 0x11, 0x33, 0x55, 0x77, 0x11, 0x33, 0x55, 0x77, From 96368b9e81ad9a4c587342c478f2f58b304f76ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 3 Mar 2020 17:08:36 +0100 Subject: [PATCH 096/107] MeshTools: doc++ --- doc/snippets/MagnumMeshTools-gl.cpp | 9 ++--- doc/snippets/MagnumMeshTools-stl.cpp | 1 + doc/snippets/MagnumMeshTools.cpp | 6 ++-- src/Magnum/MeshTools/Duplicate.h | 8 ++--- src/Magnum/MeshTools/Interleave.h | 45 ++++++++++++------------- src/Magnum/MeshTools/RemoveDuplicates.h | 4 +-- 6 files changed, 37 insertions(+), 36 deletions(-) diff --git a/doc/snippets/MagnumMeshTools-gl.cpp b/doc/snippets/MagnumMeshTools-gl.cpp index bd93ffadae..268041f98f 100644 --- a/doc/snippets/MagnumMeshTools-gl.cpp +++ b/doc/snippets/MagnumMeshTools-gl.cpp @@ -115,15 +115,16 @@ struct MyShader { typedef GL::Attribute<0, Vector2> TextureCoordinates; }; /* [interleave1] */ -std::vector positions; -std::vector textureCoordinates; +Containers::ArrayView positions; +Containers::ArrayView textureCoordinates; GL::Buffer vertexBuffer; -vertexBuffer.setData(MeshTools::interleave(positions, textureCoordinates), GL::BufferUsage::StaticDraw); +vertexBuffer.setData(MeshTools::interleave(positions, textureCoordinates)); GL::Mesh mesh; mesh.setCount(positions.size()) - .addVertexBuffer(vertexBuffer, 0, MyShader::Position{}, MyShader::TextureCoordinates{}); + .addVertexBuffer(vertexBuffer, 0, MyShader::Position{}, + MyShader::TextureCoordinates{}); /* [interleave1] */ } diff --git a/doc/snippets/MagnumMeshTools-stl.cpp b/doc/snippets/MagnumMeshTools-stl.cpp index dc01571740..ba3c634d76 100644 --- a/doc/snippets/MagnumMeshTools-stl.cpp +++ b/doc/snippets/MagnumMeshTools-stl.cpp @@ -26,6 +26,7 @@ #include #include +#include "Magnum/Math/Vector3.h" #include "Magnum/MeshTools/GenerateNormals.h" using namespace Magnum; diff --git a/doc/snippets/MagnumMeshTools.cpp b/doc/snippets/MagnumMeshTools.cpp index bfbab6fc08..7c13141b42 100644 --- a/doc/snippets/MagnumMeshTools.cpp +++ b/doc/snippets/MagnumMeshTools.cpp @@ -101,9 +101,9 @@ Containers::Array normals = { /* [interleave2] */ -std::vector positions; -std::vector weights; -std::vector vertexColors; +Containers::ArrayView positions; +Containers::ArrayView weights; +Containers::ArrayView vertexColors; auto data = MeshTools::interleave(positions, weights, 2, vertexColors, 1); /* [interleave2] */ diff --git a/src/Magnum/MeshTools/Duplicate.h b/src/Magnum/MeshTools/Duplicate.h index 577bf81ee1..80c1f255f5 100644 --- a/src/Magnum/MeshTools/Duplicate.h +++ b/src/Magnum/MeshTools/Duplicate.h @@ -52,10 +52,10 @@ template void duplicateInto(const Containers::StridedA @brief Duplicate data using given index array @m_since{2019,10} -Converts indexed array to non-indexed, for example data `{a, b, c, d}` with -index array `{1, 1, 0, 3, 2, 2}` will be converted to `{b, b, a, d, c, c}`. -The resulting array size is the same as size of @p indices, expects that all -indices are in range for the @p data array. +Converts indexed array to non-indexed, for example data @cpp {a, b, c, d} @ce +with index array @cpp {1, 1, 0, 3, 2, 2} @ce will be converted to +@cpp {b, b, a, d, c, c} @ce. The resulting array size is the same as size of +@p indices, expects that all indices are in range for the @p data array. If you want to fill an existing memory (or, for example a @ref std::vector), use @ref duplicateInto(). diff --git a/src/Magnum/MeshTools/Interleave.h b/src/Magnum/MeshTools/Interleave.h index 98de3b6fa2..d1851de479 100644 --- a/src/Magnum/MeshTools/Interleave.h +++ b/src/Magnum/MeshTools/Interleave.h @@ -120,7 +120,8 @@ template void writeInterleaved(std::size_t stride, char* st @brief Interleave vertex attributes This function takes list of attribute arrays and returns them interleaved, so -data for each attribute are in continuous place in memory. +data for each attribute are in continuous place in memory. Expects that all +attributes have the same element count. Example usage: @@ -134,14 +135,12 @@ achieve that, you can specify gaps between the attributes: All gap bytes are set zero. This way vertex stride is 24 bytes, without gaps it would be 21 bytes, causing possible performance loss. -@attention The function expects that all arrays have the same size. - -@note The only requirements to attribute array type is that it must have either - a typedef `T::Type` (in case of Corrade types such as - @ref Corrade::Containers::ArrayView) or a typedef `T::value_type` (in case - of STL types such as @ref std::vector or @ref std::array) or a, a forward - iterator (to be used with range-based for) and a function `size()` - returning count of elements. +@note The only requirements to the attribute array type is that it must have + either a typedef @cpp T::Type @ce (in case of Corrade types such as + @ref Corrade::Containers::ArrayView) or a typedef @cpp T::value_type @ce + (in case of STL types such as @ref std::vector or @ref std::array), a + forward iterator (to be used with range-based for) and a @cpp size() @ce + method returning element count. @see @ref interleaveInto() */ @@ -173,11 +172,8 @@ template void interleaveInto(Containers::ArrayView buffer, const T& first, const U&... next) { /* Verify expected buffer size */ @@ -243,17 +239,20 @@ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(const Trade::MeshData& @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. Note that offset-only @ref Trade::MeshAttributeData instances are -not supported in the @p extra array. +Returns a copy of @p data with all attributes interleaved. Indices (if any) are +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. Note that offset-only +@ref Trade::MeshAttributeData instances are not supported in the @p extra +array. Expects that each attribute in @p extra has either the same amount of elements -as @p data vertex count or has none. +as @p data vertex count or has none. This function will unconditionally make a +copy of all data even if @p data is already interleaved and needs no change, +use @ref interleave(Trade::MeshData&&, Containers::ArrayView) +to avoid that copy. @see @ref isInterleaved(), @ref Trade::MeshData::attributeData() */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(const Trade::MeshData& data, Containers::ArrayView extra = {}); diff --git a/src/Magnum/MeshTools/RemoveDuplicates.h b/src/Magnum/MeshTools/RemoveDuplicates.h index d45debfc77..b284282cc2 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.h +++ b/src/Magnum/MeshTools/RemoveDuplicates.h @@ -128,7 +128,7 @@ MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesInto(const Containers::Strid @m_since_latest Compared to @ref removeDuplicatesInPlace(const Containers::StridedArrayView2D&) -this variant is more suited for data that are already indexed as it works on +this variant is more suited for data that is already indexed as it works on the existing index array instead of allocating a new one. */ MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data); @@ -205,7 +205,7 @@ template CORRADE_DEPRECATED("use removeDuplicatesInPlace() instead @m_since_latest Compared to @ref removeDuplicatesInPlace(const Containers::StridedArrayView1D&, typename Vector::Type) -this variant is more suited for data that are already indexed as it works on +this variant is more suited for data that is already indexed as it works on the existing index array instead of allocating a new one. */ template std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView1D& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()) { From 5a276fe32800099aaa44906baf5c28038c12f424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 3 Mar 2020 19:00:04 +0100 Subject: [PATCH 097/107] Trade: high-level docs for the MeshData class. --- doc/snippets/MagnumTrade.cpp | 152 +++++++++++++++++++++++++++++++++++ src/Magnum/Trade/MeshData.h | 117 ++++++++++++++++++++++++++- 2 files changed, 267 insertions(+), 2 deletions(-) diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index b9d68c3d49..4383a3fead 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -45,8 +45,12 @@ #ifdef MAGNUM_TARGET_GL #include "Magnum/GL/Texture.h" #include "Magnum/GL/Mesh.h" +#include "Magnum/MeshTools/Compile.h" #include "Magnum/Shaders/Phong.h" #endif +#ifdef MAGNUM_TARGET_VK +#include "Magnum/Vk/Vulkan.h" +#endif #ifdef MAGNUM_BUILD_DEPRECATED #define _MAGNUM_NO_DEPRECATED_MESHDATA /* So it doesn't yell here */ @@ -242,6 +246,54 @@ for(auto&& row: data.mutablePixels()) /* [ImageData-usage-mutable] */ } +{ +/* [MeshIndexData-usage] */ +Containers::ArrayView indices; + +Trade::MeshIndexData data{indices}; +/* [MeshIndexData-usage] */ +} + +{ +/* [MeshAttributeData-usage] */ +Containers::StridedArrayView1D positions; + +Trade::MeshAttributeData data{Trade::MeshAttribute::Position, positions}; +/* [MeshAttributeData-usage] */ +} + +{ +/* [MeshAttributeData-usage-offset-only] */ +struct Vertex { + Vector3 position; + Vector4 color; +}; + +/* Layout defined statically, 15 vertices in total */ +constexpr Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + VertexFormat::Vector3, offsetof(Vertex, position), 15, sizeof(Vertex)}; +constexpr Trade::MeshAttributeData colors{Trade::MeshAttribute::Color, + VertexFormat::Vector4, offsetof(Vertex, color), 15, sizeof(Vertex)}; + +/* Actual data populated later */ +Containers::Array vertexData{15*sizeof(Vertex)}; +// ... +Trade::MeshData{MeshPrimitive::Triangles, std::move(vertexData), + {positions, colors}}; +/* [MeshAttributeData-usage-offset-only] */ +} + +#ifdef MAGNUM_TARGET_VK +{ +Containers::StridedArrayView1D data; +/* [MeshAttributeData-custom-vertex-format] */ +Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + vertexFormatWrap(VK_FORMAT_B10G11R11_UFLOAT_PACK32), + data}; +/* [MeshAttributeData-custom-vertex-format] */ +} +#endif + #ifdef MAGNUM_TARGET_GL { Trade::MeshData data{MeshPrimitive::Points, 0}; @@ -300,6 +352,13 @@ if(data.isIndexed()) { } else mesh.setCount(data.vertexCount()); /* [MeshData-usage-advanced] */ } + +{ +Trade::MeshData data{MeshPrimitive::Points, 0}; +/* [MeshData-usage-compile] */ +GL::Mesh mesh = MeshTools::compile(data); +/* [MeshData-usage-compile] */ +} #endif { @@ -317,6 +376,99 @@ MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{2.0f}), /* [MeshData-usage-mutable] */ } +{ +std::size_t vertexCount{}, indexCount{}; +/* [MeshData-populating] */ +struct Vertex { + Vector3 position; + Vector4 color; +}; + +Containers::Array indexData{indexCount*sizeof(UnsignedShort)}; +Containers::Array vertexData{vertexCount*sizeof(Vertex)}; +// … +auto vertices = Containers::arrayCast(vertexData); +auto indices = Containers::arrayCast(indexData); + +Trade::MeshData data{MeshPrimitive::Triangles, + std::move(indexData), Trade::MeshIndexData{indices}, + std::move(vertexData), { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{vertices, + &vertices[0].position, vertexCount, sizeof(Vertex)}}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::StridedArrayView1D{vertices, + &vertices[0].color, vertexCount, sizeof(Vertex)}} + }}; +/* [MeshData-populating] */ +} + +{ +struct Vertex { + Vector3 position; + Vector4 color; +}; +/* [MeshData-populating-non-owned] */ +const UnsignedShort indices[] { + 0, 1, 2, + 2, 1, 3, + 3, 4, 5, + 5, 4, 6 +}; +Vertex vertices[7]; + +Trade::MeshData data{MeshPrimitive::Triangles, + Trade::DataFlags{}, indices, Trade::MeshIndexData{indices}, + Trade::DataFlag::Mutable, vertices, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::StridedArrayView1D{ + Containers::arrayView(vertices), &vertices[0].position, + Containers::arraySize(vertices), sizeof(Vertex)}}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::StridedArrayView1D{ + Containers::arrayView(vertices), &vertices[0].color, + Containers::arraySize(vertices), sizeof(Vertex)}} + }}; +/* [MeshData-populating-non-owned] */ +} + +{ +/* [MeshData-populating-custom] */ +/* Each face can consist of 15 triangles at most, triangleCount says how many + indices in triangleIds are valid */ +struct Face { + UnsignedShort triangleIds[15]; + UnsignedByte triangleCount; +}; + +constexpr Trade::MeshAttribute TriangleIds = Trade::meshAttributeCustom(0x01); +constexpr Trade::MeshAttribute TriangleCount = Trade::meshAttributeCustom(0x02); + +Containers::Array vertexData; +auto faces = Containers::arrayCast(vertexData); + +Trade::MeshData data{MeshPrimitive::Faces, std::move(vertexData), { + Trade::MeshAttributeData{TriangleIds, + Containers::StridedArrayView2D{faces, + &faces[0].triangleIds[0], + {faces.size(), 15}, + {sizeof(Face), sizeof(UnsignedShort)}}}, + Trade::MeshAttributeData{TriangleCount, + Containers::StridedArrayView1D{faces, + &faces[0].triangleCount, faces.size(), sizeof(Face)}} +}}; +/* [MeshData-populating-custom] */ + +/* [MeshData-populating-custom-retrieve] */ +Containers::StridedArrayView2D triangleIds = + data.attribute(TriangleIds); +Containers::StridedArrayView1D triangleCounts = + data.attribute(TriangleCount); +/* [MeshData-populating-custom-retrieve] */ +static_cast(triangleIds); +static_cast(triangleCounts); +} + #ifdef MAGNUM_BUILD_DEPRECATED { CORRADE_IGNORE_DEPRECATED_PUSH diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index efee5571f6..004e30d584 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -171,6 +171,19 @@ constexpr UnsignedShort meshAttributeCustom(MeshAttribute name) { Convenience type for populating @ref MeshData, see its documentation for an introduction. + +@section Trade-MeshIndexData-usage Usage + +The most straightforward usage is constructing the instance from a view on the +index array. The @ref MeshIndexType gets inferred from the view type: + +@snippet MagnumTrade.cpp MeshIndexData-usage + +Alternatively, you can pass a typeless @cpp const void @ce view and supply +@ref MeshIndexType explicitly, or a contiguous 2D view and let the class detect +the actual index type from second dimension size. Note that the class accepts +only contiguous views and not @ref Corrade::Containers::StridedArrayView, +following limitations of GPU index buffers that also have to be contiguous. @see @ref MeshAttributeData */ class MAGNUM_TRADE_EXPORT MeshIndexData { @@ -233,7 +246,47 @@ class MAGNUM_TRADE_EXPORT MeshIndexData { @m_since_latest Convenience type for populating @ref MeshData, see its documentation for an -introduction. +introduction. Additionally usable in various @ref MeshTools algorithms such as +@ref MeshTools::duplicate(const Trade::MeshData& data, Containers::ArrayView) +or @ref MeshTools::interleave(const Trade::MeshData& data, Containers::ArrayView). + +@section Trade-MeshAttributeData-usage Usage + +The most straightforward usage is constructing an instance from a pair of +@ref MeshAttribute and a strided view. The @ref VertexFormat gets inferred from +the view type: + +@snippet MagnumTrade.cpp MeshAttributeData-usage + +Alternatively, you can pass a typeless @cpp const void @ce view and supply +@ref VertexFormat explicitly, or a 2D view. + +@subsection Trade-MeshAttributeData-usage-offset-only Offset-only attribute data + +If the actual attribute data location is not known yet, the instance can be +created as "offset-only", meaning the actual view gets created only later when +passed to a @ref MeshData instance with a concrete vertex data array. This is +useful for example when vertex layout is static (and thus can be defined at +compile time), but the actual data is allocated / populated at runtime: + +@snippet MagnumTrade.cpp MeshAttributeData-usage-offset-only + +Note that @ref MeshTools algorithms generally don't accept offset-only +@ref MeshAttributeData instances except when passed through a @ref MeshData +instance. + +@section Trade-MeshAttributeData-custom-vertex-format Custom vertex formats + +Apart from custom @ref MeshAttribute names, shown in +@ref Trade-MeshData-populating-custom, @ref VertexFormat can be extended with +implementation-specific formats as well. Formats that don't have a generic +@ref VertexFormat equivalent can be created using @ref vertexFormatWrap(), +however note that most APIs and @ref MeshTools functions can't work with those +as their size or contents can't be known: + +@snippet MagnumTrade.cpp MeshAttributeData-custom-vertex-format + +@see @ref MeshIndexData */ class MAGNUM_TRADE_EXPORT MeshAttributeData { public: @@ -505,6 +558,19 @@ element data type without having to explicitly handle all relevant types: @snippet MagnumTrade.cpp MeshData-usage-advanced +@section Trade-MeshData-usage-compile Using MeshTools::compile() + +For a quick yet efficient way to upload all data and configure a mesh for all +known attributes that are present, @ref MeshTools::compile() can be used. +Compared to the above, it's just an oneliner: + +@snippet MagnumTrade.cpp MeshData-usage-compile + +Compared to configuring the mesh manually you may lose a bit of flexibility, +especially when you need to set up custom attributes or modify the data after. +See @ref MeshTools::compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&) +for a possible solution. + @section Trade-MeshData-usage-mutable Mutable data access The interfaces implicitly provide @cpp const @ce views on the contained index @@ -519,6 +585,51 @@ first. The following snippet applies a transformation to the mesh data: @snippet MagnumTrade.cpp MeshData-usage-mutable +@section Trade-MeshData-populating Populating an instance + +A @ref MeshData instance by default takes over the ownership of an +@ref Corrade::Containers::Array containing the vertex / index data together +with a @ref MeshIndexData instance and a list of @ref MeshAttributeData +describing various index and vertex properties. For example, an interleaved +indexed mesh with 3D positions and RGBA colors would look like this --- +and variants with just vertex data or just index data or neither are possible +too: + +@snippet MagnumTrade.cpp MeshData-populating + +In cases where you want the @ref MeshData instance to only refer to external +data without taking ownership (for example in a memory-mapped file, constant +memory etc., instead of moving in an @ref Corrade::Containers::Array you pass +@ref DataFlags describing if the data is mutable or not together with an +@ref Corrade::Containers::ArrayView. A variant of the above where the index +data is constant and vertex data mutable, both referenced externally: + +@snippet MagnumTrade.cpp MeshData-populating-non-owned + +@subsection Trade-MeshData-populating-custom Custom mesh attributes + +To allow for greater flexibility, a @ref MeshData instance can describe not +just attributes that are predefined in the @ref MeshAttribute enum, but also +custom attributes, created with @ref meshAttributeCustom(). For example, the +snippet below describes a custom per-face structure that exposes faces as +higher-order polygons combining multiple triangles together ---in this case, +each face has an array of 15 IDs, which is exposed as a 2D array: + +@snippet MagnumTrade.cpp MeshData-populating-custom + +Later, the (array) attributes can be retrieved back using the same custom +identifiers --- note the use of @cpp [] @ce to get back a 2D array again: + +@snippet MagnumTrade.cpp MeshData-populating-custom-retrieve + +When a custom attribute is exposed through @ref AbstractImporter, it's possible +to map custom @ref MeshAttribute values to human-readable string names using +@ref AbstractImporter::meshAttributeName() and +@ref AbstractImporter::meshAttributeForName(). Using @ref meshPrimitiveWrap() +you can also supply implementation-specific values that are not available in +the generic @ref MeshPrimitive enum, similarly see also +@ref Trade-MeshAttributeData-custom-vertex-format for details on +implementation-specific @ref VertexFormat values. @see @ref AbstractImporter::mesh() */ class MAGNUM_TRADE_EXPORT MeshData { @@ -943,6 +1054,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref MeshAttributeData::isOffsetOnly() always returning * @cpp false @ce). The @p id is expected to be smaller than * @ref attributeCount() const. + * @see @ref MeshTools::interleavedData() */ MeshAttributeData attributeData(UnsignedInt id) const; @@ -976,7 +1088,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref attributeCount() const. You can also use * @ref attributeOffset(MeshAttribute, UnsignedInt) const to * directly get an offset of given named attribute. - * @see @ref indexOffset() + * @see @ref indexOffset(), @ref MeshTools::isInterleaved() */ std::size_t attributeOffset(UnsignedInt id) const; @@ -988,6 +1100,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * than @ref attributeCount() const. You can also use * @ref attributeStride(MeshAttribute, UnsignedInt) const to * directly get a stride of given named attribute. + * @see @ref MeshTools::isInterleaved() */ UnsignedInt attributeStride(UnsignedInt id) const; From 8c4a2b1c6f7a38de90e84f3bb8cae7475f284496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 4 Mar 2020 13:54:12 +0100 Subject: [PATCH 098/107] Trade: add getters for offset and stride in MeshAttributeData. Less code and complexity than first creating a StridedArrayView in data() and then extracting offset/stride from there. --- src/Magnum/MeshTools/Interleave.cpp | 8 ++++---- src/Magnum/Trade/MeshData.h | 24 ++++++++++++++++++++++-- src/Magnum/Trade/Test/MeshDataTest.cpp | 8 ++++++++ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp index 8f89b60952..0518008d4f 100644 --- a/src/Magnum/MeshTools/Interleave.cpp +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -80,9 +80,9 @@ Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt std::size_t extraAttributeCount = 0; for(std::size_t i = 0; i != extra.size(); ++i) { if(extra[i].format() == VertexFormat{}) { - CORRADE_ASSERT(extra[i].data().stride() > 0 || stride >= std::size_t(-extra[i].data().stride()), - "MeshTools::interleavedLayout(): negative padding" << extra[i].data().stride() << "in extra attribute" << i << "too large for stride" << stride, (Trade::MeshData{MeshPrimitive::Points, 0})); - stride += extra[i].data().stride(); + CORRADE_ASSERT(extra[i].stride() > 0 || stride >= std::size_t(-extra[i].stride()), + "MeshTools::interleavedLayout(): negative padding" << extra[i].stride() << "in extra attribute" << i << "too large for stride" << stride, (Trade::MeshData{MeshPrimitive::Points, 0})); + stride += extra[i].stride(); } else { stride += vertexFormatSize(extra[i].format()); ++extraAttributeCount; @@ -118,7 +118,7 @@ Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt for(UnsignedInt i = 0; i != extra.size(); ++i) { /* Padding, only adjust the offset for next attribute */ if(extra[i].format() == VertexFormat{}) { - offset += extra[i].data().stride(); + offset += extra[i].stride(); continue; } diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 004e30d584..bfff0fe040 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -430,6 +430,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * between interleaved attributes. Negative values can be used to alias * multiple different attributes onto each other. Not meant to be * passed to @ref MeshData. + * @see @ref stride() */ constexpr explicit MeshAttributeData(Int padding): _data{nullptr}, _vertexCount{0}, _format{}, _stride{ (CORRADE_CONSTEXPR_ASSERT(padding >= -32768 && padding <= 32767, @@ -452,6 +453,25 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /** @brief Attribute format */ constexpr VertexFormat format() const { return _format; } + /** + * @brief Attribute offset + * + * If the attribute is offset-only, returns the offset directly, + * otherwise uses the @p vertexData parameter to calculate the offset. + * @see @ref isOffsetOnly() + */ + std::size_t offset(Containers::ArrayView vertexData) const { + return _isOffsetOnly ? _data.offset : reinterpret_cast(_data.pointer) - reinterpret_cast(vertexData.data()); + } + + /** + * @brief Attribute stride + * + * Can be negative for pad values, never negative for real attributes. + * @see @ref MeshAttributeData(Int) + */ + constexpr Short stride() const { return _stride; } + /** @brief Attribute array size */ constexpr UnsignedShort arraySize() const { return _arraySize; } @@ -474,8 +494,8 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /** * @brief Type-erased attribute data for an offset-only attribute * - * If the attribute is not offset-only, the @ref vertexData parameter - * is ignored. + * If the attribute is not offset-only, the @p vertexData parameter is + * ignored. * @see @ref isOffsetOnly(), @ref data() const */ Containers::StridedArrayView1D data(Containers::ArrayView vertexData) const { diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 70448ce10f..d775781230 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -492,6 +492,8 @@ void MeshDataTest::constructAttribute() { CORRADE_COMPARE(positions.arraySize(), 0); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); + CORRADE_COMPARE(positions.offset(positionData), 0); + CORRADE_COMPARE(positions.stride(), sizeof(Vector2)); CORRADE_VERIFY(positions.data().data() == positionData); /* This is allowed too for simplicity, it just ignores the parameter */ CORRADE_VERIFY(positions.data(positionData).data() == positionData); @@ -501,11 +503,13 @@ void MeshDataTest::constructAttribute() { constexpr UnsignedShort arraySize = cpositions.arraySize(); constexpr MeshAttribute name = cpositions.name(); constexpr VertexFormat format = cpositions.format(); + constexpr Short stride = cpositions.stride(); constexpr Containers::StridedArrayView1D data = cpositions.data(); CORRADE_VERIFY(!isOffsetOnly); CORRADE_COMPARE(arraySize, 0); CORRADE_COMPARE(name, MeshAttribute::Position); CORRADE_COMPARE(format, VertexFormat::Vector2); + CORRADE_COMPARE(stride, sizeof(Vector2)); CORRADE_COMPARE(data.data(), Positions); } @@ -603,6 +607,8 @@ void MeshDataTest::constructAttributeOffsetOnly() { CORRADE_COMPARE(a.arraySize(), 0); CORRADE_COMPARE(a.name(), MeshAttribute::TextureCoordinates); CORRADE_COMPARE(a.format(), VertexFormat::Vector2); + CORRADE_COMPARE(a.offset(vertexData), sizeof(Vector2)); + CORRADE_COMPARE(a.stride(), 2*sizeof(Vector2)); CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), TestSuite::Compare::Container); @@ -612,6 +618,8 @@ void MeshDataTest::constructAttributeOffsetOnly() { CORRADE_COMPARE(ca.arraySize(), 0); CORRADE_COMPARE(ca.name(), MeshAttribute::TextureCoordinates); CORRADE_COMPARE(ca.format(), VertexFormat::Vector2); + CORRADE_COMPARE(ca.offset(vertexData), sizeof(Vector2)); + CORRADE_COMPARE(ca.stride(), 2*sizeof(Vector2)); CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), TestSuite::Compare::Container); From 7a9c630599484fd61ea18e60cbebbb71723cda4a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 4 Mar 2020 17:04:30 +0100 Subject: [PATCH 099/107] MeshTools: add interleavedLayout() that can reuse the attribute array. --- src/Magnum/MeshTools/Interleave.cpp | 53 +++++++++++++---- src/Magnum/MeshTools/Interleave.h | 21 +++++++ src/Magnum/MeshTools/Test/InterleaveTest.cpp | 62 ++++++++++++++++++-- 3 files changed, 120 insertions(+), 16 deletions(-) diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp index 0518008d4f..d73e827e81 100644 --- a/src/Magnum/MeshTools/Interleave.cpp +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -51,7 +51,7 @@ bool isInterleaved(const Trade::MeshData& data) { return maxOffset - minOffset <= stride; } -Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt vertexCount, const Containers::ArrayView extra) { +Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vertexCount, const Containers::ArrayView extra) { /* If there are no attributes, bail -- return an empty mesh with desired vertex count but nothing else */ if(!data.attributeCount() && extra.empty()) @@ -89,32 +89,49 @@ Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt } } - /* Allocate new data and attribute array */ + /* Transfer the attribute data array. If there are no extra attributes and + the attribute data array is owned (the array has a default deleter), we + can take over the ownership and avoid an allocation. Otherwise we + allocate a new array and copy the prefix over so we can just patch the + data array later. */ + const UnsignedInt originalAttributeCount = data.attributeCount(); + const UnsignedInt originalAttributeStride = originalAttributeCount ? + data.attributeStride(0) : 0; + Containers::Array originalAttributeData = + data.releaseAttributeData(); + Containers::Array attributeData; + if(!extraAttributeCount && !originalAttributeData.deleter()) + attributeData = std::move(originalAttributeData); + else { + attributeData = Containers::Array{originalAttributeCount + extraAttributeCount}; + Utility::copy(originalAttributeData, attributeData.prefix(originalAttributeCount)); + } + + /* Allocate new data array */ Containers::Array vertexData{Containers::NoInit, stride*vertexCount}; - Containers::Array attributeData{data.attributeCount() + extraAttributeCount}; /* Copy existing attribute layout. If the original is already interleaved, preserve relative attribute offsets, otherwise pack tightly. */ std::size_t offset = 0; - for(UnsignedInt i = 0; i != data.attributeCount(); ++i) { - if(interleaved) offset = data.attributeOffset(i) - minOffset; + for(UnsignedInt i = 0; i != originalAttributeCount; ++i) { + if(interleaved) offset = attributeData[i].offset(data.vertexData()) - minOffset; attributeData[i] = Trade::MeshAttributeData{ - data.attributeName(i), data.attributeFormat(i), + attributeData[i].name(), attributeData[i].format(), Containers::StridedArrayView1D{vertexData, vertexData + offset, vertexCount, std::ptrdiff_t(stride)}}; - if(!interleaved) offset += vertexFormatSize(data.attributeFormat(i)); + if(!interleaved) offset += vertexFormatSize(attributeData[i].format()); } /* In case the original is already interleaved, set the offset for extra attribs to the original stride to preserve also potential padding at the end. */ - if(interleaved && data.attributeCount()) - offset = data.attributeStride(0); + if(interleaved && originalAttributeCount) + offset = originalAttributeStride; /* Mix in the extra attributes */ - UnsignedInt attributeIndex = data.attributeCount(); + UnsignedInt attributeIndex = originalAttributeCount; for(UnsignedInt i = 0; i != extra.size(); ++i) { /* Padding, only adjust the offset for next attribute */ if(extra[i].format() == VertexFormat{}) { @@ -132,6 +149,22 @@ Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt return Trade::MeshData{data.primitive(), std::move(vertexData), std::move(attributeData)}; } +Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vertexCount, const std::initializer_list extra) { + return interleavedLayout(std::move(data), vertexCount, Containers::arrayView(extra)); +} + +Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt vertexCount, const Containers::ArrayView extra) { + /* If there's no attributes in the original mesh, we need to pass vertex + count explicitly (MeshData asserts on that to avoid it getting lost.) */ + if(!data.attributeCount()) + return interleavedLayout(Trade::MeshData{data.primitive(), data.vertexCount()}, vertexCount, extra); + + return interleavedLayout( + Trade::MeshData{data.primitive(), {}, data.vertexData(), + Trade::meshAttributeDataNonOwningArray(data.attributeData())}, + vertexCount, extra); +} + Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt vertexCount, const std::initializer_list extra) { return interleavedLayout(data, vertexCount, Containers::arrayView(extra)); } diff --git a/src/Magnum/MeshTools/Interleave.h b/src/Magnum/MeshTools/Interleave.h index d1851de479..3d5f3cfb5d 100644 --- a/src/Magnum/MeshTools/Interleave.h +++ b/src/Magnum/MeshTools/Interleave.h @@ -226,6 +226,10 @@ output non-indexed. If you want to preserve index data, create a new indexed instance with attribute and vertex data transferred from the returned instance: @snippet MagnumMeshTools.cpp interleavedLayout-indices + +This function will unconditionally allocate a new array to store all +@ref Trade::MeshAttributeData, use @ref interleavedLayout(Trade::MeshData&&, UnsignedInt, Containers::ArrayView) +to avoid that allocation. */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(const Trade::MeshData& data, UnsignedInt vertexCount, Containers::ArrayView extra = {}); @@ -235,6 +239,23 @@ 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 Create an interleaved mesh layout +@m_since_latest + +Compared to @ref interleavedLayout(const Trade::MeshData&, UnsignedInt, Containers::ArrayView) +this function can reuse the @ref Trade::MeshAttributeData array from @p data +instead of allocating a new one if there are no attributes passed in @p extra +and the attribute array is owned by the mesh. +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(Trade::MeshData&& data, UnsignedInt vertexCount, Containers::ArrayView extra = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(Trade::MeshData&& data, UnsignedInt vertexCount, std::initializer_list extra); + /** @brief Interleave mesh data @m_since_latest diff --git a/src/Magnum/MeshTools/Test/InterleaveTest.cpp b/src/Magnum/MeshTools/Test/InterleaveTest.cpp index adebe44b2b..d3542b14b5 100644 --- a/src/Magnum/MeshTools/Test/InterleaveTest.cpp +++ b/src/Magnum/MeshTools/Test/InterleaveTest.cpp @@ -67,6 +67,7 @@ struct InterleaveTest: Corrade::TestSuite::Tester { void interleavedLayoutAlreadyInterleavedAliased(); void interleavedLayoutAlreadyInterleavedExtra(); void interleavedLayoutNothing(); + void interleavedLayoutRvalue(); void interleaveMeshData(); void interleaveMeshDataIndexed(); @@ -108,6 +109,7 @@ InterleaveTest::InterleaveTest() { &InterleaveTest::interleavedLayoutAlreadyInterleavedAliased, &InterleaveTest::interleavedLayoutAlreadyInterleavedExtra, &InterleaveTest::interleavedLayoutNothing, + &InterleaveTest::interleavedLayoutRvalue, &InterleaveTest::interleaveMeshData, &InterleaveTest::interleaveMeshDataIndexed, @@ -338,15 +340,20 @@ void InterleaveTest::isInterleavedAttributeAcrossStride() { void InterleaveTest::interleavedLayout() { Containers::Array indexData{6}; Containers::Array vertexData{3*20}; - Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, - Containers::arrayCast(vertexData.prefix(3*8))}; - Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, - Containers::arrayCast(vertexData.suffix(3*8))}; + + const Trade::MeshAttributeData attributeData[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.prefix(3*8))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + Containers::arrayCast(vertexData.suffix(3*8))} + }; Trade::MeshIndexData indices{Containers::arrayCast(indexData)}; Trade::MeshData data{MeshPrimitive::TriangleFan, - std::move(indexData), indices, - std::move(vertexData), {positions, normals}}; + std::move(indexData), indices, std::move(vertexData), + /* Verify that interleavedLayout() won't attempt to modify the const + array (see interleavedLayoutRvalue()) */ + Trade::meshAttributeDataNonOwningArray(attributeData)}; CORRADE_VERIFY(!MeshTools::isInterleaved(data)); Trade::MeshData layout = MeshTools::interleavedLayout(data, 10); @@ -596,6 +603,49 @@ void InterleaveTest::interleavedLayoutNothing() { CORRADE_COMPARE(layout.vertexData().size(), 0); } +void InterleaveTest::interleavedLayoutRvalue() { + Containers::Array indexData{6}; + Containers::Array vertexData{3*20}; + Containers::Array attributeData{2}; + attributeData[0] = Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.prefix(3*8))}; + attributeData[1] = Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + Containers::arrayCast(vertexData.suffix(3*8))}; + const void* originalAttributeData = attributeData.data(); + + Trade::MeshIndexData indices{Containers::arrayCast(indexData)}; + Trade::MeshData data{MeshPrimitive::TriangleFan, + std::move(indexData), indices, + std::move(vertexData), std::move(attributeData)}; + CORRADE_VERIFY(!MeshTools::isInterleaved(data)); + + /* Check that the attribute data array gets reused when moving a rvalue. + Explicitly passing an empty init list to verify the rvalue gets + propagated correctly through all functions. */ + Trade::MeshData layout = MeshTools::interleavedLayout(std::move(data), 10, + std::initializer_list{}); + CORRADE_VERIFY(layout.attributeData().data() == originalAttributeData); + + /* The rest is same as in interleavedLayout() */ + CORRADE_VERIFY(MeshTools::isInterleaved(layout)); + CORRADE_COMPARE(layout.primitive(), MeshPrimitive::TriangleFan); + CORRADE_VERIFY(!layout.isIndexed()); /* Indices are not preserved */ + CORRADE_COMPARE(layout.attributeCount(), 2); + CORRADE_COMPARE(layout.attributeName(0), Trade::MeshAttribute::Position); + CORRADE_COMPARE(layout.attributeName(1), Trade::MeshAttribute::Normal); + CORRADE_COMPARE(layout.attributeFormat(0), VertexFormat::Vector2); + CORRADE_COMPARE(layout.attributeFormat(1), VertexFormat::Vector3); + CORRADE_COMPARE(layout.attributeStride(0), 20); + CORRADE_COMPARE(layout.attributeStride(1), 20); + CORRADE_COMPARE(layout.attributeOffset(0), 0); + CORRADE_COMPARE(layout.attributeOffset(1), 8); + CORRADE_COMPARE(layout.vertexCount(), 10); + /* Needs to be like this so we can modify the data */ + CORRADE_COMPARE(layout.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned); + CORRADE_VERIFY(layout.vertexData()); + CORRADE_COMPARE(layout.vertexData().size(), 10*20); +} + void InterleaveTest::interleaveMeshData() { struct { Vector2 positions[3]; From 27f6cc309d4636adb0f65e559de8e5cc109483cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 7 Mar 2020 14:28:12 +0100 Subject: [PATCH 100/107] Trade: allow specifying explicit vertex count on MeshData construction. Until now, except for an attribute-less index-less mesh, the vertex count was only implicitly taken from passed attributes, but it was severely limiting: - There was no way to set vertex count for an attribute-less indexed mesh, which didn't make sense - All code that made non-owning MeshData instances referencing another MeshData had to explicitly handle the attribute-less corner case to avoid vertex count getting lost - Offset-only attributes couldn't be used to specify static layout of meshes with dynamic vertex count, causing unnecessary extra allocations especially in the Primitives library. --- src/Magnum/MeshTools/Combine.cpp | 4 +- src/Magnum/MeshTools/Interleave.cpp | 18 +- src/Magnum/MeshTools/Test/CombineTest.cpp | 22 +- src/Magnum/MeshTools/Test/DuplicateTest.cpp | 4 +- src/Magnum/Trade/MeshData.cpp | 65 +++-- src/Magnum/Trade/MeshData.h | 91 +++++-- .../Trade/Test/AbstractImporterTest.cpp | 6 +- src/Magnum/Trade/Test/MeshData2DTest.cpp | 4 +- src/Magnum/Trade/Test/MeshData3DTest.cpp | 4 +- src/Magnum/Trade/Test/MeshDataTest.cpp | 244 +++++++++++------- 10 files changed, 280 insertions(+), 182 deletions(-) diff --git a/src/Magnum/MeshTools/Combine.cpp b/src/Magnum/MeshTools/Combine.cpp index d099a90e3a..8ec259769a 100644 --- a/src/Magnum/MeshTools/Combine.cpp +++ b/src/Magnum/MeshTools/Combine.cpp @@ -91,7 +91,7 @@ Trade::MeshData combineIndexedAttributes(const Containers::ArrayView indexData{indexCount*sizeof(UnsignedInt)}; const auto indexDataI = Containers::arrayCast(indexData); - const std::size_t vertexCount = removeDuplicatesInPlaceInto( + const UnsignedInt vertexCount = removeDuplicatesInPlaceInto( Containers::StridedArrayView2D{combinedIndices, {indexCount, indexStride}}, indexDataI); @@ -133,7 +133,7 @@ Trade::MeshData combineIndexedAttributes(const Containers::ArrayView> data) { diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp index d73e827e81..ad629adb52 100644 --- a/src/Magnum/MeshTools/Interleave.cpp +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -154,14 +154,10 @@ Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vert } Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt vertexCount, const Containers::ArrayView extra) { - /* If there's no attributes in the original mesh, we need to pass vertex - count explicitly (MeshData asserts on that to avoid it getting lost.) */ - if(!data.attributeCount()) - return interleavedLayout(Trade::MeshData{data.primitive(), data.vertexCount()}, vertexCount, extra); - return interleavedLayout( Trade::MeshData{data.primitive(), {}, data.vertexData(), - Trade::meshAttributeDataNonOwningArray(data.attributeData())}, + Trade::meshAttributeDataNonOwningArray(data.attributeData()), + data.vertexCount()}, vertexCount, extra); } @@ -257,19 +253,13 @@ Trade::MeshData interleave(const Trade::MeshData& data, const Containers::ArrayV 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()) + {}, data.vertexData(), Trade::meshAttributeDataNonOwningArray(data.attributeData()), + data.vertexCount() }, extra); - } Trade::MeshData interleave(const Trade::MeshData& data, const std::initializer_list extra) { diff --git a/src/Magnum/MeshTools/Test/CombineTest.cpp b/src/Magnum/MeshTools/Test/CombineTest.cpp index db91dbd099..4be6f8967e 100644 --- a/src/Magnum/MeshTools/Test/CombineTest.cpp +++ b/src/Magnum/MeshTools/Test/CombineTest.cpp @@ -107,11 +107,11 @@ void CombineTest::combineIndexedAttributesIndicesOnly() { const UnsignedShort indicesB[]{3, 4, 3}; const UnsignedByte indicesC[]{7, 6, 7}; Trade::MeshData a{MeshPrimitive::LineLoop, {}, indicesA, - Trade::MeshIndexData{indicesA}}; + Trade::MeshIndexData{indicesA}, 3}; Trade::MeshData b{MeshPrimitive::LineLoop, {}, indicesB, - Trade::MeshIndexData{indicesB}}; + Trade::MeshIndexData{indicesB}, 5}; Trade::MeshData c{MeshPrimitive::LineLoop, {}, indicesC, - Trade::MeshIndexData{indicesC}}; + Trade::MeshIndexData{indicesC}, 8}; Trade::MeshData result = MeshTools::combineIndexedAttributes({a, b, c}); CORRADE_COMPARE(result.primitive(), MeshPrimitive::LineLoop); @@ -121,7 +121,7 @@ void CombineTest::combineIndexedAttributesIndicesOnly() { Containers::arrayView({0, 1, 0}), TestSuite::Compare::Container); CORRADE_COMPARE(result.attributeCount(), 0); - CORRADE_COMPARE(result.vertexCount(), 0); + CORRADE_COMPARE(result.vertexCount(), 2); } void CombineTest::combineIndexedAttributesNoMeshes() { @@ -134,9 +134,9 @@ void CombineTest::combineIndexedAttributesNoMeshes() { void CombineTest::combineIndexedAttributesNotIndexed() { const UnsignedShort indices[5]{}; Trade::MeshData a{MeshPrimitive::Lines, - {}, indices, Trade::MeshIndexData{indices}}; + {}, indices, Trade::MeshIndexData{indices}, 1}; Trade::MeshData b{MeshPrimitive::Lines, - {}, indices, Trade::MeshIndexData{indices}}; + {}, indices, Trade::MeshIndexData{indices}, 1}; Trade::MeshData c{MeshPrimitive::Lines, 5}; std::ostringstream out; @@ -148,9 +148,9 @@ void CombineTest::combineIndexedAttributesNotIndexed() { void CombineTest::combineIndexedAttributesDifferentPrimitive() { const UnsignedShort indices[5]{}; Trade::MeshData a{MeshPrimitive::Lines, - {}, indices, Trade::MeshIndexData{indices}}; + {}, indices, Trade::MeshIndexData{indices}, 1}; Trade::MeshData b{MeshPrimitive::Points, - {}, indices, Trade::MeshIndexData{indices}}; + {}, indices, Trade::MeshIndexData{indices}, 1}; std::ostringstream out; Error redirectError{&out}; @@ -161,12 +161,12 @@ void CombineTest::combineIndexedAttributesDifferentPrimitive() { void CombineTest::combineIndexedAttributesDifferentIndexCount() { const UnsignedShort indices[5]{}; Trade::MeshData a{MeshPrimitive::Lines, - {}, indices, Trade::MeshIndexData{indices}}; + {}, indices, Trade::MeshIndexData{indices}, 1}; Trade::MeshData b{MeshPrimitive::Lines, - {}, indices, Trade::MeshIndexData{indices}}; + {}, indices, Trade::MeshIndexData{indices}, 1}; Trade::MeshData c{MeshPrimitive::Lines, {}, indices, - Trade::MeshIndexData{Containers::arrayView(indices).prefix(4)}}; + Trade::MeshIndexData{Containers::arrayView(indices).prefix(4)}, 1}; std::ostringstream out; Error redirectError{&out}; diff --git a/src/Magnum/MeshTools/Test/DuplicateTest.cpp b/src/Magnum/MeshTools/Test/DuplicateTest.cpp index 058373e050..8f638a9421 100644 --- a/src/Magnum/MeshTools/Test/DuplicateTest.cpp +++ b/src/Magnum/MeshTools/Test/DuplicateTest.cpp @@ -372,7 +372,7 @@ void DuplicateTest::duplicateMeshDataExtraWrongCount() { void DuplicateTest::duplicateMeshDataExtraOffsetOnly() { UnsignedByte indices[]{0, 1, 2, 2, 1, 0}; Trade::MeshData data{MeshPrimitive::TriangleFan, - {}, indices, Trade::MeshIndexData{indices}}; + {}, indices, Trade::MeshIndexData{indices}, 3}; std::ostringstream out; Error redirectError{&out}; @@ -386,7 +386,7 @@ void DuplicateTest::duplicateMeshDataExtraOffsetOnly() { void DuplicateTest::duplicateMeshDataNoAttributes() { UnsignedByte indices[]{0, 1, 2, 2, 1, 0}; Trade::MeshData data{MeshPrimitive::Lines, - {}, indices, Trade::MeshIndexData{indices}}; + {}, indices, Trade::MeshIndexData{indices}, 3}; Trade::MeshData duplicated = MeshTools::duplicate(data, {}); CORRADE_COMPARE(duplicated.primitive(), MeshPrimitive::Lines); diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index adaa1db160..948526f52d 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -79,15 +79,29 @@ Containers::Array meshAttributeDataNonOwningArray(const Conta return Containers::Array{const_cast(view.data()), view.size(), reinterpret_cast(Trade::Implementation::nonOwnedArrayDeleter)}; } -MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: _indexType{indices._type}, _primitive{primitive}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{Containers::arrayCast(indices._data)} { - /* Save vertex count. It's a strided array view, so the size is not - depending on type. */ +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: _indexType{indices._type}, _primitive{primitive}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)}, _attributes{std::move(attributes)}, _indices{Containers::arrayCast(indices._data)} { + /* Save vertex count. If it's passed explicitly, use that (but still check + that all attributes have the same vertex count for safety), otherwise + expect at least one attribute */ + #ifndef CORRADE_NO_ASSERT + UnsignedInt expectedAttributeVertexCount; + #endif if(_attributes.empty()) { - CORRADE_ASSERT(indices._type != MeshIndexType{}, - "Trade::MeshData: indices are expected to be valid if there are no attributes and vertex count isn't passed explicitly", ); - /** @todo some better value? attributeless indexed with defined vertex count? */ - _vertexCount = 0; - } else _vertexCount = _attributes[0]._vertexCount; + CORRADE_ASSERT(vertexCount != ImplicitVertexCount, + "Trade::MeshData: vertex count can't be implicit if there are no attributes", ); + _vertexCount = vertexCount; + /* No attributes, so we won't be checking anything */ + } else if(vertexCount != ImplicitVertexCount) { + _vertexCount = vertexCount; + #ifndef CORRADE_NO_ASSERT + expectedAttributeVertexCount = _attributes[0]._vertexCount; + #endif + } else { + _vertexCount = _attributes[0]._vertexCount; + #ifndef CORRADE_NO_ASSERT + expectedAttributeVertexCount = _vertexCount; + #endif + } CORRADE_ASSERT(!_indices.empty() || _indexData.empty(), "Trade::MeshData: indexData passed for a non-indexed mesh", ); @@ -101,8 +115,8 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde const MeshAttributeData& attribute = _attributes[i]; CORRADE_ASSERT(attribute._format != VertexFormat{}, "Trade::MeshData: attribute" << i << "doesn't specify anything", ); - CORRADE_ASSERT(attribute._vertexCount == _vertexCount, - "Trade::MeshData: attribute" << i << "has" << attribute._vertexCount << "vertices but" << _vertexCount << "expected", ); + CORRADE_ASSERT(attribute._vertexCount == expectedAttributeVertexCount, + "Trade::MeshData: attribute" << i << "has" << attribute._vertexCount << "vertices but" << expectedAttributeVertexCount << "expected", ); /* Check that the view fits into the provided vertex data array. For implementation-specific formats we don't know the size so use 0 to check at least partially. */ @@ -123,9 +137,9 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde #endif } -MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* const importerState): MeshData{primitive, std::move(indexData), indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, std::move(indexData), indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} -MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), importerState} { +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), vertexCount, importerState} { CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), @@ -134,45 +148,45 @@ MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags _vertexDataFlags = vertexDataFlags; } -MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, indexDataFlags, indexData, indices, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, const std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, indexDataFlags, indexData, indices, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} -MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, std::move(vertexData), std::move(attributes), importerState} { +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, std::move(vertexData), std::move(attributes), vertexCount, importerState} { CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); _indexDataFlags = indexDataFlags; } -MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, indexDataFlags, indexData, indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, const std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, indexDataFlags, indexData, indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} -MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), importerState} { +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), vertexCount, importerState} { CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); _vertexDataFlags = vertexDataFlags; } -MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, std::move(indexData), indices, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, const std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, std::move(indexData), indices, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} -MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, std::move(vertexData), std::move(attributes), importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, std::move(vertexData), std::move(attributes), vertexCount, importerState} {} -MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, const std::initializer_list attributes, const void* const importerState): MeshData{primitive, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, const std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} -MeshData::MeshData(const MeshPrimitive primitive, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), importerState} { +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), vertexCount, importerState} { CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); _vertexDataFlags = vertexDataFlags; } -MeshData::MeshData(const MeshPrimitive primitive, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const void* const importerState): MeshData{primitive, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} -MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, {}, {}, importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, {}, {}, vertexCount, importerState} {} -MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, importerState} { +MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, vertexCount, importerState} { CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); _indexDataFlags = indexDataFlags; } -MeshData::MeshData(const MeshPrimitive primitive, const UnsignedInt vertexCount, const void* const importerState) noexcept: _vertexCount{vertexCount}, _indexType{}, _primitive{primitive}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState} {} +MeshData::MeshData(const MeshPrimitive primitive, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, {}, {}, vertexCount, importerState} {} MeshData::~MeshData() = default; @@ -238,8 +252,7 @@ MeshAttributeData MeshData::attributeData(UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeData(): index" << id << "out of range for" << _attributes.size() << "attributes", MeshAttributeData{}); const MeshAttributeData& attribute = _attributes[id]; - return attribute._isOffsetOnly ? MeshAttributeData{attribute._name, - attribute._format, attributeDataViewInternal(attribute)} : attribute; + return MeshAttributeData{attribute._name, attribute._format, attributeDataViewInternal(attribute)}; } MeshAttribute MeshData::attributeName(UnsignedInt id) const { diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index bfff0fe040..1c4d60d079 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -418,6 +418,10 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * attribute construction time. Expects that @p arraySize is zero for * builtin attributes. Note that instances created this way can't be * used in most @ref MeshTools algorithms. + * + * Additionally, for even more flexibility, the @p vertexCount can be + * overriden at @ref MeshData construction time, however all attributes + * are still required to have the same vertex count to catch accidents. * @see @ref isOffsetOnly(), @ref arraySize(), * @ref data(Containers::ArrayView) const */ @@ -654,6 +658,14 @@ implementation-specific @ref VertexFormat values. */ class MAGNUM_TRADE_EXPORT MeshData { public: + enum: UnsignedInt { + /** + * Implicit vertex count. When passed to a constructor, indicates + * that vertex count should be taken from attribute data views. + */ + ImplicitVertexCount = ~UnsignedInt{} + }; + /** * @brief Construct an indexed mesh data * @param primitive Primitive @@ -661,6 +673,10 @@ class MAGNUM_TRADE_EXPORT MeshData { * @param indices Index data description * @param vertexData Vertex data * @param attributes Description of all vertex attribute data + * @param vertexCount Vertex count. If set to + * @ref ImplicitVertexCount, vertex count is taken from data views + * passed to @p attributes (in which case there has to be at least + * one). * @param importerState Importer-specific state * * The @p indices are expected to point to a sub-range of @p indexData. @@ -674,14 +690,14 @@ class MAGNUM_TRADE_EXPORT MeshData { * The @ref indexDataFlags() / @ref vertexDataFlags() are implicitly * set to a combination of @ref DataFlag::Owned and * @ref DataFlag::Mutable. For non-owned data use the - * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, const MeshIndexData&, DataFlags, Containers::ArrayView, Containers::Array&&, const void*) + * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, const MeshIndexData&, DataFlags, Containers::ArrayView, Containers::Array&&, UnsignedInt, const void*) * constructor or its variants instead. */ - explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr) noexcept; /** @overload */ /* Not noexcept because allocation happens inside */ - explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr); /** * @brief Construct indexed mesh data with non-owned index and vertex data @@ -692,19 +708,23 @@ class MAGNUM_TRADE_EXPORT MeshData { * @param vertexDataFlags Vertex data flags * @param vertexData View on vertex data * @param attributes Description of all vertex attribute data + * @param vertexCount Vertex count. If set to + * @ref ImplicitVertexCount, vertex count is taken from data views + * passed to @p attributes (in which case there has to be at least + * one). * @param importerState Importer-specific state * - * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, UnsignedInt, const void*) * creates an instance that doesn't own the passed vertex and index * data. The @p indexDataFlags / @p vertexDataFlags parameters can * contain @ref DataFlag::Mutable to indicate the external data can be * modified, and is expected to *not* have @ref DataFlag::Owned set. */ - explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr) noexcept; /** @overload */ /* Not noexcept because allocation happens inside */ - explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const void* importerState = nullptr); + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr); /** * @brief Construct indexed mesh data with non-owned index data @@ -714,9 +734,13 @@ class MAGNUM_TRADE_EXPORT MeshData { * @param indices Index data description * @param vertexData Vertex data * @param attributes Description of all vertex attribute data + * @param vertexCount Vertex count. If set to + * @ref ImplicitVertexCount, vertex count is taken from data views + * passed to @p attributes (in which case there has to be at least + * one). * @param importerState Importer-specific state * - * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, UnsignedInt, const void*) * creates an instance that doesn't own the passed index data. The * @p indexDataFlags parameter can contain @ref DataFlag::Mutable to * indicate the external data can be modified, and is expected to *not* @@ -724,11 +748,11 @@ class MAGNUM_TRADE_EXPORT MeshData { * implicitly set to a combination of @ref DataFlag::Owned and * @ref DataFlag::Mutable. */ - explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr) noexcept; /** @overload */ /* Not noexcept because allocation happens inside */ - explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr); /** * @brief Construct indexed mesh data with non-owned vertex data @@ -738,9 +762,13 @@ class MAGNUM_TRADE_EXPORT MeshData { * @param vertexDataFlags Vertex data flags * @param vertexData View on vertex data * @param attributes Description of all vertex attribute data + * @param vertexCount Vertex count. If set to + * @ref ImplicitVertexCount, vertex count is taken from data views + * passed to @p attributes (in which case there has to be at least + * one). * @param importerState Importer-specific state * - * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, UnsignedInt, const void*) * creates an instance that doesn't own the passed vertex data. The * @p vertexDataFlags parameter can contain @ref DataFlag::Mutable to * indicate the external data can be modified, and is expected to *not* @@ -748,20 +776,24 @@ class MAGNUM_TRADE_EXPORT MeshData { * implicitly set to a combination of @ref DataFlag::Owned and * @ref DataFlag::Mutable. */ - explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr) noexcept; /** @overload */ /* Not noexcept because allocation happens inside */ - explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const void* importerState = nullptr); + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr); /** * @brief Construct a non-indexed mesh data * @param primitive Primitive * @param vertexData Vertex data * @param attributes Description of all vertex attribute data + * @param vertexCount Vertex count. If set to + * @ref ImplicitVertexCount, vertex count is taken from data views + * passed to @p attributes (in which case there has to be at least + * one). * @param importerState Importer-specific state * - * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, UnsignedInt, const void*) * with default-constructed @p indexData and @p indices arguments. * * The @ref vertexDataFlags() are implicitly set to a combination of @@ -769,14 +801,14 @@ class MAGNUM_TRADE_EXPORT MeshData { * the @ref indexDataFlags() are implicitly set to a combination of * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there * isn't any data to own or to mutate. For non-owned data use the - * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, Containers::Array&&, const void*) + * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, Containers::Array&&, UnsignedInt, const void*) * constructor instead. */ - explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr) noexcept; /** @overload */ /* Not noexcept because allocation happens inside */ - explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, std::initializer_list attributes, const void* importerState = nullptr); + explicit MeshData(MeshPrimitive primitive, Containers::Array&& vertexData, std::initializer_list attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr); /** * @brief Construct a non-owned non-indexed mesh data @@ -784,9 +816,13 @@ class MAGNUM_TRADE_EXPORT MeshData { * @param vertexDataFlags Vertex data flags * @param vertexData View on vertex data * @param attributes Description of all vertex attribute data + * @param vertexCount Vertex count. If set to + * @ref ImplicitVertexCount, vertex count is taken from data views + * passed to @p attributes (in which case there has to be at least + * one). * @param importerState Importer-specific state * - * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, Containers::Array&&, const void*) + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, Containers::Array&&, UnsignedInt, const void*) * creates an instance that doesn't own the passed data. The * @p vertexDataFlags parameter can contain @ref DataFlag::Mutable to * indicate the external data can be modified, and is expected to *not* @@ -795,20 +831,22 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there * isn't any data to own or to mutate. */ - explicit MeshData(MeshPrimitive primitive, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const void* importerState = nullptr) noexcept; + explicit MeshData(MeshPrimitive primitive, DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr) noexcept; /** @overload */ /* Not noexcept because allocation happens inside */ - explicit MeshData(MeshPrimitive primitive, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const void* importerState = nullptr); + explicit MeshData(MeshPrimitive primitive, DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, UnsignedInt vertexCount = ImplicitVertexCount, const void* importerState = nullptr); /** * @brief Construct an attribute-less indexed mesh data * @param primitive Primitive * @param indexData Index data * @param indices Index data description + * @param vertexCount Vertex count. Passing @ref ImplicitVertexCount + * is not allowed in this overload. * @param importerState Importer-specific state * - * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, const void*) + * Same as calling @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, Containers::Array&&, Containers::Array&&, UnsignedInt, const void*) * with default-constructed @p vertexData and @p attributes arguments. * The @p indices are expected to be valid (but can be empty). If you * want to create an attribute-less non-indexed mesh, use @@ -820,10 +858,10 @@ class MAGNUM_TRADE_EXPORT MeshData { * the @ref vertexDataFlags() are implicitly set to a combination of * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there * isn't any data to own or to mutate. For non-owned data use the - * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, const MeshIndexData&, const void*) + * @ref MeshData(MeshPrimitive, DataFlags, Containers::ArrayView, const MeshIndexData&, UnsignedInt, const void*) * constructor instead. */ - explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const void* importerState = nullptr) noexcept; + explicit MeshData(MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, UnsignedInt vertexCount, const void* importerState = nullptr) noexcept; /** * @brief Construct a non-owned attribute-less indexed mesh data @@ -831,9 +869,11 @@ class MAGNUM_TRADE_EXPORT MeshData { * @param indexDataFlags Index data flags * @param indexData View on index data * @param indices Index data description + * @param vertexCount Vertex count. Passing + * @ref ImplicitVertexCount is not allowed in this overload. * @param importerState Importer-specific state * - * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, const void*) + * Compared to @ref MeshData(MeshPrimitive, Containers::Array&&, const MeshIndexData&, UnsignedInt, const void*) * creates an instance that doesn't own the passed data. The * @p indexDataFlags parameter can contain @ref DataFlag::Mutable to * indicate the external data can be modified, and is expected to *not* @@ -842,12 +882,13 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref DataFlag::Owned and @ref DataFlag::Mutable, even though there * isn't any data to own or to mutate. */ - explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, const void* importerState = nullptr) noexcept; + explicit MeshData(MeshPrimitive primitive, DataFlags indexDataFlags, Containers::ArrayView indexData, const MeshIndexData& indices, UnsignedInt vertexCount, const void* importerState = nullptr) noexcept; /** * @brief Construct an index-less attribute-less mesh data * @param primitive Primitive - * @param vertexCount Desired count of vertices to draw + * @param vertexCount Vertex count. Passing @ref ImplicitVertexCount + * is not allowed in this overload. * @param importerState Importer-specific state * * Useful in case the drawing is fully driven by a shader. For diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 7c948f2bf4..0ca558f93a 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -2264,7 +2264,7 @@ void AbstractImporterTest::mesh() { Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override { /* Verify that initializer list is converted to an array with the default deleter and not something disallowed */ - if(id == 7 && level == 2) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, &state}; + if(id == 7 && level == 2) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, MeshData::ImplicitVertexCount, &state}; else return {}; } } importer; @@ -2304,7 +2304,7 @@ void AbstractImporterTest::meshDeprecatedFallback() { else return {}; } Containers::Optional doMesh(UnsignedInt id, UnsignedInt level) override { - if(id == 7 && level == 0) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, &state}; + if(id == 7 && level == 0) return MeshData{MeshPrimitive::Points, nullptr, {MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr}}, MeshData::ImplicitVertexCount, &state}; else return {}; } } importer; @@ -2610,7 +2610,7 @@ void AbstractImporterTest::meshCustomIndexDataDeleter() { UnsignedInt doMeshCount() const override { return 1; } Int doMeshForName(const std::string&) override { return 0; } Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { - return MeshData{MeshPrimitive::Triangles, Containers::Array{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}}; + return MeshData{MeshPrimitive::Triangles, Containers::Array{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}, 1}; } char data[1]; diff --git a/src/Magnum/Trade/Test/MeshData2DTest.cpp b/src/Magnum/Trade/Test/MeshData2DTest.cpp index 5cf12e91de..fed1d16865 100644 --- a/src/Magnum/Trade/Test/MeshData2DTest.cpp +++ b/src/Magnum/Trade/Test/MeshData2DTest.cpp @@ -98,7 +98,7 @@ struct { Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords3, 2, sizeof(Vertex)}}, MeshAttributeData{MeshAttribute::Color, Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, - }, &State}}, + }, MeshData::ImplicitVertexCount, &State}}, /* GCC 4.8 needs the explicit MeshData3D conversion otherwise it tries to use a deleted copy constructor */ MeshData2D{MeshData{MeshPrimitive::Lines, {}, Vertices, { @@ -108,7 +108,7 @@ struct { Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords1, 2, sizeof(Vertex)}}, MeshAttributeData{MeshAttribute::Color, Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, - }, &State}} + }, MeshData::ImplicitVertexCount, &State}} } }; diff --git a/src/Magnum/Trade/Test/MeshData3DTest.cpp b/src/Magnum/Trade/Test/MeshData3DTest.cpp index 0965f94c33..250130ca62 100644 --- a/src/Magnum/Trade/Test/MeshData3DTest.cpp +++ b/src/Magnum/Trade/Test/MeshData3DTest.cpp @@ -106,7 +106,7 @@ struct { Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords3, 2, sizeof(Vertex)}}, MeshAttributeData{MeshAttribute::Color, Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, - }, &State}}, + }, MeshData::ImplicitVertexCount, &State}}, /* GCC 4.8 needs the explicit MeshData3D conversion otherwise it tries to use a deleted copy constructor */ MeshData3D{MeshData{MeshPrimitive::Lines, {}, Vertices, { @@ -118,7 +118,7 @@ struct { Containers::StridedArrayView1D{Vertices, &Vertices[0].textureCoords1, 2, sizeof(Vertex)}}, MeshAttributeData{MeshAttribute::Color, Containers::StridedArrayView1D{Vertices, &Vertices[0].color, 2, sizeof(Vertex)}}, - }, &State}} + }, MeshData::ImplicitVertexCount, &State}} } }; diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index d775781230..628e20da33 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -75,6 +75,7 @@ struct MeshDataTest: TestSuite::Tester { void constructArrayAttributeNotAllowed(); void construct(); + void constructZeroIndices(); void constructZeroAttributes(); void constructZeroVertices(); @@ -91,7 +92,7 @@ struct MeshDataTest: TestSuite::Tester { void constructAttributelessNotOwned(); void constructIndexDataButNotIndexed(); - void constructAttributelessInvalidIndices(); + void constructAttributelessImplicitVertexCount(); void constructIndicesNotContained(); void constructAttributeNotContained(); void constructInconsitentVertexCount(); @@ -153,6 +154,16 @@ struct MeshDataTest: TestSuite::Tester { void releaseVertexData(); }; +struct { + const char* name; + UnsignedInt vertexCount, expectedVertexCount; +} ConstructData[] { + {"implicit vertex count", MeshData::ImplicitVertexCount, 3}, + {"explicit vertex count", 3, 3}, + {"explicit large vertex count", 17, 17}, + {"explicit zero vertex count", 0, 0} +}; + struct { const char* name; DataFlags indexDataFlags, vertexDataFlags; @@ -207,10 +218,12 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructArrayAttribute2DNonContiguous, &MeshDataTest::constructArrayAttributeTypeErased, &MeshDataTest::constructArrayAttributeOffsetOnly, - &MeshDataTest::constructArrayAttributeNotAllowed, + &MeshDataTest::constructArrayAttributeNotAllowed}); - &MeshDataTest::construct, - &MeshDataTest::constructZeroIndices, + addInstancedTests({&MeshDataTest::construct}, + Containers::arraySize(ConstructData)); + + addTests({&MeshDataTest::constructZeroIndices, &MeshDataTest::constructZeroAttributes, &MeshDataTest::constructZeroVertices, &MeshDataTest::constructIndexless, @@ -228,7 +241,7 @@ MeshDataTest::MeshDataTest() { Containers::arraySize(SingleNotOwnedData)); addTests({&MeshDataTest::constructIndexDataButNotIndexed, - &MeshDataTest::constructAttributelessInvalidIndices, + &MeshDataTest::constructAttributelessImplicitVertexCount, &MeshDataTest::constructIndicesNotContained, &MeshDataTest::constructAttributeNotContained, &MeshDataTest::constructInconsitentVertexCount, @@ -823,6 +836,9 @@ void MeshDataTest::constructArrayAttributeNotAllowed() { } void MeshDataTest::construct() { + auto&& instanceData = ConstructData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + struct Vertex { Vector3 position; Vector3 normal; @@ -839,8 +855,10 @@ void MeshDataTest::construct() { indexView[4] = 2; indexView[5] = 1; - Containers::Array vertexData{3*sizeof(Vertex)}; - auto vertexView = Containers::arrayCast(vertexData); + /* Enough vertex data to fit also the case with large explicit vertex count + (but fill just the first 3, as those are only tested) */ + Containers::Array vertexData{17*sizeof(Vertex)}; + auto vertexView = Containers::arrayCast(vertexData).prefix(3); vertexView[0].position = {0.1f, 0.2f, 0.3f}; vertexView[1].position = {0.4f, 0.5f, 0.6f}; vertexView[2].position = {0.7f, 0.8f, 0.9f}; @@ -854,6 +872,9 @@ void MeshDataTest::construct() { vertexView[1].id = -374; vertexView[2].id = 22; + if(instanceData.vertexCount < 3) + vertexView = vertexView.prefix(instanceData.vertexCount); + int importerState; MeshIndexData indices{indexView}; MeshAttributeData positions{MeshAttribute::Position, @@ -869,7 +890,7 @@ void MeshDataTest::construct() { MeshData data{MeshPrimitive::Triangles, std::move(indexData), indices, /* Texture coordinates deliberately twice (though aliased) */ - std::move(vertexData), {positions, textureCoordinates, normals, textureCoordinates, ids}, &importerState}; + std::move(vertexData), {positions, textureCoordinates, normals, textureCoordinates, ids}, instanceData.vertexCount, &importerState}; /* Basics */ CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); @@ -899,7 +920,7 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.indices()[5], 1); /* Attribute access by ID */ - CORRADE_COMPARE(data.vertexCount(), 3); + CORRADE_COMPARE(data.vertexCount(), instanceData.expectedVertexCount); CORRADE_COMPARE(data.attributeCount(), 5); CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); CORRADE_COMPARE(data.attributeName(1), MeshAttribute::TextureCoordinates); @@ -927,43 +948,53 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attributeArraySize(4), 0); /* Typeless access by ID with a cast later */ - CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( - data.attribute(0))[1]), (Vector3{0.4f, 0.5f, 0.6f})); - CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( - data.attribute(1))[0]), (Vector2{0.000f, 0.125f})); - CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( - data.attribute(2))[2]), Vector3::zAxis()); - CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( - data.attribute(3))[1]), (Vector2{0.250f, 0.375f})); - CORRADE_COMPARE((Containers::arrayCast<1, const Short>( - data.attribute(4))[0]), 15); - CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( - data.mutableAttribute(0))[1]), (Vector3{0.4f, 0.5f, 0.6f})); - CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( - data.mutableAttribute(1))[0]), (Vector2{0.000f, 0.125f})); - CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( - data.mutableAttribute(2))[2]), Vector3::zAxis()); - CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( - data.mutableAttribute(3))[1]), (Vector2{0.250f, 0.375f})); - CORRADE_COMPARE((Containers::arrayCast<1, Short>( - data.mutableAttribute(4))[0]), 15); + CORRADE_COMPARE(data.attribute(0).size()[0], instanceData.expectedVertexCount); + CORRADE_COMPARE(data.mutableAttribute(0).size()[0], instanceData.expectedVertexCount); + if(instanceData.vertexCount) { + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.attribute(0))[1]), (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(1))[0]), (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.attribute(2))[2]), Vector3::zAxis()); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(3))[1]), (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Short>( + data.attribute(4))[0]), 15); + CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( + data.mutableAttribute(0))[1]), (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(1))[0]), (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( + data.mutableAttribute(2))[2]), Vector3::zAxis()); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(3))[1]), (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE((Containers::arrayCast<1, Short>( + data.mutableAttribute(4))[0]), 15); + } /* Typed access by ID */ - CORRADE_COMPARE(data.attribute(0)[1], (Vector3{0.4f, 0.5f, 0.6f})); - CORRADE_COMPARE(data.attribute(1)[0], (Vector2{0.000f, 0.125f})); - CORRADE_COMPARE(data.attribute(2)[2], Vector3::zAxis()); - CORRADE_COMPARE(data.attribute(3)[1], (Vector2{0.250f, 0.375f})); - CORRADE_COMPARE(data.attribute(4)[1], -374); - CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector3{0.4f, 0.5f, 0.6f})); - CORRADE_COMPARE(data.mutableAttribute(1)[0], (Vector2{0.000f, 0.125f})); - CORRADE_COMPARE(data.mutableAttribute(2)[2], Vector3::zAxis()); - CORRADE_COMPARE(data.mutableAttribute(3)[1], (Vector2{0.250f, 0.375f})); - CORRADE_COMPARE(data.mutableAttribute(4)[1], -374); + CORRADE_COMPARE(data.attribute(0).size(), instanceData.expectedVertexCount); + CORRADE_COMPARE(data.mutableAttribute(0).size(), instanceData.expectedVertexCount); + if(instanceData.vertexCount) { + CORRADE_COMPARE(data.attribute(0)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.attribute(1)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.attribute(2)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.attribute(3)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.attribute(4)[1], -374); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.mutableAttribute(1)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.mutableAttribute(2)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.mutableAttribute(3)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.mutableAttribute(4)[1], -374); + } /* Raw attribute data access by ID */ CORRADE_COMPARE(data.attributeData(3).name(), MeshAttribute::TextureCoordinates); CORRADE_COMPARE(data.attributeData(3).format(), VertexFormat::Vector2); - CORRADE_COMPARE(Containers::arrayCast(data.attributeData(3).data())[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.attributeData(3).data().size(), instanceData.expectedVertexCount); + if(instanceData.vertexCount) + CORRADE_COMPARE(Containers::arrayCast(data.attributeData(3).data())[1], (Vector2{0.250f, 0.375f})); /* Attribute access by name */ CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Position)); @@ -1009,38 +1040,46 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attributeArraySize(meshAttributeCustom(13)), 0); /* Typeless access by name with a cast later */ - CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( - data.attribute(MeshAttribute::Position))[1]), (Vector3{0.4f, 0.5f, 0.6f})); - CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( - data.attribute(MeshAttribute::Normal))[2]), Vector3::zAxis()); - CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( - data.attribute(MeshAttribute::TextureCoordinates, 0))[0]), (Vector2{0.000f, 0.125f})); - CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( - data.attribute(MeshAttribute::TextureCoordinates, 1))[1]), (Vector2{0.250f, 0.375f})); - CORRADE_COMPARE((Containers::arrayCast<1, const Short>( - data.attribute(meshAttributeCustom(13)))[1]), -374); - CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( - data.mutableAttribute(MeshAttribute::Position))[1]), (Vector3{0.4f, 0.5f, 0.6f})); - CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( - data.mutableAttribute(MeshAttribute::Normal))[2]), Vector3::zAxis()); - CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( - data.mutableAttribute(MeshAttribute::TextureCoordinates, 0))[0]), (Vector2{0.000f, 0.125f})); - CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( - data.mutableAttribute(MeshAttribute::TextureCoordinates, 1))[1]), (Vector2{0.250f, 0.375f})); - CORRADE_COMPARE((Containers::arrayCast<1, Short>( - data.mutableAttribute(meshAttributeCustom(13)))[1]), -374); + CORRADE_COMPARE(data.attribute(MeshAttribute::Position).size()[0], instanceData.expectedVertexCount); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position).size()[0], instanceData.expectedVertexCount); + if(instanceData.vertexCount) { + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.attribute(MeshAttribute::Position))[1]), (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.attribute(MeshAttribute::Normal))[2]), Vector3::zAxis()); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(MeshAttribute::TextureCoordinates, 0))[0]), (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(MeshAttribute::TextureCoordinates, 1))[1]), (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Short>( + data.attribute(meshAttributeCustom(13)))[1]), -374); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( + data.mutableAttribute(MeshAttribute::Position))[1]), (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( + data.mutableAttribute(MeshAttribute::Normal))[2]), Vector3::zAxis()); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(MeshAttribute::TextureCoordinates, 0))[0]), (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(MeshAttribute::TextureCoordinates, 1))[1]), (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE((Containers::arrayCast<1, Short>( + data.mutableAttribute(meshAttributeCustom(13)))[1]), -374); + } /* Typed access by name */ - CORRADE_COMPARE(data.attribute(MeshAttribute::Position)[1], (Vector3{0.4f, 0.5f, 0.6f})); - CORRADE_COMPARE(data.attribute(MeshAttribute::Normal)[2], Vector3::zAxis()); - CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); - CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f})); - CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2], 22); - CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position)[1], (Vector3{0.4f, 0.5f, 0.6f})); - CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Normal)[2], Vector3::zAxis()); - CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); - CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f})); - CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2], 22); + CORRADE_COMPARE(data.attribute(MeshAttribute::Position).size()[0], instanceData.expectedVertexCount); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position).size()[0], instanceData.expectedVertexCount); + if(instanceData.vertexCount) { + CORRADE_COMPARE(data.attribute(MeshAttribute::Position)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.attribute(MeshAttribute::Normal)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.attribute(MeshAttribute::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2], 22); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position)[1], (Vector3{0.4f, 0.5f, 0.6f})); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Normal)[2], Vector3::zAxis()); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::TextureCoordinates, 1)[1], (Vector2{0.250f, 0.375f})); + CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2], 22); + } } void MeshDataTest::constructZeroIndices() { @@ -1073,14 +1112,14 @@ void MeshDataTest::constructZeroAttributes() { auto indexView = Containers::arrayCast(indexData); MeshData data{MeshPrimitive::Triangles, std::move(indexData), MeshIndexData{indexView}, - std::move(vertexData), {}}; + std::move(vertexData), {}, 15}; CORRADE_COMPARE(data.indexCount(), 3); CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.attributeCount(), 0); CORRADE_VERIFY(!data.attributeData()); CORRADE_COMPARE(data.vertexData().size(), 3); - CORRADE_COMPARE(data.vertexCount(), 0); + CORRADE_COMPARE(data.vertexCount(), 15); } void MeshDataTest::constructZeroVertices() { @@ -1112,7 +1151,7 @@ void MeshDataTest::constructIndexless() { int importerState; MeshAttributeData positions{MeshAttribute::Position, vertexView}; - MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions}, &importerState}; + MeshData data{MeshPrimitive::LineLoop, std::move(vertexData), {positions}, MeshData::ImplicitVertexCount, &importerState}; CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); /* These are empty so it doesn't matter, but this is a nice non-restrictive default */ @@ -1153,7 +1192,7 @@ void MeshDataTest::constructAttributeless() { int importerState; MeshIndexData indices{indexView}; - MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), indices, &importerState}; + MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), indices, 3, &importerState}; /* These are empty so it doesn't matter, but this is a nice non-restrictive default */ CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); @@ -1170,7 +1209,7 @@ void MeshDataTest::constructAttributeless() { CORRADE_COMPARE(data.indices()[2], 2); CORRADE_COMPARE(data.indices()[5], 1); - CORRADE_COMPARE(data.vertexCount(), 0); /** @todo what to return here? */ + CORRADE_COMPARE(data.vertexCount(), 3); CORRADE_COMPARE(data.attributeCount(), 0); } @@ -1184,7 +1223,7 @@ void MeshDataTest::constructNotOwned() { int importerState; MeshIndexData indices{indexData}; MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; - MeshData data{MeshPrimitive::Triangles, instanceData.indexDataFlags, Containers::arrayView(indexData), indices, instanceData.vertexDataFlags, Containers::arrayView(vertexData), {positions}, &importerState}; + MeshData data{MeshPrimitive::Triangles, instanceData.indexDataFlags, Containers::arrayView(indexData), indices, instanceData.vertexDataFlags, Containers::arrayView(vertexData), {positions}, MeshData::ImplicitVertexCount, &importerState}; CORRADE_COMPARE(data.indexDataFlags(), instanceData.indexDataFlags); CORRADE_COMPARE(data.vertexDataFlags(), instanceData.vertexDataFlags); @@ -1234,7 +1273,7 @@ void MeshDataTest::constructIndicesNotOwned() { int importerState; MeshIndexData indices{indexData}; MeshAttributeData positions{MeshAttribute::Position, vertexView}; - MeshData data{MeshPrimitive::Triangles, instanceData.dataFlags, Containers::arrayView(indexData), indices, std::move(vertexData), {positions}, &importerState}; + MeshData data{MeshPrimitive::Triangles, instanceData.dataFlags, Containers::arrayView(indexData), indices, std::move(vertexData), {positions}, MeshData::ImplicitVertexCount, &importerState}; CORRADE_COMPARE(data.indexDataFlags(), instanceData.dataFlags); CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); @@ -1282,7 +1321,7 @@ void MeshDataTest::constructVerticesNotOwned() { int importerState; MeshIndexData indices{indexView}; MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; - MeshData data{MeshPrimitive::Triangles, std::move(indexData), indices, instanceData.dataFlags, Containers::arrayView(vertexData), {positions}, &importerState}; + MeshData data{MeshPrimitive::Triangles, std::move(indexData), indices, instanceData.dataFlags, Containers::arrayView(vertexData), {positions}, MeshData::ImplicitVertexCount, &importerState}; CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.vertexDataFlags(), instanceData.dataFlags); @@ -1324,7 +1363,7 @@ void MeshDataTest::constructIndexlessNotOwned() { int importerState; MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; - MeshData data{MeshPrimitive::LineLoop, instanceData.dataFlags, vertexData, {positions}, &importerState}; + MeshData data{MeshPrimitive::LineLoop, instanceData.dataFlags, vertexData, {positions}, MeshData::ImplicitVertexCount, &importerState}; CORRADE_COMPARE(data.indexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.vertexDataFlags(), instanceData.dataFlags); @@ -1351,7 +1390,7 @@ void MeshDataTest::constructAttributelessNotOwned() { int importerState; MeshIndexData indices{indexData}; - MeshData data{MeshPrimitive::TriangleStrip, instanceData.dataFlags, indexData, indices, &importerState}; + MeshData data{MeshPrimitive::TriangleStrip, instanceData.dataFlags, indexData, indices, 5, &importerState}; CORRADE_COMPARE(data.indexDataFlags(), instanceData.dataFlags); CORRADE_COMPARE(data.vertexDataFlags(), DataFlag::Owned|DataFlag::Mutable); CORRADE_COMPARE(data.primitive(), MeshPrimitive::TriangleStrip); @@ -1373,7 +1412,7 @@ void MeshDataTest::constructAttributelessNotOwned() { CORRADE_COMPARE(data.mutableIndices()[2], 0); } - CORRADE_COMPARE(data.vertexCount(), 0); /** @todo what to return here? */ + CORRADE_COMPARE(data.vertexCount(), 5); CORRADE_COMPARE(data.attributeCount(), 0); } @@ -1419,11 +1458,11 @@ void MeshDataTest::constructIndexDataButNotIndexed() { CORRADE_COMPARE(out.str(), "Trade::MeshData: indexData passed for a non-indexed mesh\n"); } -void MeshDataTest::constructAttributelessInvalidIndices() { +void MeshDataTest::constructAttributelessImplicitVertexCount() { std::ostringstream out; Error redirectError{&out}; - MeshData{MeshPrimitive::Points, nullptr, MeshIndexData{}}; - CORRADE_COMPARE(out.str(), "Trade::MeshData: indices are expected to be valid if there are no attributes and vertex count isn't passed explicitly\n"); + MeshData{MeshPrimitive::Points, nullptr, {}}; + CORRADE_COMPARE(out.str(), "Trade::MeshData: vertex count can't be implicit if there are no attributes\n"); } void MeshDataTest::constructIndicesNotContained() { @@ -1433,8 +1472,8 @@ void MeshDataTest::constructIndicesNotContained() { std::ostringstream out; Error redirectError{&out}; - MeshData{MeshPrimitive::Triangles, std::move(indexData), indices}; - MeshData{MeshPrimitive::Triangles, nullptr, indices}; + MeshData{MeshPrimitive::Triangles, std::move(indexData), indices, 1}; + MeshData{MeshPrimitive::Triangles, nullptr, indices, 1}; CORRADE_COMPARE(out.str(), "Trade::MeshData: indices [0xdead:0xdeb3] are not contained in passed indexData array [0xbadda9:0xbaddaf]\n" "Trade::MeshData: indices [0xdead:0xdeb3] are not contained in passed indexData array [0x0:0x0]\n"); @@ -1449,25 +1488,40 @@ void MeshDataTest::constructAttributeNotContained() { /* See implementationSpecificVertexFormatNotContained() below for implementation-specific formats */ + /* Here the original positions array is shrunk from 3 items to 2 and the + vertex data too, which should work without asserting -- comparing just + the original view would not pass, which is wrong */ + MeshData{MeshPrimitive::Triangles, {}, vertexData.prefix(16), {positions}, 2}; + std::ostringstream out; Error redirectError{&out}; + /* Here the original positions array is extended from 3 items to 4, which + makes it not fit anymore, and thus an assert should hit -- comparing + just the original view would pass, which is wrong */ + MeshData{MeshPrimitive::Triangles, {}, vertexData, {positions}, 4}; MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}}; MeshData{MeshPrimitive::Triangles, nullptr, {positions}}; MeshData{MeshPrimitive::Triangles, Containers::Array{24}, {positions3}}; CORRADE_COMPARE(out.str(), + "Trade::MeshData: attribute 0 [0xbadda9:0xbaddc9] is not contained in passed vertexData array [0xbadda9:0xbaddc1]\n" "Trade::MeshData: attribute 1 [0xdead:0xdec5] is not contained in passed vertexData array [0xbadda9:0xbaddc1]\n" "Trade::MeshData: attribute 0 [0xbadda9:0xbaddc1] is not contained in passed vertexData array [0x0:0x0]\n" "Trade::MeshData: offset attribute 0 spans 25 bytes but passed vertexData array has only 24\n"); } void MeshDataTest::constructInconsitentVertexCount() { - Containers::Array vertexData{24}; - MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast(vertexData)}; + Containers::Array vertexData{136}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayCast(vertexData).prefix(3)}; MeshAttributeData positions2{MeshAttribute::Position, Containers::arrayCast(vertexData).prefix(2)}; std::ostringstream out; Error redirectError{&out}; - MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}}; + /* The explicit vertex count should be ignored for the assertion message, + we only check that all passed attribute arrays have the same vertex + count. However, the actual "containment" of the attribute views is + checked with the explicit vertex count -- see the + constructAttributeNotContained() test above. */ + MeshData{MeshPrimitive::Triangles, std::move(vertexData), {positions, positions2}, 17}; CORRADE_COMPARE(out.str(), "Trade::MeshData: attribute 1 has 2 vertices but 3 expected\n"); } @@ -1552,7 +1606,7 @@ void MeshDataTest::constructAttributelessNotOwnedFlagOwned() { std::ostringstream out; Error redirectError{&out}; - MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, indexData, indices}; + MeshData data{MeshPrimitive::Triangles, DataFlag::Owned, indexData, indices, 2}; CORRADE_COMPARE(out.str(), "Trade::MeshData: can't construct with non-owned index data but Trade::DataFlag::Owned\n"); } @@ -1590,7 +1644,7 @@ void MeshDataTest::constructMove() { int importerState; MeshIndexData indices{indexView}; MeshAttributeData positions{MeshAttribute::Position, vertexView}; - MeshData a{MeshPrimitive::Triangles, std::move(indexData), indices, std::move(vertexData), {positions}, &importerState}; + MeshData a{MeshPrimitive::Triangles, std::move(indexData), indices, std::move(vertexData), {positions}, MeshData::ImplicitVertexCount, &importerState}; MeshData b{std::move(a)}; @@ -1680,7 +1734,7 @@ template void MeshDataTest::indicesAsArray() { indexView[1] = 131; indexView[2] = 240; - MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indexView}}; + MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indexView}, 241}; CORRADE_COMPARE_AS(data.indicesAsArray(), Containers::arrayView({75, 131, 240}), TestSuite::Compare::Container); @@ -1688,7 +1742,7 @@ template void MeshDataTest::indicesAsArray() { void MeshDataTest::indicesIntoArrayInvalidSize() { Containers::Array indexData{3*sizeof(UnsignedInt)}; - MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{Containers::arrayCast(indexData)}}; + MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{Containers::arrayCast(indexData)}, 1}; std::ostringstream out; Error redirectError{&out}; @@ -2338,7 +2392,7 @@ void MeshDataTest::indicesWrongType() { Containers::Array indexData{sizeof(UnsignedShort)}; auto indexView = Containers::arrayCast(indexData); indexView[0] = 57616; - MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indexView}}; + MeshData data{MeshPrimitive::Points, std::move(indexData), MeshIndexData{indexView}, 57617}; std::ostringstream out; Error redirectError{&out}; @@ -2422,7 +2476,7 @@ void MeshDataTest::releaseIndexData() { Containers::Array indexData{23}; auto indexView = Containers::arrayCast(indexData.slice(6, 12)); - MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), MeshIndexData{indexView}}; + MeshData data{MeshPrimitive::TriangleStrip, std::move(indexData), MeshIndexData{indexView}, 10}; CORRADE_VERIFY(data.isIndexed()); CORRADE_COMPARE(data.indexCount(), 3); CORRADE_COMPARE(data.indexOffset(), 6); From 06b82755ad5b952b10da1a7dc0d9992e159e2e93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 7 Mar 2020 19:48:11 +0100 Subject: [PATCH 101/107] Primitives: use compile-time attribute definitions where possible. Now possible in all cases, except for grid, where the combination count is too large to be practical, even more so with the introduction of tangents in the future. --- src/Magnum/Primitives/Circle.cpp | 87 +++++++++++++------ src/Magnum/Primitives/Grid.cpp | 15 +++- .../Primitives/Implementation/Spheroid.cpp | 50 ++++++----- .../Implementation/WireframeSpheroid.cpp | 13 ++- 4 files changed, 111 insertions(+), 54 deletions(-) diff --git a/src/Magnum/Primitives/Circle.cpp b/src/Magnum/Primitives/Circle.cpp index 61ddb46657..9086eea9bd 100644 --- a/src/Magnum/Primitives/Circle.cpp +++ b/src/Magnum/Primitives/Circle.cpp @@ -32,26 +32,39 @@ namespace Magnum { namespace Primitives { +namespace { + +constexpr Trade::MeshAttributeData AttributeData2D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector2, + 0, 0, sizeof(Vector2)} +}; + +constexpr Trade::MeshAttributeData AttributeData2DTextureCoords[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector2, + 0, 0, 2*sizeof(Vector2)}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, VertexFormat::Vector2, + sizeof(Vector2), 0, 2*sizeof(Vector2)} +}; + +} + Trade::MeshData circle2DSolid(const UnsignedInt segments, const CircleTextureCoords textureCoords) { CORRADE_ASSERT(segments >= 3, "Primitives::circle2DSolid(): segments must be >= 3", (Trade::MeshData{MeshPrimitive::TriangleFan, 0})); /* Allocate interleaved array for all vertex data */ - std::size_t stride = sizeof(Vector2); - std::size_t attributeCount = 1; - if(textureCoords == CircleTextureCoords::Generate) { - ++attributeCount; - stride += sizeof(Vector2); - } + Containers::Array attributes; + if(textureCoords == CircleTextureCoords::Generate) + attributes = Trade::meshAttributeDataNonOwningArray(AttributeData2DTextureCoords); + else + attributes = Trade::meshAttributeDataNonOwningArray(AttributeData2D); + const std::size_t stride = attributes[0].stride(); Containers::Array vertexData{stride*(segments + 2)}; - Containers::Array attributes{attributeCount}; /* Fill positions */ Containers::StridedArrayView1D positions{vertexData, reinterpret_cast(vertexData.begin()), segments + 2, std::ptrdiff_t(stride)}; - attributes[0] = - Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}; positions[0] = {}; /* Points on the circle. The first/last point is here twice to close the circle properly. */ @@ -67,13 +80,11 @@ Trade::MeshData circle2DSolid(const UnsignedInt segments, const CircleTextureCoo Containers::StridedArrayView1D textureCoords{vertexData, reinterpret_cast(vertexData.begin() + sizeof(Vector2)), positions.size(), std::ptrdiff_t(stride)}; - attributes[1] = - Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, textureCoords}; for(std::size_t i = 0; i != positions.size(); ++i) textureCoords[i] = positions[i]*0.5f + Vector2{0.5f}; } - return Trade::MeshData{MeshPrimitive::TriangleFan, std::move(vertexData), std::move(attributes)}; + return Trade::MeshData{MeshPrimitive::TriangleFan, std::move(vertexData), std::move(attributes), UnsignedInt(positions.size())}; } Trade::MeshData circle2DWireframe(const UnsignedInt segments) { @@ -91,7 +102,33 @@ Trade::MeshData circle2DWireframe(const UnsignedInt segments) { positions[i] = {sincos.second, sincos.first}; } - return Trade::MeshData{MeshPrimitive::LineLoop, std::move(vertexData), {Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}}}; + return Trade::MeshData{MeshPrimitive::LineLoop, std::move(vertexData), + Trade::meshAttributeDataNonOwningArray(AttributeData2D), UnsignedInt(positions.size())}; +} + +namespace { + +constexpr Trade::MeshAttributeData AttributeData3D[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + 0, 0, 2*sizeof(Vector3)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, + sizeof(Vector3), 0, 2*sizeof(Vector3)} +}; + +constexpr Trade::MeshAttributeData AttributeData3DTextureCoords[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + 0, 0, 2*sizeof(Vector3) + sizeof(Vector2)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, + sizeof(Vector3), 0, 2*sizeof(Vector3) + sizeof(Vector2)}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, VertexFormat::Vector2, + 2*sizeof(Vector3), 0, 2*sizeof(Vector3) + sizeof(Vector2)} +}; + +constexpr Trade::MeshAttributeData AttributeData3DWireframe[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + 0, 0, sizeof(Vector3)} +}; + } Trade::MeshData circle3DSolid(const UnsignedInt segments, CircleTextureCoords textureCoords) { @@ -99,21 +136,18 @@ Trade::MeshData circle3DSolid(const UnsignedInt segments, CircleTextureCoords te (Trade::MeshData{MeshPrimitive::TriangleFan, 0})); /* Allocate interleaved array for all vertex data */ - std::size_t stride = 2*sizeof(Vector3); - std::size_t attributeCount = 2; - if(textureCoords == CircleTextureCoords::Generate) { - ++attributeCount; - stride += sizeof(Vector2); - } + Containers::Array attributes; + if(textureCoords == CircleTextureCoords::Generate) + attributes = Trade::meshAttributeDataNonOwningArray(AttributeData3DTextureCoords); + else + attributes = Trade::meshAttributeDataNonOwningArray(AttributeData3D); + const std::size_t stride = attributes[0].stride(); Containers::Array vertexData{stride*(segments + 2)}; - Containers::Array attributes{attributeCount}; /* Fill positions */ Containers::StridedArrayView1D positions{vertexData, reinterpret_cast(vertexData.begin()), segments + 2, std::ptrdiff_t(stride)}; - attributes[0] = - Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}; positions[0] = {}; /* Points on the circle. The first/last point is here twice to close the circle properly. */ @@ -128,8 +162,6 @@ Trade::MeshData circle3DSolid(const UnsignedInt segments, CircleTextureCoords te Containers::StridedArrayView1D normals{vertexData, reinterpret_cast(vertexData.begin() + sizeof(Vector3)), segments + 2, std::ptrdiff_t(stride)}; - attributes[1] = - Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normals}; for(Vector3& normal: normals) normal = Vector3::zAxis(1.0f); /* Fill texture coords, if any */ @@ -137,13 +169,11 @@ Trade::MeshData circle3DSolid(const UnsignedInt segments, CircleTextureCoords te Containers::StridedArrayView1D textureCoords{vertexData, reinterpret_cast(vertexData.begin() + 2*sizeof(Vector3)), positions.size(), std::ptrdiff_t(stride)}; - attributes[2] = - Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, textureCoords}; for(std::size_t i = 0; i != positions.size(); ++i) textureCoords[i] = positions[i].xy()*0.5f + Vector2{0.5f}; } - return Trade::MeshData{MeshPrimitive::TriangleFan, std::move(vertexData), std::move(attributes)}; + return Trade::MeshData{MeshPrimitive::TriangleFan, std::move(vertexData), std::move(attributes), UnsignedInt(positions.size())}; } Trade::MeshData circle3DWireframe(const UnsignedInt segments) { @@ -161,7 +191,8 @@ Trade::MeshData circle3DWireframe(const UnsignedInt segments) { positions[i] = {sincos.second, sincos.first, 0.0f}; } - return Trade::MeshData{MeshPrimitive::LineLoop, std::move(vertexData), {Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}}}; + return Trade::MeshData{MeshPrimitive::LineLoop, std::move(vertexData), + Trade::meshAttributeDataNonOwningArray(AttributeData3DWireframe), UnsignedInt(positions.size())}; } }} diff --git a/src/Magnum/Primitives/Grid.cpp b/src/Magnum/Primitives/Grid.cpp index d85262df82..68f434710d 100644 --- a/src/Magnum/Primitives/Grid.cpp +++ b/src/Magnum/Primitives/Grid.cpp @@ -109,11 +109,22 @@ Trade::MeshData grid3DSolid(const Vector2i& subdivisions, const GridFlags flags) textureCoords[i] = positions[i].xy()*0.5f + Vector2{0.5f}; } + /* Not using a compile-time attribute array because there's way too many + combinations */ return Trade::MeshData{MeshPrimitive::Triangles, std::move(indexData), Trade::MeshIndexData{indices}, std::move(vertexData), std::move(attributes)}; } +namespace { + +constexpr Trade::MeshAttributeData AttributeData3DWireframe[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + 0, 0, sizeof(Vector3)} +}; + +} + Trade::MeshData grid3DWireframe(const Vector2i& subdivisions) { const Vector2i vertexCount = subdivisions + Vector2i{2}; const Vector2i faceCount = subdivisions + Vector2i{1}; @@ -153,7 +164,9 @@ Trade::MeshData grid3DWireframe(const Vector2i& subdivisions) { return Trade::MeshData{MeshPrimitive::Lines, std::move(indexData), Trade::MeshIndexData{indices}, - std::move(vertexData), {Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions}}}; + std::move(vertexData), + Trade::meshAttributeDataNonOwningArray(AttributeData3DWireframe), + UnsignedInt(vertexCount.product())}; } }} diff --git a/src/Magnum/Primitives/Implementation/Spheroid.cpp b/src/Magnum/Primitives/Implementation/Spheroid.cpp index 8c8d31e39b..377ce59ca8 100644 --- a/src/Magnum/Primitives/Implementation/Spheroid.cpp +++ b/src/Magnum/Primitives/Implementation/Spheroid.cpp @@ -220,37 +220,39 @@ void Spheroid::capVertexRing(Float y, Float textureCoordsV, const Vector3& norma } } -Trade::MeshData Spheroid::finalize() { - Trade::MeshIndexData indices{_indexData}; +namespace { + +constexpr Trade::MeshAttributeData AttributeData[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + offsetof(Vertex, position), 0, sizeof(Vertex)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, + offsetof(Vertex, normal), 0, sizeof(Vertex)} +}; - const std::size_t stride = _textureCoords == TextureCoords::Generate ? - sizeof(VertexTextureCoords) : sizeof(Vertex); - const std::size_t size = _vertexData.size()/stride; +constexpr Trade::MeshAttributeData AttributeDataTextureCoords[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + offsetof(VertexTextureCoords, position), 0, sizeof(VertexTextureCoords)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, + offsetof(VertexTextureCoords, normal), 0, sizeof(VertexTextureCoords)}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, VertexFormat::Vector2, + offsetof(VertexTextureCoords, textureCoords), 0, sizeof(VertexTextureCoords)} +}; - auto typedVertices = reinterpret_cast(_vertexData.data()); - Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, - /* GCC 4.8 needs the arrayView() */ - Containers::stridedArrayView(Containers::arrayView(_vertexData), - &typedVertices[0].position, size, stride)}; - Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, - /* GCC 4.8 needs the arrayView() */ - Containers::stridedArrayView(Containers::arrayView(_vertexData), - &typedVertices[0].normal, size, stride)}; +} + +Trade::MeshData Spheroid::finalize() { + Trade::MeshIndexData indices{_indexData}; Containers::Array attributes; - if(_textureCoords == TextureCoords::Generate) { - Trade::MeshAttributeData textureCoords{Trade::MeshAttribute::TextureCoordinates, - /* GCC 4.8 needs the arrayView() */ - Containers::stridedArrayView(Containers::arrayView(_vertexData), - &typedVertices[0].textureCoords, size, stride)}; - attributes = Containers::Array{Containers::InPlaceInit, {positions, normals, textureCoords}}; - } else { - attributes = Containers::Array{Containers::InPlaceInit, {positions, normals}}; - } + if(_textureCoords == TextureCoords::Generate) + attributes = Trade::meshAttributeDataNonOwningArray(AttributeDataTextureCoords); + else + attributes = Trade::meshAttributeDataNonOwningArray(AttributeData); + const UnsignedInt vertexCount = _vertexData.size()/attributes[0].stride(); return Trade::MeshData{MeshPrimitive::Triangles, Containers::arrayAllocatorCast(std::move(_indexData)), indices, - std::move(_vertexData), std::move(attributes)}; + std::move(_vertexData), std::move(attributes), vertexCount}; } }}} diff --git a/src/Magnum/Primitives/Implementation/WireframeSpheroid.cpp b/src/Magnum/Primitives/Implementation/WireframeSpheroid.cpp index 6b40ca3aa4..29212b416f 100644 --- a/src/Magnum/Primitives/Implementation/WireframeSpheroid.cpp +++ b/src/Magnum/Primitives/Implementation/WireframeSpheroid.cpp @@ -124,12 +124,23 @@ void WireframeSpheroid::cylinder() { arrayAppend(_indexData, {UnsignedInt(_vertexData.size()) - 4*_segments + i, UnsignedInt(_vertexData.size()) + i}); } +namespace { + +constexpr Trade::MeshAttributeData AttributeData[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, VertexFormat::Vector3, + 0, 0, sizeof(Vector3)} +}; + +} + Trade::MeshData WireframeSpheroid::finalize() { Trade::MeshIndexData indices{_indexData}; Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, Containers::arrayView(_vertexData)}; + const UnsignedInt vertexCount = _vertexData.size(); return Trade::MeshData{MeshPrimitive::Lines, Containers::arrayAllocatorCast(std::move(_indexData)), indices, - Containers::arrayAllocatorCast(std::move(_vertexData)), {positions}}; + Containers::arrayAllocatorCast(std::move(_vertexData)), + Trade::meshAttributeDataNonOwningArray(AttributeData), vertexCount}; } }}} From 9a6ef0a2202fd88998ef72514424d4e65592641d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 2 Mar 2020 20:55:54 +0100 Subject: [PATCH 102/107] MeshTools: new interleavedData() utility. --- doc/changelog.dox | 5 +- src/Magnum/MeshTools/Interleave.cpp | 40 ++++++++-- src/Magnum/MeshTools/Interleave.h | 14 +++- src/Magnum/MeshTools/Test/InterleaveTest.cpp | 80 ++++++++++++++++++++ 4 files changed, 128 insertions(+), 11 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 92f9386bb5..ebabb2f28f 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -116,8 +116,9 @@ See also: - Added @ref MeshTools::compile(const Trade::MeshData&) operating on the new @ref Trade::MeshData API -- New @ref MeshTools::isInterleaved() utility for checking if - @ref Trade::MeshData is interleaved +- New @ref MeshTools::isInterleaved() and @ref MeshTools::interleavedData() + utilities for checking if @ref Trade::MeshData is interleaved and for + getting an interleaved view - 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), diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp index ad629adb52..4f959bffc3 100644 --- a/src/Magnum/MeshTools/Interleave.cpp +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -32,23 +32,47 @@ namespace Magnum { namespace MeshTools { -bool isInterleaved(const Trade::MeshData& data) { - /* There is nothing, so yes it is (because there is nothing we could do - to make it interleaved anyway) */ - if(!data.attributeCount()) return true; +namespace { + +Containers::StridedArrayView2D interleavedDataInternal(const Trade::MeshData& data) { + /* There is no attributes, return a non-nullptr zero-sized view to indicate + a success */ + if(!data.attributeCount() || !data.vertexData()) + return Containers::StridedArrayView2D{data.vertexData(), {data.vertexCount(), 0}}; const UnsignedInt stride = data.attributeStride(0); std::size_t minOffset = data.attributeOffset(0); - std::size_t maxOffset = minOffset; - for(UnsignedInt i = 1; i != data.attributeCount(); ++i) { - if(data.attributeStride(i) != stride) return false; + std::size_t maxOffset = minOffset + vertexFormatSize(data.attributeFormat(0)); + for(UnsignedInt i = 0; i != data.attributeCount(); ++i) { + if(data.attributeStride(i) != stride) return nullptr; const std::size_t offset = data.attributeOffset(i); minOffset = Math::min(minOffset, offset); maxOffset = Math::max(maxOffset, offset + vertexFormatSize(data.attributeFormat(i))); } - return maxOffset - minOffset <= stride; + /* The offsets can't fit into the stride, report failure */ + if(maxOffset - minOffset > stride) return nullptr; + + return Containers::StridedArrayView2D{ + data.vertexData(), data.vertexData().data() + minOffset, + {data.vertexCount(), maxOffset - minOffset}, + {std::ptrdiff_t(stride), 1}}; +} + +} + +bool isInterleaved(const Trade::MeshData& data) { + /* If a nullptr value is returned but the mesh vertex data is not nullptr, + the mesh is not interleaved. Otherwise it is */ + return !!interleavedDataInternal(data).data() == !!data.vertexData().data(); +} + +Containers::StridedArrayView2D interleavedData(const Trade::MeshData& data) { + auto out = interleavedDataInternal(data); + CORRADE_ASSERT(out || !data.vertexData(), + "MeshTools::interleavedData(): the mesh is not interleaved", {}); + return out; } Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vertexCount, const Containers::ArrayView extra) { diff --git a/src/Magnum/MeshTools/Interleave.h b/src/Magnum/MeshTools/Interleave.h index 3d5f3cfb5d..1e2f38e670 100644 --- a/src/Magnum/MeshTools/Interleave.h +++ b/src/Magnum/MeshTools/Interleave.h @@ -196,10 +196,22 @@ between minimal and maximal offset is not larger than the stride, @cpp false @ce otherwise. In particular, returns @cpp true @ce also if the mesh has just one or no attributes. @see @ref Trade::MeshData::attributeStride(), - @ref Trade::MeshData::attributeOffset() + @ref Trade::MeshData::attributeOffset(), @ref interleavedData() */ MAGNUM_MESHTOOLS_EXPORT bool isInterleaved(const Trade::MeshData& data); +/** +@brief Type-erased view on interleaved mesh data +@m_since_latest + +Returns a 2D view on @ref Trade::MeshData::vertexData() that spans all +interleaved attributes, with the first dimension being the vertex count and the +second being the attribute stride that's common for all attributes. Expects +that the mesh is interleaved. +@see @ref isInterleaved() +*/ +MAGNUM_MESHTOOLS_EXPORT Containers::StridedArrayView2D interleavedData(const Trade::MeshData& data); + /** @brief Create an interleaved mesh layout @m_since_latest diff --git a/src/Magnum/MeshTools/Test/InterleaveTest.cpp b/src/Magnum/MeshTools/Test/InterleaveTest.cpp index d3542b14b5..142e381f78 100644 --- a/src/Magnum/MeshTools/Test/InterleaveTest.cpp +++ b/src/Magnum/MeshTools/Test/InterleaveTest.cpp @@ -58,6 +58,11 @@ struct InterleaveTest: Corrade::TestSuite::Tester { void isInterleavedUnordered(); void isInterleavedAttributeAcrossStride(); + void interleavedData(); + void interleavedDataNoAttributes(); + void interleavedDataNoVertices(); + void interleavedDataNotInterleaved(); + void interleavedLayout(); void interleavedLayoutExtra(); void interleavedLayoutExtraAliased(); @@ -100,6 +105,11 @@ InterleaveTest::InterleaveTest() { &InterleaveTest::isInterleavedUnordered, &InterleaveTest::isInterleavedAttributeAcrossStride, + &InterleaveTest::interleavedData, + &InterleaveTest::interleavedDataNoAttributes, + &InterleaveTest::interleavedDataNoVertices, + &InterleaveTest::interleavedDataNotInterleaved, + &InterleaveTest::interleavedLayout, &InterleaveTest::interleavedLayoutExtra, &InterleaveTest::interleavedLayoutExtraAliased, @@ -337,6 +347,76 @@ void InterleaveTest::isInterleavedAttributeAcrossStride() { CORRADE_VERIFY(!MeshTools::isInterleaved(data2)); } +void InterleaveTest::interleavedData() { + Containers::Array vertexData{100 + 3*40}; + Containers::StridedArrayView1D normals{vertexData, + reinterpret_cast(vertexData.data() + 100 + 24), 3, 40}; + Containers::StridedArrayView1D positions{vertexData, + reinterpret_cast(vertexData.data() + 100 + 5), 3, 40}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), { + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normals}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions} + }}; + + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + Containers::StridedArrayView2D interleaved = MeshTools::interleavedData(data); + CORRADE_COMPARE(interleaved.data(), positions.data()); + CORRADE_COMPARE(interleaved.size()[0], 3); + CORRADE_COMPARE(interleaved.size()[1], 31); + CORRADE_COMPARE(interleaved.stride()[0], 40); + CORRADE_COMPARE(interleaved.stride()[1], 1); +} + +void InterleaveTest::interleavedDataNoAttributes() { + char a[1]; + Trade::MeshData data{MeshPrimitive::Lines, {}, a, {}, 15}; + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + Containers::StridedArrayView2D interleaved = MeshTools::interleavedData(data); + CORRADE_COMPARE(interleaved.data(), static_cast(a)); + CORRADE_COMPARE(interleaved.size()[0], 15); + CORRADE_COMPARE(interleaved.size()[1], 0); + CORRADE_COMPARE(interleaved.stride()[0], 0); + CORRADE_COMPARE(interleaved.stride()[1], 1); +} + +void InterleaveTest::interleavedDataNoVertices() { + struct Vertex { + Vector3 normal; + Vector3 position; + }; + Vertex a[1]; + Trade::MeshData data{MeshPrimitive::Triangles, {}, a, { + Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + Containers::stridedArrayView(a, &a[0].normal, 0, sizeof(Vertex))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(a, &a[0].position, 0, sizeof(Vertex))} + }}; + + CORRADE_VERIFY(MeshTools::isInterleaved(data)); + Containers::StridedArrayView2D interleaved = MeshTools::interleavedData(data); + CORRADE_COMPARE(interleaved.data(), static_cast(a)); + CORRADE_COMPARE(interleaved.size()[0], 0); + CORRADE_COMPARE(interleaved.size()[1], sizeof(Vertex)); + CORRADE_COMPARE(interleaved.stride()[0], sizeof(Vertex)); + CORRADE_COMPARE(interleaved.stride()[1], 1); +} + +void InterleaveTest::interleavedDataNotInterleaved() { + Containers::Array vertexData{100 + 3*20}; + Trade::MeshAttributeData positions{Trade::MeshAttribute::Position, + Containers::arrayCast(vertexData.suffix(100).prefix(3*8))}; + Trade::MeshAttributeData normals{Trade::MeshAttribute::Normal, + Containers::arrayCast(vertexData.suffix(100).suffix(3*8))}; + + Trade::MeshData data{MeshPrimitive::Triangles, std::move(vertexData), {positions, normals}}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::interleavedData(data); + CORRADE_COMPARE(out.str(), "MeshTools::interleavedData(): the mesh is not interleaved\n"); +} + void InterleaveTest::interleavedLayout() { Containers::Array indexData{6}; Containers::Array vertexData{3*20}; 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 103/107] 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 ebabb2f28f..0de8669d00 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 8ec259769a..749295c758 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 a2c5441714..45f68cf36b 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 4be6f8967e..3d6b8f0e6b 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) From 393ba7a088bb9937628eac4233e256b0218e80f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 7 Mar 2020 20:01:16 +0100 Subject: [PATCH 104/107] MeshTools: implemented concatenate() and concatenateInto(). --- doc/changelog.dox | 2 + doc/snippets/MagnumMeshTools.cpp | 12 + src/Magnum/MeshTools/CMakeLists.txt | 2 + src/Magnum/MeshTools/Concatenate.cpp | 247 ++++++++ src/Magnum/MeshTools/Concatenate.h | 153 +++++ src/Magnum/MeshTools/Interleave.cpp | 48 +- src/Magnum/MeshTools/Interleave.h | 3 + src/Magnum/MeshTools/Test/CMakeLists.txt | 3 + src/Magnum/MeshTools/Test/ConcatenateTest.cpp | 595 ++++++++++++++++++ 9 files changed, 1052 insertions(+), 13 deletions(-) create mode 100644 src/Magnum/MeshTools/Concatenate.cpp create mode 100644 src/Magnum/MeshTools/Concatenate.h create mode 100644 src/Magnum/MeshTools/Test/ConcatenateTest.cpp diff --git a/doc/changelog.dox b/doc/changelog.dox index 0de8669d00..0df47ac2c2 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -133,6 +133,8 @@ See also: differently indexed attributes into a single index buffer, and @ref MeshTools::combineFaceAttributes() for converting per-face attributes into per-vertex +- New @ref MeshTools::concatenate() and @ref MeshTools::concatenateInto() + tool for batching multiple generic meshes together @subsubsection changelog-latest-new-platform Platform libraries diff --git a/doc/snippets/MagnumMeshTools.cpp b/doc/snippets/MagnumMeshTools.cpp index 7c13141b42..32a991c88e 100644 --- a/doc/snippets/MagnumMeshTools.cpp +++ b/doc/snippets/MagnumMeshTools.cpp @@ -26,11 +26,14 @@ #include "Magnum/Math/Color.h" #include "Magnum/Math/FunctionsBatch.h" #include "Magnum/MeshTools/CompressIndices.h" +#include "Magnum/MeshTools/Concatenate.h" #include "Magnum/MeshTools/Duplicate.h" +#include "Magnum/MeshTools/FlipNormals.h" #include "Magnum/MeshTools/GenerateNormals.h" #include "Magnum/MeshTools/Interleave.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Transform.h" +#include "Magnum/Primitives/Cube.h" #include "Magnum/Trade/MeshData.h" #ifdef MAGNUM_BUILD_DEPRECATED @@ -74,6 +77,15 @@ std::pair, MeshIndexType> result = /* [compressIndices-offset] */ } +{ +/* [concatenate-make-mutable] */ +/* Flip triangles on a cube primitive so it's counterclockwise from the inside + in order to render a cube map */ +Trade::MeshData mesh = MeshTools::concatenate(Primitives::cubeSolid()); +MeshTools::flipFaceWindingInPlace(mesh.mutableIndices()); +/* [concatenate-make-mutable] */ +} + #ifdef MAGNUM_BUILD_DEPRECATED { CORRADE_IGNORE_DEPRECATED_PUSH diff --git a/src/Magnum/MeshTools/CMakeLists.txt b/src/Magnum/MeshTools/CMakeLists.txt index 8c3ca0e9c6..b5df5d1389 100644 --- a/src/Magnum/MeshTools/CMakeLists.txt +++ b/src/Magnum/MeshTools/CMakeLists.txt @@ -31,6 +31,7 @@ set(MagnumMeshTools_SRCS set(MagnumMeshTools_GracefulAssert_SRCS Combine.cpp CompressIndices.cpp + Concatenate.cpp Duplicate.cpp FlipNormals.cpp GenerateNormals.cpp @@ -40,6 +41,7 @@ set(MagnumMeshTools_GracefulAssert_SRCS set(MagnumMeshTools_HEADERS Combine.h CompressIndices.h + Concatenate.h Duplicate.h FlipNormals.h GenerateNormals.h diff --git a/src/Magnum/MeshTools/Concatenate.cpp b/src/Magnum/MeshTools/Concatenate.cpp new file mode 100644 index 0000000000..ea08e5ed96 --- /dev/null +++ b/src/Magnum/MeshTools/Concatenate.cpp @@ -0,0 +1,247 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include "Concatenate.h" + +#include +#include +#include + +namespace Magnum { namespace MeshTools { + +namespace Implementation { + +std::pair concatenateIndexVertexCount(const Trade::MeshData& first, const Containers::ArrayView> next) { + UnsignedInt indexCount = first.isIndexed() ? first.indexCount() : 0; + UnsignedInt vertexCount = first.vertexCount(); + for(const Trade::MeshData& mesh: next) { + /* If the mesh is indexed, add to index count. If this is the first + indexed mesh, all previous meshes will have a trivial index buffer + generated for all their vertices */ + if(mesh.isIndexed()) { + if(!indexCount) indexCount = vertexCount; + indexCount += mesh.indexCount(); + + /* Otherwise, if some earlier mesh was indexed, this mesh will have a + trivial index buffer generated for all its vertices */ + } else if(indexCount) indexCount += mesh.vertexCount(); + + vertexCount += mesh.vertexCount(); + } + + return {indexCount, vertexCount}; +} + +/* std::hash for enumeration types is only since C++14, so we need to make our + own. It's amazing how extremely verbose this can get, ugh. */ +struct MeshAttributeHash: std::hash::type> { + std::size_t operator()(Trade::MeshAttribute value) const { + return std::hash::type>::operator()(static_cast::type>(value)); + } +}; + +Trade::MeshData concatenate(Containers::Array&& indexData, const UnsignedInt vertexCount, Containers::Array&& vertexData, Containers::Array&& attributeData, const Trade::MeshData& first, const Containers::ArrayView> next, const char* const assertPrefix, const std::size_t meshIndexOffset) { + #ifdef CORRADE_NO_ASSERT + static_cast(assertPrefix); + static_cast(meshIndexOffset); + #endif + + /* Convert the attributes from offset-only and zero vertex count to + absolute, referencing the vertex data array */ + for(Trade::MeshAttributeData& attribute: attributeData) { + attribute = Trade::MeshAttributeData{ + attribute.name(), attribute.format(), + Containers::StridedArrayView1D{vertexData, + vertexData + attribute.offset(vertexData), + vertexCount, attribute.stride()}}; + } + + /* Only list primitives are supported currently */ + /** @todo delegate to `indexTriangleStrip()` (`duplicate*()`?) etc when + those are done */ + CORRADE_ASSERT( + first.primitive() != MeshPrimitive::LineStrip && + first.primitive() != MeshPrimitive::LineLoop && + first.primitive() != MeshPrimitive::TriangleStrip && + first.primitive() != MeshPrimitive::TriangleFan, + assertPrefix << first.primitive() << "is not supported, turn it into a plain indexed mesh first", + (Trade::MeshData{MeshPrimitive{}, 0})); + + /* Populate the resulting instance with what we have. It'll be used below + for convenient access to vertex / index data */ + auto indices = Containers::arrayCast(indexData); + Trade::MeshData out{first.primitive(), + /* If the index array is empty, we're creating a non-indexed mesh (not + an indexed mesh with zero indices) */ + std::move(indexData), indices.empty() ? + Trade::MeshIndexData{} : Trade::MeshIndexData{indices}, + std::move(vertexData), std::move(attributeData), vertexCount}; + /* Create an attribute map. Yes, this is an inevitable fugly thing that + allocates like mad, while everything else is zero-alloc. + Containers::HashMap can't be here soon enough. */ + std::unordered_multimap, MeshAttributeHash> attributeMap; + attributeMap.reserve(out.attributeCount()); + for(UnsignedInt i = 0; i != out.attributeCount(); ++i) + attributeMap.emplace(out.attributeName(i), std::make_pair(i, false)); + + /* Go through all meshes and put all attributes and index arrays together. + The first mesh might get separately and thus can't be a part of the + view, so abuse the *defined* unsigned integer overflow to add it to the + loop. This probably breaks all coding guidelines on earth tho. */ + std::size_t indexOffset = 0; + std::size_t vertexOffset = 0; + for(std::size_t i = ~std::size_t{}; i != next.size(); ++i) { + const Trade::MeshData& mesh = i == ~std::size_t{} ? first : next[i].get(); + + /* This won't fire for i == ~std::size_t{}, as that's where + out.primitive() comes from */ + CORRADE_ASSERT(mesh.primitive() == out.primitive(), + assertPrefix << "expected" << out.primitive() << "but got" << mesh.primitive() << "in mesh" << i + meshIndexOffset, + (Trade::MeshData{MeshPrimitive{}, 0})); + + /* If the mesh is indexed, copy the indices over, expanded to 32bit */ + if(mesh.isIndexed()) { + Containers::ArrayView dst = indices.slice(indexOffset, indexOffset + mesh.indexCount()); + mesh.indicesInto(dst); + indexOffset += mesh.indexCount(); + + /* Adjust indices for current vertex offset */ + for(UnsignedInt& index: dst) index += vertexOffset; + + /* Otherwise, if we need an index buffer (meaning at least one of the + meshes is indexed), generate a trivial index buffer */ + } else if(!indices.empty()) { + std::iota(indices + indexOffset, indices + indexOffset + mesh.vertexCount(), UnsignedInt(vertexOffset)); + indexOffset += mesh.vertexCount(); + } + + /* Reset markers saying which attribute has already been copied */ + for(auto it = attributeMap.begin(); it != attributeMap.end(); ++it) + it->second.second = false; + + /* Copy attributes to their destination, skipping ones that don't have + any equivalent in the destination mesh */ + for(UnsignedInt src = 0; src != mesh.attributeCount(); ++src) { + /* Go through destination attributes of the same name and find the + earliest one that hasn't been copied yet */ + auto range = attributeMap.equal_range(mesh.attributeName(src)); + UnsignedInt dst = ~UnsignedInt{}; + auto found = attributeMap.end(); + for(auto it = range.first; it != range.second; ++it) { + if(it->second.second) continue; + + /* The range is unordered so we need to go through everything + and pick one with smallest ID */ + if(it->second.first < dst) { + dst = it->second.first; + found = it; + } + } + + /* No corresponding attribute found, continue */ + if(dst == ~UnsignedInt{}) continue; + + /* Check format compatibility. This won't fire for i == + ~std::size_t{}, as that's where out.primitive() comes from */ + CORRADE_ASSERT(out.attributeFormat(dst) == mesh.attributeFormat(src), + assertPrefix << "expected" << out.attributeFormat(dst) << "for attribute" << dst << "(" << Debug::nospace << out.attributeName(dst) << Debug::nospace << ") but got" << mesh.attributeFormat(src) << "in mesh" << i + meshIndexOffset << "attribute" << src, + (Trade::MeshData{MeshPrimitive{}, 0})); + + /* Copy the data to a slice of the output, mark the attribute as + copied */ + Utility::copy(mesh.attribute(src), out.mutableAttribute(dst) + .slice(vertexOffset, vertexOffset + mesh.vertexCount())); + found->second.second = true; + } + + /* Update vertex offset for the next mesh */ + vertexOffset += mesh.vertexCount(); + } + + return out; +} + +} + +Trade::MeshData concatenate(Trade::MeshData&& first, const Containers::ArrayView> next) { + /* If there's just a single non-empty mesh and its data is owned, pass it + through, as it passes the guarantee that the returned data is always + owned. If it's empty, it doesn't matter that we drag it through the rest + as there will be no heavy allocation / copy made (and that also makes + tests easier to write). */ + if(first.indexDataFlags() & Trade::DataFlag::Owned && + first.vertexDataFlags() & Trade::DataFlag::Owned && + first.attributeCount() && first.vertexCount() && next.empty()) + return std::move(first); + + /* Calculate final attribute stride and offsets. Make a non-owning copy of + the attribute data to avoid interleavedLayout() stealing the original + (we still need it to be able to reference the original data). If there's + no attributes in the original array, pass just vertex count --- + otherwise MeshData will assert on that to avoid it getting lost. */ + Containers::Array attributeData; + if(first.attributeCount()) + attributeData = Implementation::interleavedLayout(Trade::MeshData{first.primitive(), + {}, first.vertexData(), + Trade::meshAttributeDataNonOwningArray(first.attributeData())}, {}); + else attributeData = + Implementation::interleavedLayout(Trade::MeshData{first.primitive(), + first.vertexCount()}, {}); + + /* Calculate total index/vertex count and allocate the target memory. + Index data are allocated with NoInit as the whole array will be written, + however vertex data might have holes and thus it's zero-initialized. */ + const std::pair indexVertexCount = Implementation::concatenateIndexVertexCount(first, next); + Containers::Array indexData{Containers::NoInit, + indexVertexCount.first*sizeof(UnsignedInt)}; + Containers::Array vertexData{Containers::ValueInit, + attributeData.empty() ? 0 : (attributeData[0].stride()*indexVertexCount.second)}; + return Implementation::concatenate(std::move(indexData), indexVertexCount.second, std::move(vertexData), std::move(attributeData), first, next, "MeshTools::concatenate():", 0); +} + +Trade::MeshData concatenate(Trade::MeshData&& first, std::initializer_list> next) { + return concatenate(std::move(first), Containers::arrayView(next)); +} + +Trade::MeshData concatenate(const Trade::MeshData& first, const Containers::ArrayView> next) { + Containers::ArrayView indexData; + Trade::MeshIndexData indices; + if(first.isIndexed()) { + indexData = first.indexData(); + indices = Trade::MeshIndexData{first.indices()}; + } + + return concatenate(Trade::MeshData{first.primitive(), + {}, indexData, indices, + {}, first.vertexData(), Trade::meshAttributeDataNonOwningArray(first.attributeData()), + first.vertexCount(), + }, next); +} + +Trade::MeshData concatenate(const Trade::MeshData& first, std::initializer_list> next) { + return concatenate(first, Containers::arrayView(next)); +} + +}} diff --git a/src/Magnum/MeshTools/Concatenate.h b/src/Magnum/MeshTools/Concatenate.h new file mode 100644 index 0000000000..9e78af4a88 --- /dev/null +++ b/src/Magnum/MeshTools/Concatenate.h @@ -0,0 +1,153 @@ +#ifndef Magnum_MeshTools_Concatenate_h +#define Magnum_MeshTools_Concatenate_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +/** @file + * @brief Function @ref Magnum::MeshTools::concatenate(), @ref Magnum::MeshTools::concatenateInto() + * @m_since_latest + */ + +#include +#include + +#include "Magnum/MeshTools/Interleave.h" +#include "Magnum/Trade/MeshData.h" + +namespace Magnum { namespace MeshTools { + +namespace Implementation { + MAGNUM_MESHTOOLS_EXPORT std::pair concatenateIndexVertexCount(const Trade::MeshData& first, const Containers::ArrayView> next); + MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Containers::Array&& indexData, UnsignedInt vertexCount, Containers::Array&& vertexData, Containers::Array&& attributeData, const Trade::MeshData& first, const Containers::ArrayView> next, const char* assertPrefix, std::size_t meshIndexOffset); +} + +/** +@brief Concatenate meshes together +@m_since_latest + +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. + +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 +missing attributes zeroed out. Matching attributes are expected to have the +same type, all meshes are expected to have the same primitive. The vertex data +are concatenated in the same order as passed, with no duplicate removal. +Returned instance vertex and index data flags always have both +@ref Trade::DataFlag::Owned and @ref Trade::DataFlag::Mutable to guarante +mutable access to particular parts of the concatenated mesh --- for example for +applying transformations. + +If an index buffer is needed, @ref MeshIndexType::UnsignedInt is always used. +Call @ref compressIndices(const Trade::MeshData&, MeshIndexType) on the result +to compress it to a smaller type, if desired. +@see @ref concatenate(Trade::MeshData&&, const Containers::ArrayView>), + @ref concatenateInto() +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(const Trade::MeshData& first, const Containers::ArrayView> next = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(const Trade::MeshData& first, std::initializer_list> next); + +/** +@brief Concatenate meshes together +@m_since_latest + +Compared to @ref concatenate(const Trade::MeshData&, const Containers::ArrayView>), +if @p first has both vertex and index data owned and @p next is empty, it's +passed through without any extra allocations or other work. This can be used +for example to ensure a mesh is mutable in order to do various modifications on +its data: + +@snippet MagnumMeshTools.cpp concatenate-make-mutable +*/ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Trade::MeshData&& first, const Containers::ArrayView> next = {}); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Trade::MeshData&& first, std::initializer_list> next); + +/** +@brief Concatenate a list of meshes into a pre-existing destination, enlarging it if necessary +@tparam Allocator Allocator to use +@param[in,out] destination Destination mesh from which the output arrays as + well as desired attribute layout is taken +@param[in] meshes Meshes to concatenate +@m_since_latest + +Compared to @ref concatenate(const Trade::MeshData&, const Containers::ArrayView>) this +function resizes existing index and vertex buffers in @p destination using +@ref Containers::arrayResize() and given @p allocator, and reuses its +atttribute data array instead of always allocating new ones. Only the attribute +layout from @p destination is used, all vertex/index data are taken from +@p meshes. Expects that @p meshes contains at least one item. +*/ +template class Allocator = Containers::ArrayAllocator> void concatenateInto(Trade::MeshData& destination, const Containers::ArrayView> meshes) { + CORRADE_ASSERT(!meshes.empty(), + "MeshTools::concatenateInto(): no meshes passed", ); + + std::pair indexVertexCount = Implementation::concatenateIndexVertexCount(meshes[0], meshes.suffix(1)); + + Containers::Array indexData; + if(indexVertexCount.first) { + indexData = destination.releaseIndexData(); + /* Everything is overwritten here so we don't need to zero-out the + memory */ + Containers::arrayResize(indexData, Containers::NoInit, indexVertexCount.first*sizeof(UnsignedInt)); + } + + Containers::Array attributeData = Implementation::interleavedLayout(std::move(destination), {}); + Containers::Array vertexData; + if(!attributeData.empty() && indexVertexCount.second) { + const UnsignedInt attributeStride = attributeData[0].stride(); + vertexData = destination.releaseVertexData(); + /* Resize to 0 and then to the desired size to zero-out whatever was + there, otherwise attributes that are not present in `meshes` would + be garbage */ + Containers::arrayResize(vertexData, 0); + Containers::arrayResize(vertexData, Containers::ValueInit, attributeStride*indexVertexCount.second); + } + + destination = Implementation::concatenate(std::move(indexData), indexVertexCount.second, std::move(vertexData), std::move(attributeData), meshes[0], meshes.suffix(1), "MeshTools::concatenateInto():", 1); +} + +/** + * @overload + * @m_since_latest + */ +template class Allocator = Containers::ArrayAllocator> void concatenateInto(Trade::MeshData& destination, const std::initializer_list> meshes) { + concatenateInto(destination, Containers::arrayView(meshes)); +} + +}} + +#endif diff --git a/src/Magnum/MeshTools/Interleave.cpp b/src/Magnum/MeshTools/Interleave.cpp index 4f959bffc3..913a16a675 100644 --- a/src/Magnum/MeshTools/Interleave.cpp +++ b/src/Magnum/MeshTools/Interleave.cpp @@ -75,11 +75,11 @@ Containers::StridedArrayView2D interleavedData(const Trade::MeshData return out; } -Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vertexCount, const Containers::ArrayView extra) { - /* If there are no attributes, bail -- return an empty mesh with desired - vertex count but nothing else */ - if(!data.attributeCount() && extra.empty()) - return Trade::MeshData{data.primitive(), vertexCount}; +namespace Implementation { + +Containers::Array interleavedLayout(Trade::MeshData&& data, const Containers::ArrayView extra) { + /* Nothing to do here, bye! */ + if(!data.attributeCount() && extra.empty()) return {}; const bool interleaved = isInterleaved(data); @@ -105,7 +105,7 @@ Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vert for(std::size_t i = 0; i != extra.size(); ++i) { if(extra[i].format() == VertexFormat{}) { CORRADE_ASSERT(extra[i].stride() > 0 || stride >= std::size_t(-extra[i].stride()), - "MeshTools::interleavedLayout(): negative padding" << extra[i].stride() << "in extra attribute" << i << "too large for stride" << stride, (Trade::MeshData{MeshPrimitive::Points, 0})); + "MeshTools::interleavedLayout(): negative padding" << extra[i].stride() << "in extra attribute" << i << "too large for stride" << stride, {}); stride += extra[i].stride(); } else { stride += vertexFormatSize(extra[i].format()); @@ -131,9 +131,6 @@ Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vert Utility::copy(originalAttributeData, attributeData.prefix(originalAttributeCount)); } - /* Allocate new data array */ - Containers::Array vertexData{Containers::NoInit, stride*vertexCount}; - /* Copy existing attribute layout. If the original is already interleaved, preserve relative attribute offsets, otherwise pack tightly. */ std::size_t offset = 0; @@ -142,8 +139,7 @@ Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vert attributeData[i] = Trade::MeshAttributeData{ attributeData[i].name(), attributeData[i].format(), - Containers::StridedArrayView1D{vertexData, vertexData + offset, - vertexCount, std::ptrdiff_t(stride)}}; + offset, 0, std::ptrdiff_t(stride)}; if(!interleaved) offset += vertexFormatSize(attributeData[i].format()); } @@ -164,12 +160,38 @@ Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vert } attributeData[attributeIndex++] = Trade::MeshAttributeData{ - extra[i].name(), extra[i].format(), Containers::StridedArrayView1D{vertexData, vertexData + offset, - vertexCount, std::ptrdiff_t(stride)}}; + extra[i].name(), extra[i].format(), + offset, 0, std::ptrdiff_t(stride)}; offset += vertexFormatSize(extra[i].format()); } + return attributeData; +} + +} + +Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vertexCount, const Containers::ArrayView extra) { + Containers::Array attributeData = Implementation::interleavedLayout(std::move(data), extra); + + /* If there are no attributes, bail -- return an empty mesh with desired + vertex count but nothing else */ + if(!attributeData) + return Trade::MeshData{data.primitive(), vertexCount}; + + /* Allocate new data array */ + Containers::Array vertexData{Containers::NoInit, attributeData[0].stride()*vertexCount}; + + /* Convert the attributes from offset-only and zero vertex count to + absolute, referencing the above-allocated data array */ + for(Trade::MeshAttributeData& attribute: attributeData) { + attribute = Trade::MeshAttributeData{ + attribute.name(), attribute.format(), + Containers::StridedArrayView1D{vertexData, + vertexData + attribute.offset(vertexData), + vertexCount, attribute.stride()}}; + } + return Trade::MeshData{data.primitive(), std::move(vertexData), std::move(attributeData)}; } diff --git a/src/Magnum/MeshTools/Interleave.h b/src/Magnum/MeshTools/Interleave.h index 1e2f38e670..9c73e64c81 100644 --- a/src/Magnum/MeshTools/Interleave.h +++ b/src/Magnum/MeshTools/Interleave.h @@ -114,6 +114,9 @@ template void writeInterleaved(std::size_t stride, char* st writeInterleaved(stride, startingOffset + writeOneInterleaved(stride, startingOffset, first), next...); } +/* Used internally by interleavedLayout() and concatenate() */ +MAGNUM_MESHTOOLS_EXPORT Containers::Array interleavedLayout(Trade::MeshData&& data, Containers::ArrayView extra); + } /** diff --git a/src/Magnum/MeshTools/Test/CMakeLists.txt b/src/Magnum/MeshTools/Test/CMakeLists.txt index cc9d558f8a..8f1913b8bd 100644 --- a/src/Magnum/MeshTools/Test/CMakeLists.txt +++ b/src/Magnum/MeshTools/Test/CMakeLists.txt @@ -25,6 +25,7 @@ corrade_add_test(MeshToolsCombineTest CombineTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsCompressIndicesTest CompressIndicesTest.cpp LIBRARIES MagnumMeshToolsTestLib) +corrade_add_test(MeshToolsConcatenateTest ConcatenateTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsFlipNormalsTest FlipNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsGenerateNormalsTest GenerateNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib MagnumPrimitives) @@ -37,6 +38,7 @@ corrade_add_test(MeshToolsSubdivideRemov___Benchmark SubdivideRemoveDuplicatesBe # Graceful assert for testing set_property(TARGET + MeshToolsConcatenateTest MeshToolsDuplicateTest MeshToolsInterleaveTest MeshToolsRemoveDuplicatesTest @@ -46,6 +48,7 @@ set_property(TARGET set_target_properties( MeshToolsCombineTest MeshToolsCompressIndicesTest + MeshToolsConcatenateTest MeshToolsDuplicateTest MeshToolsFlipNormalsTest MeshToolsGenerateNormalsTest diff --git a/src/Magnum/MeshTools/Test/ConcatenateTest.cpp b/src/Magnum/MeshTools/Test/ConcatenateTest.cpp new file mode 100644 index 0000000000..393a4ce857 --- /dev/null +++ b/src/Magnum/MeshTools/Test/ConcatenateTest.cpp @@ -0,0 +1,595 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + Vladimír Vondruš + + Permission is hereby granted, free of charge, to any person obtaining a + copy of this software and associated documentation files (the "Software"), + to deal in the Software without restriction, including without limitation + the rights to use, copy, modify, merge, publish, distribute, sublicense, + and/or sell copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included + in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + DEALINGS IN THE SOFTWARE. +*/ + +#include +#include +#include +#include + +#include "Magnum/Math/Color.h" +#include "Magnum/MeshTools/Concatenate.h" + +namespace Magnum { namespace MeshTools { namespace Test { namespace { + +struct ConcatenateTest: TestSuite::Tester { + explicit ConcatenateTest(); + + void concatenate(); + void concatenateNotIndexed(); + void concatenateNoAttributes(); + void concatenateNoAttributesNotIndexed(); + void concatenateOne(); + void concatenateOneRvalue(); + void concatenateInto(); + void concatenateIntoNoIndexArray(); + void concatenateIntoNonOwnedAttributeArray(); + + void concatenateUnsupportedPrimitive(); + void concatenateInconsistentPrimitive(); + void concatenateInconsistentAttributeType(); + void concatenateIntoNoMeshes(); +}; + +ConcatenateTest::ConcatenateTest() { + addTests({&ConcatenateTest::concatenate, + &ConcatenateTest::concatenateNotIndexed, + &ConcatenateTest::concatenateNoAttributes, + &ConcatenateTest::concatenateNoAttributesNotIndexed, + &ConcatenateTest::concatenateOne, + &ConcatenateTest::concatenateOneRvalue, + &ConcatenateTest::concatenateInto, + &ConcatenateTest::concatenateIntoNoIndexArray, + &ConcatenateTest::concatenateIntoNonOwnedAttributeArray, + + &ConcatenateTest::concatenateUnsupportedPrimitive, + &ConcatenateTest::concatenateInconsistentPrimitive, + &ConcatenateTest::concatenateInconsistentAttributeType, + &ConcatenateTest::concatenateIntoNoMeshes}); +} + +/* MSVC 2015 doesn't like unnamed bitfields in local structs, so thhis has to + be outside */ +struct VertexDataA { + Vector2 texcoords1; + Vector2 texcoords2; + Int:32; + Vector3 position; +}; + +void ConcatenateTest::concatenate() { + using namespace Math::Literals; + + /* First is non-indexed, this layout (including the gap) will be + preserved */ + const VertexDataA vertexDataA[]{ + {{0.1f, 0.2f}, {0.5f, 0.6f}, {1.0f, 2.0f, 3.0f}}, + {{0.3f, 0.4f}, {0.7f, 0.8f}, {4.0f, 5.0f, 6.0f}} + }; + Trade::MeshData a{MeshPrimitive::Points, {}, vertexDataA, { + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexDataA, + &vertexDataA[0].texcoords1, 2, sizeof(VertexDataA))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexDataA, + &vertexDataA[0].texcoords2, 2, sizeof(VertexDataA))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(vertexDataA, + &vertexDataA[0].position, 2, sizeof(VertexDataA))}, + }}; + + /* Second is indexed, has only one texture coordinate of the two, an extra + color (which gets ignored) and misses the position (which will be + zero-filled) */ + const struct VertexDataB { + Color4 color; + Vector2 texcoords1; + } vertexDataB[]{ + {0x112233_rgbf, {0.15f, 0.25f}}, + {0x445566_rgbf, {0.35f, 0.45f}}, + {0x778899_rgbf, {0.55f, 0.65f}}, + {0xaabbcc_rgbf, {0.75f, 0.85f}} + }; + const UnsignedShort indicesB[]{0, 2, 1, 0, 3, 2}; + Trade::MeshData b{MeshPrimitive::Points, + {}, indicesB, Trade::MeshIndexData{indicesB}, {}, vertexDataB, { + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + Containers::stridedArrayView(vertexDataB, + &vertexDataB[0].color, 4, sizeof(VertexDataB))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexDataB, + &vertexDataB[0].texcoords1, 4, sizeof(VertexDataB))}, + }}; + + /* Third is again non-indexed, has one texcoord attribute more (which will + get ignored). Additionally, attribute memory order is inversed and mixed + together to verify the attributes are picked based on declaration order, + not memory order. */ + const struct VertexDataC { + Vector2 texcoords2; + Vector3 position; + Vector2 texcoords3; + Vector2 texcoords1; + } vertexDataC[]{ + {{0.425f, 0.475f}, {1.5f, 2.5f, 3.5f}, {0.725f, 0.775f}, {0.125f, 0.175f}}, + {{0.525f, 0.575f}, {4.5f, 5.5f, 6.5f}, {0.825f, 0.875f}, {0.225f, 0.275f}}, + {{0.625f, 0.675f}, {7.5f, 8.5f, 9.5f}, {0.925f, 0.975f}, {0.325f, 0.375f}}, + }; + Trade::MeshData c{MeshPrimitive::Points, {}, vertexDataC, { + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexDataC, + &vertexDataC[0].texcoords1, 3, sizeof(VertexDataC))}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::stridedArrayView(vertexDataC, + &vertexDataC[0].position, 3, sizeof(VertexDataC))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexDataC, + &vertexDataC[0].texcoords2, 3, sizeof(VertexDataC))}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::stridedArrayView(vertexDataC, + &vertexDataC[0].texcoords3, 3, sizeof(VertexDataC))}, + }}; + + Trade::MeshData dst = MeshTools::concatenate(a, {b, c}); + CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); + CORRADE_COMPARE(dst.attributeCount(), 3); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f}, + {}, {}, {}, {}, /* Missing in the second mesh */ + {1.5f, 2.5f, 3.5f}, + {4.5f, 5.5f, 6.5f}, + {7.5f, 8.5f, 9.5f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {0.1f, 0.2f}, + {0.3f, 0.4f}, + {0.15f, 0.25f}, + {0.35f, 0.45f}, + {0.55f, 0.65f}, + {0.75f, 0.85f}, + {0.125f, 0.175f}, + {0.225f, 0.275f}, + {0.325f, 0.375f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::TextureCoordinates, 1), + Containers::arrayView({ + {0.5f, 0.6f}, + {0.7f, 0.8f}, + {}, {}, {}, {}, /* Missing in the second mesh */ + {0.425f, 0.475f}, + {0.525f, 0.575f}, + {0.625f, 0.675f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(dst.isIndexed()); + CORRADE_COMPARE(dst.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(dst.indices(), + Containers::arrayView({ + 0, 1, /* implicit for the first nonindexed mesh */ + 2, 4, 3, 2, 5, 4, /* offset for the second indexed mesh */ + 6, 7, 8 /* implicit + offset for the third mesh */ + }), TestSuite::Compare::Container); + + /* The original interleaved layout should be preserved */ + CORRADE_VERIFY(isInterleaved(dst)); + CORRADE_COMPARE(dst.attributeStride(0), sizeof(VertexDataA)); + CORRADE_COMPARE(dst.attributeOffset(0), 0); + CORRADE_COMPARE(dst.attributeOffset(1), sizeof(Vector2)); + CORRADE_COMPARE(dst.attributeOffset(2), 2*sizeof(Vector2) + 4); +} + +void ConcatenateTest::concatenateNotIndexed() { + const Vector3 positionA[]{ + {1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f} + }; + Trade::MeshData a{MeshPrimitive::Points, {}, positionA, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positionA)} + }}; + + const Vector3 positionB[]{ + {1.5f, 2.5f, 3.5f}, + {4.5f, 5.5f, 6.5f}, + {7.5f, 8.5f, 9.5f}, + }; + Trade::MeshData b{MeshPrimitive::Points, {}, positionB, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positionB)} + }}; + + Trade::MeshData dst = MeshTools::concatenate(a, {b, b}); + CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); + CORRADE_COMPARE(dst.attributeCount(), 1); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f}, + {1.5f, 2.5f, 3.5f}, + {4.5f, 5.5f, 6.5f}, + {7.5f, 8.5f, 9.5f}, + {1.5f, 2.5f, 3.5f}, + {4.5f, 5.5f, 6.5f}, + {7.5f, 8.5f, 9.5f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(!dst.isIndexed()); +} + +void ConcatenateTest::concatenateNoAttributes() { + /* Compared to concatenate(), now the first and last is indexed */ + const UnsignedShort indicesA[]{1, 0}; + Trade::MeshData a{MeshPrimitive::Points, {}, indicesA, Trade::MeshIndexData{indicesA}, 2}; + + /* Second is not indexed, just a vertex count */ + Trade::MeshData b{MeshPrimitive::Points, 6}; + + const UnsignedByte indicesC[]{1, 0, 1, 0}; + Trade::MeshData c{MeshPrimitive::Points, {}, indicesC, Trade::MeshIndexData{indicesC}, 2}; + + Trade::MeshData dst = MeshTools::concatenate(a, {b, c}); + CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); + CORRADE_COMPARE(dst.attributeCount(), 0); + CORRADE_COMPARE(dst.vertexCount(), 10); + CORRADE_VERIFY(!dst.vertexData()); + CORRADE_VERIFY(dst.isIndexed()); + CORRADE_COMPARE(dst.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(dst.indices(), + Containers::arrayView({ + 1, 0, + 2, 3, 4, 5, 6, 7, + 9, 8, 9, 8 + }), TestSuite::Compare::Container); +} + +void ConcatenateTest::concatenateNoAttributesNotIndexed() { + Trade::MeshData a{MeshPrimitive::Points, 3}; + Trade::MeshData b{MeshPrimitive::Points, 6}; + Trade::MeshData c{MeshPrimitive::Points, 2}; + + Trade::MeshData dst = MeshTools::concatenate(a, {b, c}); + CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); + CORRADE_COMPARE(dst.attributeCount(), 0); + CORRADE_COMPARE(dst.vertexCount(), 11); + CORRADE_VERIFY(!dst.vertexData()); + CORRADE_VERIFY(!dst.isIndexed()); +} + +/* MSVC 2015 doesn't like unnamed bitfields in local structs, so thhis has to + be outside */ +struct VertexDataNonInterleaved { + Vector2 texcoords1[2]; + Vector2 texcoords2[2]; + Int:32; + Int:32; + Vector3 position[2]; +}; + +void ConcatenateTest::concatenateOne() { + const VertexDataNonInterleaved vertexData[]{{ + {{0.1f, 0.2f}, + {0.3f, 0.4f}}, + {{0.5f, 0.6f}, + {0.7f, 0.8f}}, + {{1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f}} + }}; + const UnsignedByte indices[]{1, 0, 1}; + Trade::MeshData a{MeshPrimitive::Points, + {}, indices, Trade::MeshIndexData{indices}, {}, vertexData, { + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::arrayView(vertexData[0].texcoords1)}, + Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, + Containers::arrayView(vertexData[0].texcoords2)}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(vertexData[0].position)}, + }}; + + Trade::MeshData dst = MeshTools::concatenate(a); + CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); + CORRADE_COMPARE(dst.attributeCount(), 3); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {1.0f, 2.0f, 3.0f}, + {4.0f, 5.0f, 6.0f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::TextureCoordinates), + Containers::arrayView({ + {0.1f, 0.2f}, + {0.3f, 0.4f} + }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::TextureCoordinates, 1), + Containers::arrayView({ + {0.5f, 0.6f}, + {0.7f, 0.8f} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(dst.isIndexed()); + CORRADE_COMPARE(dst.indexType(), MeshIndexType::UnsignedInt); + CORRADE_COMPARE_AS(dst.indices(), + Containers::arrayView({ + 1, 0, 1 + }), TestSuite::Compare::Container); + + /* The mesh should get interleaved (w/o gaps) and owned */ + CORRADE_VERIFY(isInterleaved(dst)); + CORRADE_COMPARE(dst.attributeStride(0), 2*sizeof(Vector2) + sizeof(Vector3)); + CORRADE_COMPARE(dst.indexDataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); + CORRADE_COMPARE(dst.vertexDataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); +} + +void ConcatenateTest::concatenateOneRvalue() { + Containers::Array vertexData{sizeof(Vector2)*4}; + auto positions = Containers::arrayCast(vertexData); + Containers::Array indexData{sizeof(UnsignedInt)*6}; + auto indices = Containers::arrayCast(indexData); + Trade::MeshAttributeData attributeData[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions} + }; + + /* The result should be just a pass-through, as both index and vertex data + are already owned */ + Trade::MeshData dst = MeshTools::concatenate(Trade::MeshData{ + MeshPrimitive::Triangles, + std::move(indexData), Trade::MeshIndexData{indices}, + std::move(vertexData), Trade::meshAttributeDataNonOwningArray(attributeData)}, + /* Explicitly pass an empty init list to ensure this overload is + covered as well */ + std::initializer_list>{}); + CORRADE_COMPARE(dst.indexData().data(), static_cast(indices.data())); + CORRADE_COMPARE(dst.vertexData().data(), static_cast(positions.data())); +} + +void ConcatenateTest::concatenateInto() { + Containers::Array attributeData{2}; + Containers::Array vertexData; + Containers::Array indexData; + arrayResize(vertexData, Containers::DirectInit, (sizeof(Vector2) + sizeof(Vector3))*7, '\xff'); + arrayResize(vertexData, 0); + arrayResize(indexData, Containers::DirectInit, sizeof(UnsignedInt)*9, '\xff'); + arrayResize(indexData, 0); + const void* attributeDataPointer = attributeData; + const void* vertexDataPointer = vertexData; + const void* indexDataPointer = indexData; + + attributeData[0] = Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, nullptr}; + attributeData[1] = Trade::MeshAttributeData{Trade::MeshAttribute::Normal, + VertexFormat::Vector3, nullptr}; + Trade::MeshIndexData indices{MeshIndexType::UnsignedInt, indexData}; + Trade::MeshData dst{MeshPrimitive::Triangles, + std::move(indexData), indices, + std::move(vertexData), std::move(attributeData)}; + + const Vector2 positionsA[]{ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + {-1.0f, 1.0f}, + { 1.0f, 1.0f} + }; + const UnsignedShort indicesA[]{ + 0, 1, 2, 2, 1, 3 + }; + Trade::MeshData a{MeshPrimitive::Triangles, + {}, indicesA, Trade::MeshIndexData{indicesA}, + {}, positionsA, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positionsA)} + }}; + + const Vector2 positionsB[]{ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 0.0f, 1.0f} + }; + Trade::MeshData b{MeshPrimitive::Triangles, + {}, positionsB, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positionsB)} + }}; + + MeshTools::concatenateInto(dst, {a, b}); + CORRADE_COMPARE(dst.attributeCount(), 2); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + {-1.0f, 1.0f}, + { 1.0f, 1.0f}, + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 0.0f, 1.0f} + }), TestSuite::Compare::Container); + /* The normal isn't present in any attribute and thus should be zeroed out + (*not* the whatever garbage present there from before) */ + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Normal), + Containers::arrayView({ + {}, {}, {}, {}, {}, {}, {} + }), TestSuite::Compare::Container); + CORRADE_VERIFY(dst.isIndexed()); + CORRADE_COMPARE_AS(dst.indices(), + Containers::arrayView({ + 0, 1, 2, 2, 1, 3, + 4, 5, 6 + }), TestSuite::Compare::Container); + + /* Verify that no reallocation happened */ + CORRADE_COMPARE(dst.attributeData().size(), 2); + CORRADE_COMPARE(dst.attributeData().data(), attributeDataPointer); + CORRADE_COMPARE(dst.vertexData().size(), 7*(sizeof(Vector2) + sizeof(Vector3))); + CORRADE_COMPARE(dst.vertexData().data(), vertexDataPointer); + CORRADE_COMPARE(dst.indexData().size(), 9*sizeof(UnsignedInt)); + CORRADE_COMPARE(dst.indexData().data(), indexDataPointer); +} + +void ConcatenateTest::concatenateIntoNoIndexArray() { + Containers::Array attributeData{1}; + Containers::Array vertexData; + Containers::Array indexData; + arrayReserve(vertexData, sizeof(Vector2)*3); + arrayReserve(indexData, sizeof(UnsignedInt)); + const void* attributeDataPointer = attributeData; + const void* vertexDataPointer = vertexData; + + attributeData[0] = Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, nullptr}; + Trade::MeshIndexData indices{MeshIndexType::UnsignedInt, indexData}; + Trade::MeshData dst{MeshPrimitive::Triangles, + std::move(indexData), indices, + std::move(vertexData), std::move(attributeData)}; + CORRADE_VERIFY(dst.isIndexed()); + + const Vector2 positions[]{ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 0.0f, 1.0f} + }; + Trade::MeshData a{MeshPrimitive::Triangles, + {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positions)} + }}; + + MeshTools::concatenateInto(dst, {a}); + CORRADE_COMPARE(dst.attributeCount(), 1); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 0.0f, 1.0f} + }), TestSuite::Compare::Container); + + /* The index array gets removed, but no reallocation happens for the other + two */ + CORRADE_VERIFY(!dst.isIndexed()); + CORRADE_COMPARE(dst.attributeData().size(), 1); + CORRADE_COMPARE(dst.attributeData().data(), attributeDataPointer); + CORRADE_COMPARE(dst.vertexData().size(), 3*sizeof(Vector2)); + CORRADE_COMPARE(dst.vertexData().data(), vertexDataPointer); +} + +void ConcatenateTest::concatenateIntoNonOwnedAttributeArray() { + Containers::Array vertexData; + arrayReserve(vertexData, sizeof(Vector2)*3); + const void* vertexDataPointer = vertexData; + + const Trade::MeshAttributeData attributeData[]{ + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector2, nullptr} + }; + Trade::MeshData dst{MeshPrimitive::Triangles, + std::move(vertexData), Trade::meshAttributeDataNonOwningArray(attributeData)}; + + const Vector2 positions[]{ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 0.0f, 1.0f} + }; + Trade::MeshData a{MeshPrimitive::Triangles, + {}, positions, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + Containers::arrayView(positions)} + }}; + + MeshTools::concatenateInto(dst, {a}); + CORRADE_COMPARE(dst.attributeCount(), 1); + CORRADE_COMPARE_AS(dst.attribute(Trade::MeshAttribute::Position), + Containers::arrayView({ + {-1.0f, -1.0f}, + { 1.0f, -1.0f}, + { 0.0f, 1.0f} + }), TestSuite::Compare::Container); + + /* Reallocation happens only for the attribute data as it's not owned */ + CORRADE_VERIFY(!dst.isIndexed()); + CORRADE_COMPARE(dst.attributeData().size(), 1); + CORRADE_VERIFY(dst.attributeData().data() != attributeData); + CORRADE_COMPARE(dst.vertexData().size(), 3*sizeof(Vector2)); + CORRADE_COMPARE(dst.vertexData().data(), vertexDataPointer); +} + +void ConcatenateTest::concatenateUnsupportedPrimitive() { + Trade::MeshData a{MeshPrimitive::TriangleStrip, 0}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::concatenate(a); + MeshTools::concatenateInto(a, {a}); + CORRADE_COMPARE(out.str(), + "MeshTools::concatenate(): MeshPrimitive::TriangleStrip is not supported, turn it into a plain indexed mesh first\n" + "MeshTools::concatenateInto(): MeshPrimitive::TriangleStrip is not supported, turn it into a plain indexed mesh first\n"); +} + +void ConcatenateTest::concatenateInconsistentPrimitive() { + /* Things are a bit duplicated to test correct numbering */ + Trade::MeshData a{MeshPrimitive::Triangles, 0}; + Trade::MeshData b{MeshPrimitive::Lines, 0}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::concatenate(a, {a, b}); + MeshTools::concatenateInto(a, {a, b}); + CORRADE_COMPARE(out.str(), + "MeshTools::concatenate(): expected MeshPrimitive::Triangles but got MeshPrimitive::Lines in mesh 1\n" + "MeshTools::concatenateInto(): expected MeshPrimitive::Triangles but got MeshPrimitive::Lines in mesh 1\n"); +} + +void ConcatenateTest::concatenateInconsistentAttributeType() { + /* Things are a bit duplicated to test correct numbering */ + Trade::MeshData a{MeshPrimitive::Lines, nullptr, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector3, nullptr}, + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector3, nullptr}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + VertexFormat::Vector3ubNormalized, nullptr} + }}; + Trade::MeshData b{MeshPrimitive::Lines, nullptr, { + Trade::MeshAttributeData{Trade::MeshAttribute::Position, + VertexFormat::Vector3, nullptr}, + Trade::MeshAttributeData{Trade::MeshAttribute::Color, + VertexFormat::Vector3usNormalized, nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::concatenate(a, {a, a, a, b}); + MeshTools::concatenateInto(a, {a, a, a, b}); + CORRADE_COMPARE(out.str(), + "MeshTools::concatenate(): expected VertexFormat::Vector3ubNormalized for attribute 2 (Trade::MeshAttribute::Color) but got VertexFormat::Vector3usNormalized in mesh 3 attribute 1\n" + "MeshTools::concatenateInto(): expected VertexFormat::Vector3ubNormalized for attribute 2 (Trade::MeshAttribute::Color) but got VertexFormat::Vector3usNormalized in mesh 3 attribute 1\n"); +} + +void ConcatenateTest::concatenateIntoNoMeshes() { + Trade::MeshData destination{MeshPrimitive::Triangles, 0}; + + std::ostringstream out; + Error redirectError{&out}; + MeshTools::concatenateInto(destination, {}); + CORRADE_COMPARE(out.str(), "MeshTools::concatenateInto(): no meshes passed\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::MeshTools::Test::ConcatenateTest) From 715f6114de9a65a51433d85de56fcd7e93e44bbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 10 Mar 2020 10:46:38 +0100 Subject: [PATCH 105/107] MeshTools: reorder code. No functional change. --- src/Magnum/MeshTools/Compile.cpp | 152 +++++++++++++++---------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/src/Magnum/MeshTools/Compile.cpp b/src/Magnum/MeshTools/Compile.cpp index fa7938fa36..3ef943f764 100644 --- a/src/Magnum/MeshTools/Compile.cpp +++ b/src/Magnum/MeshTools/Compile.cpp @@ -52,82 +52,6 @@ namespace Magnum { namespace MeshTools { -GL::Mesh compile(const Trade::MeshData& meshData, CompileFlags flags) { - /* If we want to generate normals, prepare a new mesh data and recurse, - with the flags unset */ - if(meshData.primitive() == MeshPrimitive::Triangles && (flags & (CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals))) { - CORRADE_ASSERT(meshData.attributeCount(Trade::MeshAttribute::Position), - "MeshTools::compile(): the mesh has no positions, can't generate normals", GL::Mesh{}); - /* This could fire if we have 2D positions or for packed formats */ - CORRADE_ASSERT(meshData.attributeFormat(Trade::MeshAttribute::Position) == VertexFormat::Vector3, - "MeshTools::compile(): can't generate normals for" << meshData.attributeFormat(Trade::MeshAttribute::Position) << "positions", GL::Mesh{}); - - /* If the data already have a normal array, reuse its location, - otherwise mix in an extra one */ - Trade::MeshAttributeData normalAttribute; - Containers::ArrayView extra; - if(!meshData.hasAttribute(Trade::MeshAttribute::Normal)) { - normalAttribute = Trade::MeshAttributeData{ - Trade::MeshAttribute::Normal, VertexFormat::Vector3, - nullptr}; - extra = {&normalAttribute, 1}; - /* If we reuse a normal location, expect correct type */ - } else CORRADE_ASSERT(meshData.attributeFormat(Trade::MeshAttribute::Normal) == VertexFormat::Vector3, - "MeshTools::compile(): can't generate normals into" << meshData.attributeFormat(Trade::MeshAttribute::Normal), GL::Mesh{}); - - /* If we want flat normals, we need to first duplicate everything using - the index buffer. Otherwise just interleave the potential extra - normal attribute in. */ - Trade::MeshData generated{MeshPrimitive::Points, 0}; - if(flags & CompileFlag::GenerateFlatNormals && meshData.isIndexed()) - generated = duplicate(meshData, extra); - else - generated = interleave(meshData, extra); - - /* Generate the normals. If we don't have the index buffer, we can only - generate flat ones. */ - if(flags & CompileFlag::GenerateFlatNormals || !meshData.isIndexed()) - generateFlatNormalsInto( - generated.attribute(Trade::MeshAttribute::Position), - generated.mutableAttribute(Trade::MeshAttribute::Normal)); - else - generateSmoothNormalsInto(generated.indices(), - generated.attribute(Trade::MeshAttribute::Position), - generated.mutableAttribute(Trade::MeshAttribute::Normal)); - - return compile(generated, flags & ~(CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals)); - } - - flags &= ~(CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals); - CORRADE_INTERNAL_ASSERT(!flags); - return compile(meshData); -} - -GL::Mesh compile(const Trade::MeshData& meshData) { - GL::Buffer indices{NoCreate}; - if(meshData.isIndexed()) { - indices = GL::Buffer{GL::Buffer::TargetHint::ElementArray}; - indices.setData(meshData.indexData()); - } - - GL::Buffer vertices{GL::Buffer::TargetHint::Array}; - vertices.setData(meshData.vertexData()); - - return compile(meshData, std::move(indices), std::move(vertices)); -} - -GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer& vertices) { - return compile(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array)); -} - -GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer&& vertices) { - return compile(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), std::move(vertices)); -} - -GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer& vertices) { - return compile(meshData, std::move(indices), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array)); -} - GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer&& vertices) { CORRADE_ASSERT((!meshData.isIndexed() || indices.id()) && vertices.id(), "MeshTools::compile(): invalid external buffer(s)", GL::Mesh{}); @@ -198,6 +122,82 @@ GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buff return mesh; } +GL::Mesh compile(const Trade::MeshData& meshData) { + GL::Buffer indices{NoCreate}; + if(meshData.isIndexed()) { + indices = GL::Buffer{GL::Buffer::TargetHint::ElementArray}; + indices.setData(meshData.indexData()); + } + + GL::Buffer vertices{GL::Buffer::TargetHint::Array}; + vertices.setData(meshData.vertexData()); + + return compile(meshData, std::move(indices), std::move(vertices)); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer& vertices) { + return compile(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array)); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer&& vertices) { + return compile(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), std::move(vertices)); +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer& vertices) { + return compile(meshData, std::move(indices), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array)); +} + +GL::Mesh compile(const Trade::MeshData& meshData, CompileFlags flags) { + /* If we want to generate normals, prepare a new mesh data and recurse, + with the flags unset */ + if(meshData.primitive() == MeshPrimitive::Triangles && (flags & (CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals))) { + CORRADE_ASSERT(meshData.attributeCount(Trade::MeshAttribute::Position), + "MeshTools::compile(): the mesh has no positions, can't generate normals", GL::Mesh{}); + /* This could fire if we have 2D positions or for packed formats */ + CORRADE_ASSERT(meshData.attributeFormat(Trade::MeshAttribute::Position) == VertexFormat::Vector3, + "MeshTools::compile(): can't generate normals for" << meshData.attributeFormat(Trade::MeshAttribute::Position) << "positions", GL::Mesh{}); + + /* If the data already have a normal array, reuse its location, + otherwise mix in an extra one */ + Trade::MeshAttributeData normalAttribute; + Containers::ArrayView extra; + if(!meshData.hasAttribute(Trade::MeshAttribute::Normal)) { + normalAttribute = Trade::MeshAttributeData{ + Trade::MeshAttribute::Normal, VertexFormat::Vector3, + nullptr}; + extra = {&normalAttribute, 1}; + /* If we reuse a normal location, expect correct type */ + } else CORRADE_ASSERT(meshData.attributeFormat(Trade::MeshAttribute::Normal) == VertexFormat::Vector3, + "MeshTools::compile(): can't generate normals into" << meshData.attributeFormat(Trade::MeshAttribute::Normal), GL::Mesh{}); + + /* If we want flat normals, we need to first duplicate everything using + the index buffer. Otherwise just interleave the potential extra + normal attribute in. */ + Trade::MeshData generated{MeshPrimitive::Points, 0}; + if(flags & CompileFlag::GenerateFlatNormals && meshData.isIndexed()) + generated = duplicate(meshData, extra); + else + generated = interleave(meshData, extra); + + /* Generate the normals. If we don't have the index buffer, we can only + generate flat ones. */ + if(flags & CompileFlag::GenerateFlatNormals || !meshData.isIndexed()) + generateFlatNormalsInto( + generated.attribute(Trade::MeshAttribute::Position), + generated.mutableAttribute(Trade::MeshAttribute::Normal)); + else + generateSmoothNormalsInto(generated.indices(), + generated.attribute(Trade::MeshAttribute::Position), + generated.mutableAttribute(Trade::MeshAttribute::Normal)); + + return compile(generated, flags & ~(CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals)); + } + + flags &= ~(CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals); + CORRADE_INTERNAL_ASSERT(!flags); + return compile(meshData); +} + #ifdef MAGNUM_BUILD_DEPRECATED CORRADE_IGNORE_DEPRECATED_PUSH GL::Mesh compile(const Trade::MeshData2D& meshData) { From a4bf0e61a12be342a1e66c65fbe424970a400972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 10 Mar 2020 12:12:23 +0100 Subject: [PATCH 106/107] MeshTools: option to disable unknown attribute warnings in compile(). --- src/Magnum/MeshTools/Compile.cpp | 36 +++++++++++++----- src/Magnum/MeshTools/Compile.h | 21 ++++++++++- src/Magnum/MeshTools/Test/CompileGLTest.cpp | 42 ++++++++++++++++----- 3 files changed, 77 insertions(+), 22 deletions(-) diff --git a/src/Magnum/MeshTools/Compile.cpp b/src/Magnum/MeshTools/Compile.cpp index 3ef943f764..7905e67305 100644 --- a/src/Magnum/MeshTools/Compile.cpp +++ b/src/Magnum/MeshTools/Compile.cpp @@ -52,7 +52,11 @@ namespace Magnum { namespace MeshTools { -GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer&& vertices) { +namespace { + +GL::Mesh compileInternal(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer&& vertices, const CompileFlags flags) { + /* Only this one flag is allowed at this point */ + CORRADE_INTERNAL_ASSERT(!(flags & ~CompileFlag::NoWarnOnCustomAttributes)); CORRADE_ASSERT((!meshData.isIndexed() || indices.id()) && vertices.id(), "MeshTools::compile(): invalid external buffer(s)", GL::Mesh{}); @@ -70,7 +74,8 @@ GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buff single 32-bit value :( */ const VertexFormat format = meshData.attributeFormat(i); if(isVertexFormatImplementationSpecific(format)) { - Warning{} << "MeshTools::compile(): ignoring attribute" << meshData.attributeName(i) << "with an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)); + if(!(flags & CompileFlag::NoWarnOnCustomAttributes)) + Warning{} << "MeshTools::compile(): ignoring attribute" << meshData.attributeName(i) << "with an implementation-specific format" << reinterpret_cast(vertexFormatUnwrap(format)); continue; } @@ -101,7 +106,8 @@ GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buff } if(!attribute) { - Warning{} << "MeshTools::compile(): ignoring unknown attribute" << meshData.attributeName(i); + if(!(flags & CompileFlag::NoWarnOnCustomAttributes)) + Warning{} << "MeshTools::compile(): ignoring unknown attribute" << meshData.attributeName(i); continue; } @@ -122,7 +128,7 @@ GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buff return mesh; } -GL::Mesh compile(const Trade::MeshData& meshData) { +GL::Mesh compileInternal(const Trade::MeshData& meshData, const CompileFlags flags) { GL::Buffer indices{NoCreate}; if(meshData.isIndexed()) { indices = GL::Buffer{GL::Buffer::TargetHint::ElementArray}; @@ -132,19 +138,29 @@ GL::Mesh compile(const Trade::MeshData& meshData) { GL::Buffer vertices{GL::Buffer::TargetHint::Array}; vertices.setData(meshData.vertexData()); - return compile(meshData, std::move(indices), std::move(vertices)); + return compileInternal(meshData, std::move(indices), std::move(vertices), flags); +} + +} + +GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer&& vertices) { + return compileInternal(meshData, std::move(indices), std::move(vertices), {}); } GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer& vertices) { - return compile(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array)); + return compileInternal(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array), CompileFlag::NoWarnOnCustomAttributes); } GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer&& vertices) { - return compile(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), std::move(vertices)); + return compileInternal(meshData, GL::Buffer::wrap(indices.id(), GL::Buffer::TargetHint::ElementArray), std::move(vertices), CompileFlag::NoWarnOnCustomAttributes); } GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer&& indices, GL::Buffer& vertices) { - return compile(meshData, std::move(indices), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array)); + return compileInternal(meshData, std::move(indices), GL::Buffer::wrap(vertices.id(), GL::Buffer::TargetHint::Array), CompileFlag::NoWarnOnCustomAttributes); +} + +GL::Mesh compile(const Trade::MeshData& meshData) { + return compileInternal(meshData, {}); } GL::Mesh compile(const Trade::MeshData& meshData, CompileFlags flags) { @@ -194,8 +210,8 @@ GL::Mesh compile(const Trade::MeshData& meshData, CompileFlags flags) { } flags &= ~(CompileFlag::GenerateFlatNormals|CompileFlag::GenerateSmoothNormals); - CORRADE_INTERNAL_ASSERT(!flags); - return compile(meshData); + CORRADE_INTERNAL_ASSERT(!(flags & ~CompileFlag::NoWarnOnCustomAttributes)); + return compileInternal(meshData, flags); } #ifdef MAGNUM_BUILD_DEPRECATED diff --git a/src/Magnum/MeshTools/Compile.h b/src/Magnum/MeshTools/Compile.h index 3353495862..8392c79279 100644 --- a/src/Magnum/MeshTools/Compile.h +++ b/src/Magnum/MeshTools/Compile.h @@ -72,7 +72,18 @@ enum class CompileFlag: UnsignedByte { * is not a triangle mesh or doesn't have 3D positions, this flag does * nothing. If the mesh already has its own normals, these get replaced. */ - GenerateSmoothNormals = 1 << 1 + GenerateSmoothNormals = 1 << 1, + + /** + * By default, @ref compile() warns when it encounters custom attributes + * and attributes with implementation-specific format, as those get ignored + * by it. If you're binding those manually with + * @ref compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&) or + * handling them in some other way on the application side already, use + * this flag to suppress the warning messages. + * @m_since_latest + */ + NoWarnOnCustomAttributes = 1 << 2 }; /** @@ -105,7 +116,8 @@ possibly also an index buffer, if the mesh is indexed. their type. - Custom attributes and known attributes of implementation-specific types are ignored with a warning. See the @ref compile(const Trade::MeshData&, GL::Buffer&, GL::Buffer&) - for an example showing how to bind them manually. + for an example showing how to bind them manually, and + @ref CompileFlag::NoWarnOnCustomAttributes to suppress the warning. If normal generation is not requested, @ref Trade::MeshData::indexData() and @ref Trade::MeshData::vertexData() are uploaded as-is without any further @@ -158,6 +170,11 @@ by the mesh or not: If @p meshData is not indexed, the @p indices parameter is ignored --- in that case you can pass a @ref NoCreate "NoCreate"-d instance to avoid allocating an unnecessary OpenGL buffer object. + +Compared to @ref compile(const Trade::MeshData&, CompileFlags), this function +implicitly enables the @ref CompileFlag::NoWarnOnCustomAttributes flag, +assuming that custom attributes and attributes with implementation-specific +formats are explicitly handled on the application side. */ MAGNUM_MESHTOOLS_EXPORT GL::Mesh compile(const Trade::MeshData& meshData, GL::Buffer& indices, GL::Buffer& vertices); diff --git a/src/Magnum/MeshTools/Test/CompileGLTest.cpp b/src/Magnum/MeshTools/Test/CompileGLTest.cpp index 7debc5f29d..b68344f704 100644 --- a/src/Magnum/MeshTools/Test/CompileGLTest.cpp +++ b/src/Magnum/MeshTools/Test/CompileGLTest.cpp @@ -93,7 +93,7 @@ struct CompileGLTest: GL::OpenGLTester { void packedAttributes(); - void unknownAttribute(); + void customAttribute(); void implementationSpecificAttributeFormat(); void generateNormalsNoPosition(); void generateNormals2DPosition(); @@ -170,6 +170,14 @@ constexpr struct { {"move both", true, true, true} }; +constexpr struct { + const char* name; + CompileFlags flags; +} CustomAttributeWarningData[] { + {"", {}}, + {"no warning", CompileFlag::NoWarnOnCustomAttributes} +}; + using namespace Math::Literals; constexpr Color4ub ImageData[] { @@ -203,11 +211,13 @@ CompileGLTest::CompileGLTest() { CORRADE_IGNORE_DEPRECATED_POP #endif - addTests({&CompileGLTest::packedAttributes, + addTests({&CompileGLTest::packedAttributes}); + + addInstancedTests({&CompileGLTest::customAttribute, + &CompileGLTest::implementationSpecificAttributeFormat}, + Containers::arraySize(CustomAttributeWarningData)); - &CompileGLTest::unknownAttribute, - &CompileGLTest::implementationSpecificAttributeFormat, - &CompileGLTest::generateNormalsNoPosition, + addTests({&CompileGLTest::generateNormalsNoPosition, &CompileGLTest::generateNormals2DPosition, &CompileGLTest::generateNormalsNoFloats}); @@ -714,27 +724,39 @@ void CompileGLTest::packedAttributes() { (DebugTools::CompareImageToFile{_manager, 1.0f, 0.0948f})); } -void CompileGLTest::unknownAttribute() { +void CompileGLTest::customAttribute() { + auto&& instanceData = CustomAttributeWarningData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + Trade::MeshData data{MeshPrimitive::Triangles, nullptr, {Trade::MeshAttributeData{Trade::meshAttributeCustom(115), VertexFormat::Short, nullptr}}}; std::ostringstream out; Warning redirectError{&out}; - MeshTools::compile(data); - CORRADE_COMPARE(out.str(), + if(instanceData.flags) + MeshTools::compile(data, instanceData.flags); + else + MeshTools::compile(data); + CORRADE_COMPARE(out.str(), instanceData.flags ? "" : "MeshTools::compile(): ignoring unknown attribute Trade::MeshAttribute::Custom(115)\n"); } void CompileGLTest::implementationSpecificAttributeFormat() { + auto&& instanceData = CustomAttributeWarningData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + Trade::MeshData data{MeshPrimitive::Triangles, nullptr, {Trade::MeshAttributeData{Trade::MeshAttribute::Position, vertexFormatWrap(0xdead), nullptr}}}; std::ostringstream out; Warning redirectError{&out}; - MeshTools::compile(data); - CORRADE_COMPARE(out.str(), + if(instanceData.flags) + MeshTools::compile(data, instanceData.flags); + else + MeshTools::compile(data); + CORRADE_COMPARE(out.str(), instanceData.flags ? "" : "MeshTools::compile(): ignoring attribute Trade::MeshAttribute::Position with an implementation-specific format 0xdead\n"); } From 236954ad166275135b0b8d9d56ea6285d643c7e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 11 Mar 2020 17:58:28 +0100 Subject: [PATCH 107/107] CMake: MeshTools now depends on Trade unconditionally. Isn't it great when I discover this five minutes before merging to master? --- modules/FindMagnum.cmake | 5 ++--- src/Magnum/MeshTools/CMakeLists.txt | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/FindMagnum.cmake b/modules/FindMagnum.cmake index daef6d5249..09569afaf9 100644 --- a/modules/FindMagnum.cmake +++ b/modules/FindMagnum.cmake @@ -378,10 +378,9 @@ if(MAGNUM_TARGET_GL) set(_MAGNUM_DebugTools_GL_DEPENDENCY_IS_OPTIONAL ON) endif() -set(_MAGNUM_MeshTools_DEPENDENCIES ) +set(_MAGNUM_MeshTools_DEPENDENCIES Trade) if(MAGNUM_TARGET_GL) - # Trade is used only in compile(), which needs GL as well - list(APPEND _MAGNUM_MeshTools_DEPENDENCIES Trade GL) + list(APPEND _MAGNUM_MeshTools_DEPENDENCIES GL) endif() set(_MAGNUM_OpenGLTester_DEPENDENCIES GL) diff --git a/src/Magnum/MeshTools/CMakeLists.txt b/src/Magnum/MeshTools/CMakeLists.txt index b5df5d1389..67cc0d2aef 100644 --- a/src/Magnum/MeshTools/CMakeLists.txt +++ b/src/Magnum/MeshTools/CMakeLists.txt @@ -106,9 +106,9 @@ elseif(BUILD_STATIC_PIC) set_target_properties(MagnumMeshTools PROPERTIES POSITION_INDEPENDENT_CODE ON) endif() target_link_libraries(MagnumMeshTools PUBLIC - Magnum) + Magnum MagnumTrade) if(TARGET_GL) - target_link_libraries(MagnumMeshTools PUBLIC MagnumGL MagnumTrade) + target_link_libraries(MagnumMeshTools PUBLIC MagnumGL) endif() install(TARGETS MagnumMeshTools @@ -131,9 +131,9 @@ if(BUILD_TESTS) set_target_properties(MagnumMeshToolsTestLib PROPERTIES POSITION_INDEPENDENT_CODE ON) endif() target_link_libraries(MagnumMeshToolsTestLib PUBLIC - Magnum) + Magnum MagnumTrade) if(TARGET_GL) - target_link_libraries(MagnumMeshToolsTestLib PUBLIC MagnumGL MagnumTrade) + target_link_libraries(MagnumMeshToolsTestLib PUBLIC MagnumGL) endif() add_subdirectory(Test)