Browse Source

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.
pull/449/head
Vladimír Vondruš 6 years ago
parent
commit
d846944b7a
  1. 1
      doc/custom-buildsystems-order.dot
  2. 3
      doc/snippets/MagnumMeshTools.cpp
  3. 2
      modules/FindMagnum.cmake
  4. 1
      src/Magnum/MeshTools/Combine.cpp
  5. 190
      src/Magnum/MeshTools/RemoveDuplicates.cpp
  6. 183
      src/Magnum/MeshTools/RemoveDuplicates.h
  7. 165
      src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp
  8. 6
      src/Magnum/MeshTools/Test/SubdivideRemoveDuplicatesBenchmark.cpp
  9. 1
      src/Magnum/MeshTools/Test/SubdivideTest.cpp
  10. 1
      src/Magnum/Primitives/CMakeLists.txt
  11. 4
      src/Magnum/Primitives/Icosphere.cpp

1
doc/custom-buildsystems-order.dot

@ -79,6 +79,7 @@ digraph "Magnum library dependency order" {
MagnumOpenGLTester -> MagnumWindowlessApplication
MagnumPrimitives -> MagnumMeshTools
MagnumPrimitives -> MagnumTrade
MagnumSceneGraph -> Magnum

3
doc/snippets/MagnumMeshTools.cpp

@ -23,6 +23,9 @@
DEALINGS IN THE SOFTWARE.
*/
#include <tuple>
#include <vector>
#include "Magnum/Math/Color.h"
#include "Magnum/Math/FunctionsBatch.h"
#include "Magnum/MeshTools/CompressIndices.h"

2
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)

1
src/Magnum/MeshTools/Combine.cpp

@ -30,6 +30,7 @@
#include <Corrade/Containers/Reference.h>
#include <Corrade/Utility/Algorithms.h>
#include "Magnum/Math/Functions.h"
#include "Magnum/MeshTools/Interleave.h"
#include "Magnum/MeshTools/Duplicate.h"
#include "Magnum/MeshTools/RemoveDuplicates.h"

190
src/Magnum/MeshTools/RemoveDuplicates.cpp

