From d846944b7a4bb628eb7b0fd0de533937b981b300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Wed, 20 May 2020 12:51:00 +0200 Subject: [PATCH] MeshTools: de-template removeDuplicatesFuzzy(). Basically using the same idea as with the discrete version -- having the second dimension dynamic, together with restricting the implementation to just Float and Double. According to the SubdivideRemoveDuplicatesBenchmark, this makes the implementation slightly slower. I presume this is due to how minmax and offsets are calculated which is quite cache-inefficient as it goes over the same memory block multiple times. Added a TODO for later. --- doc/custom-buildsystems-order.dot | 1 + doc/snippets/MagnumMeshTools.cpp | 3 + modules/FindMagnum.cmake | 2 +- src/Magnum/MeshTools/Combine.cpp | 1 + src/Magnum/MeshTools/RemoveDuplicates.cpp | 190 ++++++++++++++++++ src/Magnum/MeshTools/RemoveDuplicates.h | 183 +++++++---------- .../MeshTools/Test/RemoveDuplicatesTest.cpp | 165 +++++++++------ .../SubdivideRemoveDuplicatesBenchmark.cpp | 6 +- src/Magnum/MeshTools/Test/SubdivideTest.cpp | 1 + src/Magnum/Primitives/CMakeLists.txt | 1 + src/Magnum/Primitives/Icosphere.cpp | 4 +- 11 files changed, 382 insertions(+), 175 deletions(-) diff --git a/doc/custom-buildsystems-order.dot b/doc/custom-buildsystems-order.dot index 8ab4fa1fd..45172a28d 100644 --- a/doc/custom-buildsystems-order.dot +++ b/doc/custom-buildsystems-order.dot @@ -79,6 +79,7 @@ digraph "Magnum library dependency order" { MagnumOpenGLTester -> MagnumWindowlessApplication + MagnumPrimitives -> MagnumMeshTools MagnumPrimitives -> MagnumTrade MagnumSceneGraph -> Magnum diff --git a/doc/snippets/MagnumMeshTools.cpp b/doc/snippets/MagnumMeshTools.cpp index 32a991c88..28386e637 100644 --- a/doc/snippets/MagnumMeshTools.cpp +++ b/doc/snippets/MagnumMeshTools.cpp @@ -23,6 +23,9 @@ DEALINGS IN THE SOFTWARE. */ +#include +#include + #include "Magnum/Math/Color.h" #include "Magnum/Math/FunctionsBatch.h" #include "Magnum/MeshTools/CompressIndices.h" diff --git a/modules/FindMagnum.cmake b/modules/FindMagnum.cmake index 898030fc8..03b2e2c53 100644 --- a/modules/FindMagnum.cmake +++ b/modules/FindMagnum.cmake @@ -416,7 +416,7 @@ elseif(CORRADE_TARGET_WINDOWS) endif() endif() -set(_MAGNUM_Primitives_DEPENDENCIES Trade) +set(_MAGNUM_Primitives_DEPENDENCIES MeshTools Trade) set(_MAGNUM_SceneGraph_DEPENDENCIES ) set(_MAGNUM_Shaders_DEPENDENCIES GL) set(_MAGNUM_Text_DEPENDENCIES TextureTools) diff --git a/src/Magnum/MeshTools/Combine.cpp b/src/Magnum/MeshTools/Combine.cpp index 434136fc5..b7fd7d8f1 100644 --- a/src/Magnum/MeshTools/Combine.cpp +++ b/src/Magnum/MeshTools/Combine.cpp @@ -30,6 +30,7 @@ #include #include +#include "Magnum/Math/Functions.h" #include "Magnum/MeshTools/Interleave.h" #include "Magnum/MeshTools/Duplicate.h" #include "Magnum/MeshTools/RemoveDuplicates.h" diff --git a/src/Magnum/MeshTools/RemoveDuplicates.cpp b/src/Magnum/MeshTools/RemoveDuplicates.cpp index bda4f18d9..af875ddd6 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.cpp +++ b/src/Magnum/MeshTools/RemoveDuplicates.cpp @@ -26,9 +26,16 @@ #include "RemoveDuplicates.h" #include +#include +#include +#include +#include #include #include +#include +#include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/Math/Range.h" #include "Magnum/MeshTools/Concatenate.h" #include "Magnum/MeshTools/Interleave.h" #include "Magnum/Trade/MeshData.h" @@ -173,6 +180,189 @@ std::size_t removeDuplicatesIndexedInPlace(const Containers::StridedArrayView2D< } } +namespace { + +template std::size_t removeDuplicatesFuzzyIndexedInPlaceImplementation(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, T epsilon) { + /* Compared to the discrete version, we don't require the second dimension + to be contiguous, as we calculate the hash from a discretized contiguous + copy */ + + /* 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::removeDuplicatesFuzzyIndexedInPlace(): a" << sizeof(IndexType) << Debug::nospace << "-byte index type is too small for" << data.size()[0] << "vertices", {}); + + /* Get bounds across all dimensions. 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. */ + const std::size_t vectorSize = data.size()[1]; + T range = T(0.0); + Containers::Array offsets{Containers::NoInit, vectorSize}; + { + /** @todo this isn't really cache-efficient, do differently */ + std::size_t i = 0; + for(Containers::StridedArrayView1D dimension: data.template transposed<0, 1>()) { + const Math::Range1D minmax = Math::minmax(dimension); + range = Math::max(minmax.size(), range); + offsets[i++] = minmax.min(); + } + } + + /* Make epsilon so large that std::size_t can index all vectors inside the + bounds. */ + epsilon = Math::max(epsilon, range/T(~std::size_t{})); + + /* Table containing original vector index for each discretized vector. + Reserving more buckets than necessary (i.e. as if each vector was + unique). */ + std::size_t dataSize = data.size()[0]; + std::unordered_map, UnsignedInt, ArrayHash, ArrayEqual> table{dataSize}; + + /* Index array that'll be filled in each pass and then used for remapping + the `indices` */ + Containers::Array remapping{Containers::NoInit, dataSize}; + + /* First go with original coordinates, then move them by epsilon/2 in each + dimension. */ + T moveAmount = T(0.0); + Containers::Array discretized{Containers::NoInit, vectorSize}; + for(std::size_t moving = 0; moving <= vectorSize; ++moving) { + for(std::size_t i = 0; i != dataSize; ++i) { + /* Take the original vector and discretize it -- append the move + amount to given dimension, subtract the minmal offset and divide + by epsilon. */ + const Containers::StridedArrayView1D entry = data[i]; + for(std::size_t vi = 0; vi != vectorSize; ++vi) { + T c = entry[vi]; + /* In iteration `0` we're not moving in any dimension, in + iteration `vectorSize` we're moving in `vectorSize - 1` + dimension */ + if(vi + 1 == moving) c += moveAmount; + discretized[vi] = (c - offsets[vi])/epsilon; + } + + /* Try to insert new entry into the table. The inserted index + points into the new data array that has all duplicates removed. + This is a similar workflow to removeDuplicatesInPlaceInto() with + the only difference that we're remapping an existing index array + several times over instead of creating a new one */ + const auto result = table.emplace(Containers::arrayCast(discretized), 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]); + } + + /* Remap the resulting index array */ + for(auto& i: indices) i = remapping[i]; + + /* Move vertex coordinates by epsilon/2 in the next dimension (which + is moving + 1 in the next loop iteration) */ + moveAmount = epsilon/2; + + /* Next time go only through the unique prefix; clear the table for the + next pass */ + dataSize = table.size(); + table.clear(); + } + + CORRADE_INTERNAL_ASSERT(data.size()[0] >= dataSize); + return dataSize; +} + +} + +std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Float epsilon) { + return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon); +} + +std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Float epsilon) { + return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon); +} + +std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Float epsilon) { + return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon); +} + +std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Double epsilon) { + return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon); +} + +std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Double epsilon) { + return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon); +} + +std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, const Double epsilon) { + return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon); +} + +namespace { + +template std::size_t removeDuplicatesFuzzyInPlaceIntoImplementation(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices, const T epsilon) { + CORRADE_ASSERT(indices.size() == data.size()[0], + "MeshTools::removeDuplicatesFuzzyInPlaceInto(): output index array has" << indices.size() << "elements but expected" << data.size()[0], {}); + + /* A trivial index array that'll be remapped */ + std::iota(indices.begin(), indices.end(), 0); + const std::size_t size = removeDuplicatesFuzzyIndexedInPlaceImplementation(Containers::stridedArrayView(indices), data, epsilon); + return size; +} + +template std::pair, std::size_t> removeDuplicatesFuzzyInPlaceImplementation(const Containers::StridedArrayView2D& data, const T epsilon) { + Containers::Array indices{Containers::NoInit, data.size()[0]}; + const std::size_t size = removeDuplicatesFuzzyInPlaceIntoImplementation(data, indices, epsilon); + return {std::move(indices), size}; +} + +} + +std::pair, std::size_t> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D& data, const Float epsilon) { + return removeDuplicatesFuzzyInPlaceImplementation(data, epsilon); +} + +std::pair, std::size_t> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D& data, const Double epsilon) { + return removeDuplicatesFuzzyInPlaceImplementation(data, epsilon); +} + +std::size_t removeDuplicatesFuzzyInPlaceInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices, const Float epsilon) { + return removeDuplicatesFuzzyInPlaceIntoImplementation(data, indices, epsilon); +} + +std::size_t removeDuplicatesFuzzyInPlaceInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices, const Double epsilon) { + return removeDuplicatesFuzzyInPlaceIntoImplementation(data, indices, epsilon); +} + +namespace { + +template std::size_t removeDuplicatesFuzzyIndexedInPlaceImplementation(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView2D& data, const T epsilon) { + CORRADE_ASSERT(indices.isContiguous<1>(), "MeshTools::removeDuplicatesFuzzyIndexedInPlace(): second index view dimension is not contiguous", {}); + if(indices.size()[1] == 4) + return removeDuplicatesFuzzyIndexedInPlaceImplementation(Containers::arrayCast<1, UnsignedInt>(indices), data, epsilon); + else if(indices.size()[1] == 2) + return removeDuplicatesFuzzyIndexedInPlaceImplementation(Containers::arrayCast<1, UnsignedShort>(indices), data, epsilon); + else { + CORRADE_ASSERT(indices.size()[1] == 1, "MeshTools::removeDuplicatesFuzzyIndexedInPlace(): expected index type size 1, 2 or 4 but got" << indices.size()[1], {}); + return removeDuplicatesFuzzyIndexedInPlaceImplementation(Containers::arrayCast<1, UnsignedByte>(indices), data, epsilon); + } +} + +} + +std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView2D& data, const Float epsilon) { + return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon); +} + +std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView2D& data, const Double epsilon) { + return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon); +} + Trade::MeshData removeDuplicates(const Trade::MeshData& data) { return removeDuplicates(Trade::MeshData{data.primitive(), {}, data.indexData(), Trade::MeshIndexData{data.indices()}, diff --git a/src/Magnum/MeshTools/RemoveDuplicates.h b/src/Magnum/MeshTools/RemoveDuplicates.h index 8a7e6fffe..38fee302d 100644 --- a/src/Magnum/MeshTools/RemoveDuplicates.h +++ b/src/Magnum/MeshTools/RemoveDuplicates.h @@ -29,29 +29,20 @@ * @brief Function @ref Magnum::MeshTools::removeDuplicatesInPlace(), @ref Magnum::MeshTools::removeDuplicatesIndexedInPlace() */ -#include -#include -#include -#include -#include -#include -#include +#include #include "Magnum/Magnum.h" -#include "Magnum/Math/FunctionsBatch.h" +#include "Magnum/Math/TypeTraits.h" #include "Magnum/MeshTools/visibility.h" #include "Magnum/Trade/Trade.h" -namespace Magnum { namespace MeshTools { +#ifdef MAGNUM_BUILD_DEPRECATED +#include +#include +#include +#endif -namespace Implementation { - template class VectorHash { - public: - std::size_t operator()(const Math::Vector& data) const { - return *reinterpret_cast(Utility::MurmurHash2()(reinterpret_cast(&data), sizeof(data)).byteArray()); - } - }; -} +namespace Magnum { namespace MeshTools { /** @brief Remove duplicate data from given array in-place @@ -175,13 +166,20 @@ bit-exact matching is sufficient use @ref removeDuplicatesInPlace(const Containe instead. If you want to remove duplicate data from an already indexed array, use -@ref removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, typename Vector::Type) instead. +@ref removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView2D&, Float) +and friends instead. 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> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView1D& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()); +MAGNUM_MESHTOOLS_EXPORT std::pair, std::size_t> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D& data, Float epsilon = Math::TypeTraits::epsilon()); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT std::pair, std::size_t> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D& data, Double epsilon = Math::TypeTraits::epsilon()); /** @brief Remove duplicate data from given array using fuzzy comparison in-place into given output index array @@ -196,7 +194,13 @@ template std::pair, std::size_t> re 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. */ -template std::size_t removeDuplicatesFuzzyInPlaceInto(const Containers::StridedArrayView1D& data, const Containers::StridedArrayView1D& indices, typename Vector::Type epsilon = Math::TypeTraits::epsilon()); +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyInPlaceInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices, Float epsilon = Math::TypeTraits::epsilon()); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyInPlaceInto(const Containers::StridedArrayView2D& data, const Containers::StridedArrayView1D& indices, Double epsilon = Math::TypeTraits::epsilon()); #ifdef MAGNUM_BUILD_DEPRECATED /** @@ -231,74 +235,41 @@ template CORRADE_DEPRECATED("use removeDuplicatesInPlace() instead @return Size of unique prefix in the cleaned up @p data array @m_since_latest -Compared to @ref removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView1D&, typename Vector::Type) +Compared to @ref removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D&, Float) 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 removeDuplicatesFuzzyIndexedInPlace(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::removeDuplicatesFuzzyIndexedInPlace(): 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); - - /* 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()/static_cast(~std::size_t{}))); - - /* Table containing original vector index for each discretized vector. - Reserving more buckets than necessary (i.e. as if each vector was - unique). */ - 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` */ - 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) { - /* Go through all vectors */ - 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 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) - data[table.size() - 1] = data[i]; - } - - /* Remap the resulting index array */ - for(auto& i: indices) i = remapping[i]; - - /* Move vertex coordinates by epsilon/2 in the next direction. Do that - only if we're not in the last iteration, as that would be an OOB - access otherwise. */ - if(moving != Vector::Size) { - moved = Vector(); - moved[moving] = epsilon/2; - } - - /* Reduce to an unique prefix; clear the table for the next pass */ - dataSize = table.size(); - table.clear(); - } - - CORRADE_INTERNAL_ASSERT(data.size() >= dataSize); - return dataSize; -} +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, Float epsilon = Math::TypeTraits::epsilon()); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, Float epsilon = Math::TypeTraits::epsilon()); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, Float epsilon = Math::TypeTraits::epsilon()); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, Double epsilon = Math::TypeTraits::epsilon()); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, Double epsilon = Math::TypeTraits::epsilon()); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D& indices, const Containers::StridedArrayView2D& data, Double epsilon = Math::TypeTraits::epsilon()); /** @brief Remove duplicates from indexed data using fuzzy comparison in-place on a type-erased index array @@ -306,20 +277,16 @@ template std::size_t removeDuplicatesFuzzyIndexed 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 -@ref removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, typename Vector::Type) -with a concrete index type. +@ref removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D&, const Containers::StridedArrayView2D&, Float) +or the other overloads. */ -template std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView1D& data, typename Vector::Type epsilon = Math::TypeTraits::epsilon()) { - CORRADE_ASSERT(indices.isContiguous<1>(), "MeshTools::removeDuplicatesFuzzyIndexedInPlace(): second index view dimension is not contiguous", {}); - if(indices.size()[1] == 4) - return removeDuplicatesFuzzyIndexedInPlace(Containers::arrayCast<1, UnsignedInt>(indices), data, epsilon); - else if(indices.size()[1] == 2) - return removeDuplicatesFuzzyIndexedInPlace(Containers::arrayCast<1, UnsignedShort>(indices), data, epsilon); - else { - CORRADE_ASSERT(indices.size()[1] == 1, "MeshTools::removeDuplicatesFuzzyIndexedInPlace(): expected index type size 1, 2 or 4 but got" << indices.size()[1], {}); - return removeDuplicatesFuzzyIndexedInPlace(Containers::arrayCast<1, UnsignedByte>(indices), data, epsilon); - } -} +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView2D& data, Float epsilon = Math::TypeTraits::epsilon()); + +/** + * @overload + * @m_since_latest + */ +MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView2D& indices, const Containers::StridedArrayView2D& data, Double epsilon = Math::TypeTraits::epsilon()); /** @brief Remove mesh data duplicates @@ -351,27 +318,13 @@ data. */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData removeDuplicates(Trade::MeshData&& data); -template std::size_t removeDuplicatesFuzzyInPlaceInto(const Containers::StridedArrayView1D& data, const Containers::StridedArrayView1D& indices, typename Vector::Type epsilon) { - CORRADE_ASSERT(indices.size() == data.size(), - "MeshTools::removeDuplicatesFuzzyInPlaceInto(): output index array has" << indices.size() << "elements but expected" << data.size(), {}); - - /* A trivial index array that'll be remapped */ - std::iota(indices.begin(), indices.end(), 0); - const std::size_t size = removeDuplicatesFuzzyIndexedInPlace(Containers::stridedArrayView(indices), data, epsilon); - return size; -} - -template std::pair, std::size_t> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView1D& data, typename Vector::Type epsilon) { - Containers::Array indices{Containers::NoInit, data.size()}; - const std::size_t size = removeDuplicatesFuzzyInPlaceInto(data, indices, epsilon); - 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()); - const std::size_t size = removeDuplicatesFuzzyInPlaceInto(Containers::stridedArrayView(data), Containers::stridedArrayView(indices), epsilon); + const std::size_t size = removeDuplicatesFuzzyInPlaceInto( + Containers::arrayCast<2, typename Vector::Type>(Containers::stridedArrayView(data)), + Containers::stridedArrayView(indices), epsilon); data.resize(size); return indices; } diff --git a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp index e7aa6a92d..1c10682eb 100644 --- a/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp +++ b/src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include "Magnum/Math/Vector2.h" #include "Magnum/MeshTools/RemoveDuplicates.h" @@ -48,17 +49,18 @@ struct RemoveDuplicatesTest: TestSuite::Tester { void removeDuplicatesIndexedInPlaceErasedNonContiguous(); void removeDuplicatesIndexedInPlaceErasedWrongIndexSize(); - void removeDuplicatesFuzzyInPlaceOneDimension(); - void removeDuplicatesFuzzyInPlaceMoreDimensions(); + template void removeDuplicatesFuzzyInPlaceOneDimension(); + template void removeDuplicatesFuzzyInPlaceMoreDimensions(); + template void removeDuplicatesFuzzyInPlaceInto(); void removeDuplicatesFuzzyInPlaceIntoWrongOutputSize(); #ifdef MAGNUM_BUILD_DEPRECATED void removeDuplicatesFuzzyStl(); #endif - template void removeDuplicatesFuzzyIndexedInPlace(); + template void removeDuplicatesFuzzyIndexedInPlace(); void removeDuplicatesFuzzyIndexedInPlaceSmallType(); void removeDuplicatesFuzzyIndexedInPlaceEmptyIndices(); void removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices(); - template void removeDuplicatesFuzzyIndexedInPlaceErased(); + template void removeDuplicatesFuzzyIndexedInPlaceErased(); void removeDuplicatesFuzzyIndexedInPlaceErasedNonContiguous(); void removeDuplicatesFuzzyIndexedInPlaceErasedWrongIndexSize(); @@ -92,21 +94,31 @@ RemoveDuplicatesTest::RemoveDuplicatesTest() { &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceErasedNonContiguous, &RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceErasedWrongIndexSize, - &RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceOneDimension, - &RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceMoreDimensions, + &RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceOneDimension, + &RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceOneDimension, + &RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceMoreDimensions, + &RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceMoreDimensions, + &RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceInto, + &RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceInto, &RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceIntoWrongOutputSize, #ifdef MAGNUM_BUILD_DEPRECATED &RemoveDuplicatesTest::removeDuplicatesFuzzyStl, #endif - &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, - &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, - &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace, &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceSmallType, &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndices, &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices, - &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased, - &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased, - &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased, + &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased, &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErasedNonContiguous, &RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErasedWrongIndexSize}); @@ -271,43 +283,76 @@ void RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceErasedWrongIndexSize() "MeshTools::removeDuplicatesIndexedInPlace(): expected index type size 1, 2 or 4 but got 3\n"); } -void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceOneDimension() { +template void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceOneDimension() { + setTestCaseTemplateName(Math::TypeTraits::name()); + /* Numbers with distance <=1 should be merged. In the first iteration item 2 gets collapsed into item 1, in the second iteration item 3 gets collapsed into item 1, reducing to 2 items in total. */ - Math::Vector<1, Float> data[]{ - 1.0f, /* bucket 0 in 1st iteration, bucket 1 in 2nd */ - 2.9f, /* bucket 2 in 1st iteration, bucket 3 in 2nd */ - 0.0f, /* bucket 0 in 1st iteration, bucket 0 in 2nd */ - 3.4f /* bucket 3 in 1st iteration, bucket 3 in 2nd */ + T data[]{ + T(1.0), /* bucket 0 in 1st iteration, bucket 1 in 2nd */ + T(2.9), /* bucket 2 in 1st iteration, bucket 3 in 2nd */ + T(0.0), /* bucket 0 in 1st iteration, bucket 0 in 2nd */ + T(3.4) /* bucket 3 in 1st iteration, bucket 3 in 2nd */ }; - std::pair, std::size_t> result = MeshTools::removeDuplicatesFuzzyInPlace(Containers::stridedArrayView(data), 1.00001f); + std::pair, std::size_t> result = + MeshTools::removeDuplicatesFuzzyInPlace( + Containers::arrayCast<2, T>(Containers::stridedArrayView(data)), + T(1.00001)); CORRADE_COMPARE_AS(Containers::arrayView(result.first), Containers::arrayView({0, 1, 0, 1}), TestSuite::Compare::Container); CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(result.second), - (Containers::arrayView>({1.0f, 2.9f})), + (Containers::arrayView({T(1.0), T(2.9)})), TestSuite::Compare::Container); } -void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceMoreDimensions() { +template void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceMoreDimensions() { + setTestCaseTemplateName(Math::TypeTraits::name()); + /* 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. */ - Vector2 data[]{ - {1.0f, 0.0f}, - {2.0f, 1.0f}, - {0.0f, 4.0f}, - {1.0f, 5.0f} + Math::Vector2 data[]{ + {T(1.0), T(0.0)}, + {T(2.0), T(1.0)}, + {T(0.0), T(4.0)}, + {T(1.0), T(5.0)} }; - std::pair, std::size_t> result = MeshTools::removeDuplicatesFuzzyInPlace(Containers::stridedArrayView(data), 2.0f); + std::pair, std::size_t> result = + MeshTools::removeDuplicatesFuzzyInPlace( + Containers::arrayCast<2, T>(Containers::stridedArrayView(data)), + T(2.0)); 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.0f, 0.0f}, {0.0f, 4.0f}}), + Containers::arrayView>({{T(1.0), T(0.0)}, {T(0.0), T(4.0)}}), + TestSuite::Compare::Container); +} + +template void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceInto() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + /* Same as above, but using the Into variant */ + Math::Vector2 data[]{ + {T(1.0), T(0.0)}, + {T(2.0), T(1.0)}, + {T(0.0), T(4.0)}, + {T(1.0), T(5.0)} + }; + + Containers::Array indices{Containers::NoInit, Containers::arraySize(data)}; + std::size_t result = MeshTools::removeDuplicatesFuzzyInPlaceInto( + Containers::arrayCast<2, T>(Containers::stridedArrayView(data)), + indices, T(2.0)); + CORRADE_COMPARE_AS(indices, + Containers::arrayView({0, 0, 1, 1}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(result), + Containers::arrayView>({{T(1.0), T(0.0)}, {T(0.0), T(4.0)}}), TestSuite::Compare::Container); } @@ -321,7 +366,9 @@ void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceIntoWrongOutputSize() { std::ostringstream out; Error redirectError{&out}; - MeshTools::removeDuplicatesFuzzyInPlaceInto(Containers::stridedArrayView(data), output); + MeshTools::removeDuplicatesFuzzyInPlaceInto( + Containers::arrayCast<2, Float>(Containers::stridedArrayView(data)), + output); CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesFuzzyInPlaceInto(): output index array has 7 elements but expected 8\n"); } @@ -348,26 +395,28 @@ void RemoveDuplicatesTest::removeDuplicatesFuzzyStl() { } #endif -template void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace() { - setTestCaseTemplateName(Math::TypeTraits::name()); +template void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace() { + setTestCaseTemplateName(Utility::formatString("{}, {}", + Math::TypeTraits::name(), + Math::TypeTraits::name())); /* Same as above, but with an explicit index buffer */ - T indices[]{3, 2, 0, 1, 2, 3}; - Vector2 data[]{ - {1.0f, 0.0f}, - {2.0f, 1.0f}, - {0.0f, 4.0f}, - {1.0f, 5.0f} + IndexType indices[]{3, 2, 0, 1, 2, 3}; + Math::Vector2 data[]{ + {T(1.0), T(0.0)}, + {T(2.0), T(1.0)}, + {T(0.0), T(4.0)}, + {T(1.0), T(5.0)} }; std::size_t count = MeshTools::removeDuplicatesFuzzyIndexedInPlace( Containers::stridedArrayView(indices), - Containers::stridedArrayView(data), 2); + Containers::arrayCast<2, T>(Containers::stridedArrayView(data)), 2); CORRADE_COMPARE_AS(Containers::arrayView(indices), - Containers::arrayView({1, 1, 0, 0, 1, 1}), + Containers::arrayView({1, 1, 0, 0, 1, 1}), TestSuite::Compare::Container); CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count), - Containers::arrayView({{1.0f, 0.0f}, {0.0f, 4.0f}}), + Containers::arrayView>({{T(1.0), T(0.0)}, {T(0.0), T(4.0)}}), TestSuite::Compare::Container); } @@ -383,7 +432,7 @@ void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceSmallType() { Vector2 data[256]{}; MeshTools::removeDuplicatesFuzzyIndexedInPlace( Containers::stridedArrayView(indices), - Containers::stridedArrayView(data)); + Containers::arrayCast<2, Float>(Containers::stridedArrayView(data))); CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesFuzzyIndexedInPlace(): a 1-byte index type is too small for 256 vertices\n"); } @@ -397,36 +446,40 @@ void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndices() { std::size_t count = MeshTools::removeDuplicatesFuzzyIndexedInPlace( Containers::StridedArrayView1D{}, - Containers::stridedArrayView(data), 2.0f); + Containers::arrayCast<2, Float>(Containers::stridedArrayView(data)), 2.0f); CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count), Containers::arrayView({{1.0f, 0.0f}, {0.0f, 4.0f}}), TestSuite::Compare::Container); } void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices() { - CORRADE_COMPARE((MeshTools::removeDuplicatesFuzzyIndexedInPlace({}, {})), 0); + CORRADE_COMPARE((MeshTools::removeDuplicatesFuzzyIndexedInPlace( + Containers::StridedArrayView1D{}, + Containers::StridedArrayView2D{})), 0); } -template void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased() { - setTestCaseTemplateName(Math::TypeTraits::name()); +template void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased() { + setTestCaseTemplateName(Utility::formatString("{}, {}", + Math::TypeTraits::name(), + Math::TypeTraits::name())); /* Same as above, but with an explicit index buffer */ - T indices[]{3, 2, 0, 1, 2, 3}; - Vector2 data[]{ - {1.0f, 0.0f}, - {2.0f, 1.0f}, - {0.0f, 4.0f}, - {1.0f, 5.0f} + IndexType indices[]{3, 2, 0, 1, 2, 3}; + Math::Vector2 data[]{ + {T(1.0), T(0.0)}, + {T(2.0), T(1.0)}, + {T(0.0), T(4.0)}, + {T(1.0), T(5.0)} }; std::size_t count = MeshTools::removeDuplicatesFuzzyIndexedInPlace( Containers::arrayCast<2, char>(Containers::arrayView(indices)), - Containers::stridedArrayView(data), 2); + Containers::arrayCast<2, T>(Containers::stridedArrayView(data)), 2); CORRADE_COMPARE_AS(Containers::arrayView(indices), - Containers::arrayView({1, 1, 0, 0, 1, 1}), + Containers::arrayView({1, 1, 0, 0, 1, 1}), TestSuite::Compare::Container); CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count), - Containers::arrayView({{1.0f, 0.0f}, {0.0f, 4.0f}}), + Containers::arrayView>({{T(1.0), T(0.0)}, {T(0.0), T(4.0)}}), TestSuite::Compare::Container); } @@ -442,7 +495,7 @@ void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErasedNonContiguou Error redirectError{&out}; MeshTools::removeDuplicatesFuzzyIndexedInPlace( Containers::StridedArrayView2D{indices, {6, 2}, {4, 2}}, - Containers::stridedArrayView(data)); + Containers::arrayCast<2, Float>(Containers::stridedArrayView(data))); CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesFuzzyIndexedInPlace(): second index view dimension is not contiguous\n"); } @@ -459,7 +512,7 @@ void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErasedWrongIndexSi Error redirectError{&out}; MeshTools::removeDuplicatesFuzzyIndexedInPlace( Containers::StridedArrayView2D{indices, {6, 3}}, - Containers::stridedArrayView(data)); + Containers::arrayCast<2, Float>(Containers::stridedArrayView(data))); CORRADE_COMPARE(out.str(), "MeshTools::removeDuplicatesFuzzyIndexedInPlace(): expected index type size 1, 2 or 4 but got 3\n"); } diff --git a/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp b/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp index b476bcdb6..dec138860 100644 --- a/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp +++ b/src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp @@ -94,7 +94,8 @@ void SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesAfter() { /* Remove duplicates after */ arrayResize(positions, MeshTools::removeDuplicatesFuzzyIndexedInPlace( - stridedArrayView(indices), stridedArrayView(positions))); + stridedArrayView(indices), + Containers::arrayCast<2, Float>(stridedArrayView(positions)))); } } @@ -121,7 +122,8 @@ void SubdivideRemoveDuplicatesBenchmark::subdivideAndRemoveDuplicatesInBetween() for(std::size_t i = 0; i != 5; ++i) { MeshTools::subdivide(indices, positions, interpolator); arrayResize(positions, MeshTools::removeDuplicatesFuzzyIndexedInPlace( - stridedArrayView(indices), stridedArrayView(positions))); + stridedArrayView(indices), + Containers::arrayCast<2, Float>(stridedArrayView(positions)))); } } } diff --git a/src/Magnum/MeshTools/Test/SubdivideTest.cpp b/src/Magnum/MeshTools/Test/SubdivideTest.cpp index 3f878899b..826ca9dc1 100644 --- a/src/Magnum/MeshTools/Test/SubdivideTest.cpp +++ b/src/Magnum/MeshTools/Test/SubdivideTest.cpp @@ -28,6 +28,7 @@ #include #include +#include "Magnum/Math/Vector.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Subdivide.h" diff --git a/src/Magnum/Primitives/CMakeLists.txt b/src/Magnum/Primitives/CMakeLists.txt index c69646c97..89d08bdbb 100644 --- a/src/Magnum/Primitives/CMakeLists.txt +++ b/src/Magnum/Primitives/CMakeLists.txt @@ -80,6 +80,7 @@ elseif(BUILD_STATIC_PIC) endif() target_link_libraries(MagnumPrimitives PUBLIC Magnum + MagnumMeshTools MagnumTrade) install(TARGETS MagnumPrimitives diff --git a/src/Magnum/Primitives/Icosphere.cpp b/src/Magnum/Primitives/Icosphere.cpp index b09a9871b..cab2ec9a3 100644 --- a/src/Magnum/Primitives/Icosphere.cpp +++ b/src/Magnum/Primitives/Icosphere.cpp @@ -116,7 +116,9 @@ Trade::MeshData icosphereSolid(const UnsignedInt subdivisions) { /** @todo i need arrayShrinkAndGiveUpMemoryIfItDoesntCauseRealloc() */ Containers::arrayResize(vertexData, - MeshTools::removeDuplicatesFuzzyIndexedInPlace(Containers::stridedArrayView(indices), Containers::stridedArrayView(positions))*sizeof(Vertex)); + MeshTools::removeDuplicatesFuzzyIndexedInPlace( + Containers::stridedArrayView(indices), + Containers::arrayCast<2, Float>(positions))*sizeof(Vertex)); } /* Build up the views again with correct size, fill the normals */