@ -26,9 +26,16 @@
#include "RemoveDuplicates.h"
#include <cstring>
#include <limits>
#include <numeric>
#include <unordered_map>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/StridedArrayView.h>
#include <Corrade/Utility/Algorithms.h>
#include <Corrade/Utility/MurmurHash2.h>
#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<class IndexType, class T> std::size_t removeDuplicatesFuzzyIndexedInPlaceImplementation(const Containers::StridedArrayView1D<IndexType>& indices, const Containers::StridedArrayView2D<T>& 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<T> offsets{Containers::NoInit, vectorSize};
{
/** @todo this isn't really cache-efficient, do differently */
std::size_t i = 0;
for(Containers::StridedArrayView1D<T> dimension: data.template transposed<0, 1>()) {
const Math::Range1D<T> 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<Containers::ArrayView<const char>, UnsignedInt, ArrayHash, ArrayEqual> table{dataSize};
/* Index array that'll be filled in each pass and then used for remapping
the `indices` */
Containers::Array<UnsignedInt> 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<std::size_t> 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<T> 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<const char>(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<UnsignedInt>& indices, const Containers::StridedArrayView2D<Float>& data, const Float epsilon) {
return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon);
}
std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<UnsignedShort>& indices, const Containers::StridedArrayView2D<Float>& data, const Float epsilon) {
return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon);
}
std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<UnsignedByte>& indices, const Containers::StridedArrayView2D<Float>& data, const Float epsilon) {
return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon);
}
std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<UnsignedInt>& indices, const Containers::StridedArrayView2D<Double>& data, const Double epsilon) {
return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon);
}
std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<UnsignedShort>& indices, const Containers::StridedArrayView2D<Double>& data, const Double epsilon) {
return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon);
}
std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<UnsignedByte>& indices, const Containers::StridedArrayView2D<Double>& data, const Double epsilon) {
return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon);
}
namespace {
template<class T> std::size_t removeDuplicatesFuzzyInPlaceIntoImplementation(const Containers::StridedArrayView2D<T>& data, const Containers::StridedArrayView1D<UnsignedInt>& 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<class T> std::pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicatesFuzzyInPlaceImplementation(const Containers::StridedArrayView2D<T>& data, const T epsilon) {
Containers::Array<UnsignedInt> indices{Containers::NoInit, data.size()[0]};
const std::size_t size = removeDuplicatesFuzzyInPlaceIntoImplementation(data, indices, epsilon);
return {std::move(indices), size};
}
}
std::pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D<Float>& data, const Float epsilon) {
return removeDuplicatesFuzzyInPlaceImplementation(data, epsilon);
}
std::pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D<Double>& data, const Double epsilon) {
return removeDuplicatesFuzzyInPlaceImplementation(data, epsilon);
}
std::size_t removeDuplicatesFuzzyInPlaceInto(const Containers::StridedArrayView2D<Float>& data, const Containers::StridedArrayView1D<UnsignedInt>& indices, const Float epsilon) {
return removeDuplicatesFuzzyInPlaceIntoImplementation(data, indices, epsilon);
}
std::size_t removeDuplicatesFuzzyInPlaceInto(const Containers::StridedArrayView2D<Double>& data, const Containers::StridedArrayView1D<UnsignedInt>& indices, const Double epsilon) {
return removeDuplicatesFuzzyInPlaceIntoImplementation(data, indices, epsilon);
}
namespace {
template<class T> std::size_t removeDuplicatesFuzzyIndexedInPlaceImplementation(const Containers::StridedArrayView2D<char>& indices, const Containers::StridedArrayView2D<T>& 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<char>& indices, const Containers::StridedArrayView2D<Float>& data, const Float epsilon) {
return removeDuplicatesFuzzyIndexedInPlaceImplementation(indices, data, epsilon);
}
std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView2D<char>& indices, const Containers::StridedArrayView2D<Double>& 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()},

183
src/Magnum/MeshTools/RemoveDuplicates.h

@ -29,29 +29,20 @@
* @brief Function @ref Magnum::MeshTools::removeDuplicatesInPlace(), @ref Magnum::MeshTools::removeDuplicatesIndexedInPlace()
*/
#include <limits>
#include <numeric>
#include <unordered_map>
#include <vector>
#include <Corrade/Containers/Array.h>
#include <Corrade/Containers/ArrayViewStl.h>
#include <Corrade/Utility/MurmurHash2.h>
#include <utility>
#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 <vector>
#include <Corrade/Containers/ArrayViewStl.h>
#include <Corrade/Containers/StridedArrayView.h>
#endif
namespace Implementation {
template<std::size_t size> class VectorHash {
public:
std::size_t operator()(const Math::Vector<size, std::size_t>& data) const {
return *reinterpret_cast<const std::size_t*>(Utility::MurmurHash2()(reinterpret_cast<const char*>(&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<IndexType>&, const Containers::StridedArrayView1D<Vector>&, typename Vector::Type) instead.
@ref removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<UnsignedInt>&, const Containers::StridedArrayView2D<Float>&, 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<class Vector> std::pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView1D<Vector>& data, typename Vector::Type epsilon = Math::TypeTraits<typename Vector::Type>::epsilon());
MAGNUM_MESHTOOLS_EXPORT std::pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D<Float>& data, Float epsilon = Math::TypeTraits<Float>::epsilon());
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT std::pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D<Double>& data, Double epsilon = Math::TypeTraits<Double>::epsilon());
/**
@brief Remove duplicate data from given array using fuzzy comparison in-place into given output index array
@ -196,7 +194,13 @@ template<class Vector> std::pair<Containers::Array<UnsignedInt>, 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<class Vector> std::size_t removeDuplicatesFuzzyInPlaceInto(const Containers::StridedArrayView1D<Vector>& data, const Containers::StridedArrayView1D<UnsignedInt>& indices, typename Vector::Type epsilon = Math::TypeTraits<typename Vector::Type>::epsilon());
MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyInPlaceInto(const Containers::StridedArrayView2D<Float>& data, const Containers::StridedArrayView1D<UnsignedInt>& indices, Float epsilon = Math::TypeTraits<Float>::epsilon());
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyInPlaceInto(const Containers::StridedArrayView2D<Double>& data, const Containers::StridedArrayView1D<UnsignedInt>& indices, Double epsilon = Math::TypeTraits<Double>::epsilon());
#ifdef MAGNUM_BUILD_DEPRECATED
/**
@ -231,74 +235,41 @@ template<class Vector> 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<Vector>&, typename Vector::Type)
Compared to @ref removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView2D<Float>&, 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<class IndexType, class Vector> std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<IndexType>& indices, const Containers::StridedArrayView1D<Vector>& data, typename Vector::Type epsilon = Math::TypeTraits<typename Vector::Type>::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<Vector, Vector> 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<typename Vector::Type>(~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<Math::Vector<Vector::Size, std::size_t>, UnsignedInt, Implementation::VectorHash<Vector::Size>> table(dataSize);
/* Index array that'll be filled in each pass and then used for remapping
the `indices` */
Containers::Array<UnsignedInt> 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<Vector::Size, std::size_t> 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<UnsignedInt>& indices, const Containers::StridedArrayView2D<Float>& data, Float epsilon = Math::TypeTraits<Float>::epsilon());
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<UnsignedShort>& indices, const Containers::StridedArrayView2D<Float>& data, Float epsilon = Math::TypeTraits<Float>::epsilon());
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<UnsignedByte>& indices, const Containers::StridedArrayView2D<Float>& data, Float epsilon = Math::TypeTraits<Float>::epsilon());
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<UnsignedInt>& indices, const Containers::StridedArrayView2D<Double>& data, Double epsilon = Math::TypeTraits<Double>::epsilon());
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<UnsignedShort>& indices, const Containers::StridedArrayView2D<Double>& data, Double epsilon = Math::TypeTraits<Double>::epsilon());
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<UnsignedByte>& indices, const Containers::StridedArrayView2D<Double>& data, Double epsilon = Math::TypeTraits<Double>::epsilon());
/**
@brief Remove duplicates from indexed data using fuzzy comparison in-place on a type-erased index array
@ -306,20 +277,16 @@ template<class IndexType, class Vector> 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<IndexType>&, const Containers::StridedArrayView1D<Vector>&, typename Vector::Type)
with a concrete index type.
@ref removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView1D<UnsignedInt>&, const Containers::StridedArrayView2D<Float>&, Float)
or the other overloads.
*/
template<class Vector> std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView2D<char>& indices, const Containers::StridedArrayView1D<Vector>& data, typename Vector::Type epsilon = Math::TypeTraits<typename Vector::Type>::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<char>& indices, const Containers::StridedArrayView2D<Float>& data, Float epsilon = Math::TypeTraits<Float>::epsilon());
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT std::size_t removeDuplicatesFuzzyIndexedInPlace(const Containers::StridedArrayView2D<char>& indices, const Containers::StridedArrayView2D<Double>& data, Double epsilon = Math::TypeTraits<Double>::epsilon());
/**
@brief Remove mesh data duplicates
@ -351,27 +318,13 @@ data.
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData removeDuplicates(Trade::MeshData&& data);
template<class Vector> std::size_t removeDuplicatesFuzzyInPlaceInto(const Containers::StridedArrayView1D<Vector>& data, const Containers::StridedArrayView1D<UnsignedInt>& 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<class Vector> std::pair<Containers::Array<UnsignedInt>, std::size_t> removeDuplicatesFuzzyInPlace(const Containers::StridedArrayView1D<Vector>& data, typename Vector::Type epsilon) {
Containers::Array<UnsignedInt> indices{Containers::NoInit, data.size()};
const std::size_t size = removeDuplicatesFuzzyInPlaceInto(data, indices, epsilon);
return {std::move(indices), size};
}
#ifdef MAGNUM_BUILD_DEPRECATED
template<class Vector> std::vector<UnsignedInt> removeDuplicates(std::vector<Vector>& data, typename Vector::Type epsilon) {
/* A trivial index array that'll be remapped and returned after */
std::vector<UnsignedInt> 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;
}

165
src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp

@ -27,6 +27,7 @@
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/DebugStl.h>
#include <Corrade/Utility/FormatStl.h>
#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<class T> void removeDuplicatesFuzzyInPlaceOneDimension();
template<class T> void removeDuplicatesFuzzyInPlaceMoreDimensions();
template<class T> void removeDuplicatesFuzzyInPlaceInto();
void removeDuplicatesFuzzyInPlaceIntoWrongOutputSize();
#ifdef MAGNUM_BUILD_DEPRECATED
void removeDuplicatesFuzzyStl();
#endif
template<class T> void removeDuplicatesFuzzyIndexedInPlace();
template<class IndexType, class T> void removeDuplicatesFuzzyIndexedInPlace();
void removeDuplicatesFuzzyIndexedInPlaceSmallType();
void removeDuplicatesFuzzyIndexedInPlaceEmptyIndices();
void removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices();
template<class T> void removeDuplicatesFuzzyIndexedInPlaceErased();
template<class IndexType, class T> void removeDuplicatesFuzzyIndexedInPlaceErased();
void removeDuplicatesFuzzyIndexedInPlaceErasedNonContiguous();
void removeDuplicatesFuzzyIndexedInPlaceErasedWrongIndexSize();
@ -92,21 +94,31 @@ RemoveDuplicatesTest::RemoveDuplicatesTest() {
&RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceErasedNonContiguous,
&RemoveDuplicatesTest::removeDuplicatesIndexedInPlaceErasedWrongIndexSize,
&RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceOneDimension,
&RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceMoreDimensions,
&RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceOneDimension<Float>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceOneDimension<Double>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceMoreDimensions<Float>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceMoreDimensions<Double>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceInto<Float>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceInto<Double>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceIntoWrongOutputSize,
#ifdef MAGNUM_BUILD_DEPRECATED
&RemoveDuplicatesTest::removeDuplicatesFuzzyStl,
#endif
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace<UnsignedByte>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace<UnsignedShort>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace<UnsignedInt>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace<UnsignedByte, Float>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace<UnsignedByte, Double>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace<UnsignedShort, Float>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace<UnsignedShort, Double>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace<UnsignedInt, Float>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace<UnsignedInt, Double>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceSmallType,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndices,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased<UnsignedByte>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased<UnsignedShort>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased<UnsignedInt>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased<UnsignedByte, Float>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased<UnsignedByte, Double>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased<UnsignedShort, Float>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased<UnsignedShort, Double>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased<UnsignedInt, Float>,
&RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased<UnsignedInt, Double>,
&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<class T> void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceOneDimension() {
setTestCaseTemplateName(Math::TypeTraits<T>::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<Containers::Array<UnsignedInt>, std::size_t> result = MeshTools::removeDuplicatesFuzzyInPlace(Containers::stridedArrayView(data), 1.00001f);
std::pair<Containers::Array<UnsignedInt>, 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<UnsignedInt>({0, 1, 0, 1}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(result.second),
(Containers::arrayView<Math::Vector<1, Float>>({1.0f, 2.9f})),
(Containers::arrayView<T>({T(1.0), T(2.9)})),
TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceMoreDimensions() {
template<class T> void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceMoreDimensions() {
setTestCaseTemplateName(Math::TypeTraits<T>::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<T> 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<Containers::Array<UnsignedInt>, std::size_t> result = MeshTools::removeDuplicatesFuzzyInPlace(Containers::stridedArrayView(data), 2.0f);
std::pair<Containers::Array<UnsignedInt>, 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<UnsignedInt>({0, 0, 1, 1}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(result.second),
Containers::arrayView<Vector2>({{1.0f, 0.0f}, {0.0f, 4.0f}}),
Containers::arrayView<Math::Vector2<T>>({{T(1.0), T(0.0)}, {T(0.0), T(4.0)}}),
TestSuite::Compare::Container);
}
template<class T> void RemoveDuplicatesTest::removeDuplicatesFuzzyInPlaceInto() {
setTestCaseTemplateName(Math::TypeTraits<T>::name());
/* Same as above, but using the Into variant */
Math::Vector2<T> 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<UnsignedInt> 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<UnsignedInt>({0, 0, 1, 1}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(result),
Containers::arrayView<Math::Vector2<T>>({{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<class T> void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace() {
setTestCaseTemplateName(Math::TypeTraits<T>::name());
template<class IndexType, class T> void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlace() {
setTestCaseTemplateName(Utility::formatString("{}, {}",
Math::TypeTraits<IndexType>::name(),
Math::TypeTraits<T>::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<T> 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<T>({1, 1, 0, 0, 1, 1}),
Containers::arrayView<IndexType>({1, 1, 0, 0, 1, 1}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count),
Containers::arrayView<Vector2>({{1.0f, 0.0f}, {0.0f, 4.0f}}),
Containers::arrayView<Math::Vector2<T>>({{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<UnsignedInt>{},
Containers::stridedArrayView(data), 2.0f);
Containers::arrayCast<2, Float>(Containers::stridedArrayView(data)), 2.0f);
CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count),
Containers::arrayView<Vector2>({{1.0f, 0.0f}, {0.0f, 4.0f}}),
TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceEmptyIndicesVertices() {
CORRADE_COMPARE((MeshTools::removeDuplicatesFuzzyIndexedInPlace<UnsignedInt, Vector2>({}, {})), 0);
CORRADE_COMPARE((MeshTools::removeDuplicatesFuzzyIndexedInPlace(
Containers::StridedArrayView1D<UnsignedInt>{},
Containers::StridedArrayView2D<Float>{})), 0);
}
template<class T> void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased() {
setTestCaseTemplateName(Math::TypeTraits<T>::name());
template<class IndexType, class T> void RemoveDuplicatesTest::removeDuplicatesFuzzyIndexedInPlaceErased() {
setTestCaseTemplateName(Utility::formatString("{}, {}",
Math::TypeTraits<IndexType>::name(),
Math::TypeTraits<T>::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<T> 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<T>({1, 1, 0, 0, 1, 1}),
Containers::arrayView<IndexType>({1, 1, 0, 0, 1, 1}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(Containers::arrayView(data).prefix(count),
Containers::arrayView<Vector2>({{1.0f, 0.0f}, {0.0f, 4.0f}}),
Containers::arrayView<Math::Vector2<T>>({{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<char>{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<char>{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");
}

6
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))));
}
}
}

1
src/Magnum/MeshTools/Test/SubdivideTest.cpp

@ -28,6 +28,7 @@
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/DebugStl.h>
#include "Magnum/Math/Vector.h"
#include "Magnum/MeshTools/RemoveDuplicates.h"
#include "Magnum/MeshTools/Subdivide.h"

1
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

4
src/Magnum/Primitives/Icosphere.cpp

@ -116,7 +116,9 @@ Trade::MeshData icosphereSolid(const UnsignedInt subdivisions) {
/** @todo i need arrayShrinkAndGiveUpMemoryIfItDoesntCauseRealloc() */
Containers::arrayResize<Trade::ArrayAllocator>(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 */

Loading…
Cancel
Save