Browse Source

MeshTools: reworked removeDuplicates().

It now returns new index array instead of operating on already existing
one and also custom vector size is removed. The internal implementation
is now much simpler and cleaner. The old way (but still without custom
vector size) is now alias to the new implementation, is marked as
deprecated and will be removed in future release.

Renamed the parameters and reworded the documentation so it doesn't talk
about vertices, but rather about generic floating-point vector data.
pull/51/head
Vladimír Vondruš 12 years ago
parent
commit
cc1142cc17
  1. 3
      src/Magnum/MeshTools/GenerateFlatNormals.cpp
  2. 209
      src/Magnum/MeshTools/RemoveDuplicates.h
  3. 2
      src/Magnum/MeshTools/Test/CMakeLists.txt
  4. 30
      src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp
  5. 5
      src/Magnum/MeshTools/Test/SubdivideTest.cpp
  6. 5
      src/Magnum/Primitives/Icosphere.cpp

3
src/Magnum/MeshTools/GenerateFlatNormals.cpp

@ -26,6 +26,7 @@
#include "GenerateFlatNormals.h" #include "GenerateFlatNormals.h"
#include "Magnum/Math/Vector3.h" #include "Magnum/Math/Vector3.h"
#include "Magnum/MeshTools/Duplicate.h"
#include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/RemoveDuplicates.h"
namespace Magnum { namespace MeshTools { namespace Magnum { namespace MeshTools {
@ -50,7 +51,7 @@ std::tuple<std::vector<UnsignedInt>, std::vector<Vector3>> generateFlatNormals(c
} }
/* Remove duplicate normals and return */ /* Remove duplicate normals and return */
MeshTools::removeDuplicates(normalIndices, normals); normalIndices = MeshTools::duplicate(normalIndices, MeshTools::removeDuplicates(normals));
return std::make_tuple(std::move(normalIndices), std::move(normals)); return std::make_tuple(std::move(normalIndices), std::move(normals));
} }

209
src/Magnum/MeshTools/RemoveDuplicates.h

@ -26,10 +26,11 @@
*/ */
/** @file /** @file
* @brief Function Magnum::MeshTools::removeDuplicates() * @brief Function @ref Magnum::MeshTools::removeDuplicates()
*/ */
#include <limits> #include <limits>
#include <numeric>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <Corrade/Utility/MurmurHash2.h> #include <Corrade/Utility/MurmurHash2.h>
@ -37,120 +38,142 @@
#include "Magnum/Magnum.h" #include "Magnum/Magnum.h"
#include "Magnum/Math/Functions.h" #include "Magnum/Math/Functions.h"
namespace Magnum { namespace MeshTools { #ifdef MAGNUM_BUILD_DEPRECATED
#include <tuple>
namespace Implementation {
template<class Vertex, std::size_t vertexSize = Vertex::Size> class RemoveDuplicates {
public:
RemoveDuplicates(std::vector<UnsignedInt>& indices, std::vector<Vertex>& vertices): indices(indices), vertices(vertices) {}
void operator()(typename Vertex::Type epsilon = Math::TypeTraits<typename Vertex::Type>::epsilon());
private:
class IndexHash {
public:
std::size_t operator()(const Math::Vector<vertexSize, std::size_t>& data) const {
return *reinterpret_cast<const std::size_t*>(Utility::MurmurHash2()(reinterpret_cast<const char*>(&data), sizeof(data)).byteArray());
}
};
struct HashedVertex { #include "Magnum/MeshTools/Duplicate.h"
UnsignedInt oldIndex, newIndex; #endif
HashedVertex(UnsignedInt oldIndex, UnsignedInt newIndex): oldIndex(oldIndex), newIndex(newIndex) {}
};
std::vector<UnsignedInt>& indices; namespace Magnum { namespace MeshTools {
std::vector<Vertex>& vertices;
};
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());
}
};
} }
/** /**
@brief %Remove duplicate vertices from the mesh @brief %Remove duplicate floating-point vector data from given array
@tparam Vertex Vertex data type @param[in,out] data Input data array
@tparam vertexSize How many initial vertex fields are important (for @param[out] epsilon Epsilon value, vertices nearer than this distance will be
example, when dealing with perspective in 3D space, only first three melt together
fields of otherwise 4D vertex are important) @return Index array and unique data
@param[in,out] indices Index array to operate on
@param[in,out] vertices Vertex array to operate on Removes duplicate data from the array by collapsing them into buckets of size
@param[in] epsilon Epsilon value, vertices nearer than this distance will @p epsilon. First vector in given bucket is used, other ones are thrown away,
be melt together. 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
Removes duplicate vertices from the mesh. the usual sorting method is much more efficient.
@see duplicate()
If you want to remove duplicate data from already indexed array, first remove
@todo Different (no cycle) implementation for integral vertices duplicates as if the array wasn't indexed at all and then use @ref duplicate()
@todo Interpolate vertices, not collapse them to first in the cell to combine the two index arrays:
@todo Ability to specify other attributes for interpolation @code
std::vector<UnsignedInt> indices;
std::vector<Vector3> positions;
indices = MeshTools::duplicate(indices, MeshTools::removeDuplicates(positions));
@endcode
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:
@code
std::vector<Vector3> positions;
std::vector<Vector2> texCoords;
std::vector<UnsignedInt> positionIndices;
std::tie(positionIndices, positions) = MeshTools::removeDuplicates(positions);
std::vector<UnsignedInt> texCoordIndices;
std::tie(texCoordIndices, texCoords) = MeshTools::removeDuplicates(texCoords);
std::vector<UnsignedInt> indices = MeshTools::combineIndexedArrays(
std::make_pair(std::cref(positionIndices), std::ref(positions)),
std::make_pair(std::cref(texCoordIndices), std::ref(texCoords))
);
@endcode
*/ */
template<class Vertex, std::size_t vertexSize = Vertex::Size> inline void removeDuplicates(std::vector<UnsignedInt>& indices, std::vector<Vertex>& vertices, typename Vertex::Type epsilon = Math::TypeTraits<typename Vertex::Type>::epsilon()) { template<class Vector> std::vector<UnsignedInt> removeDuplicates(std::vector<Vector>& data, typename Vector::Type epsilon = Math::TypeTraits<typename Vector::Type>::epsilon()) {
Implementation::RemoveDuplicates<Vertex, vertexSize>(indices, vertices)(epsilon); /* Get bounds */
} Vector min = data[0], max = data[0];
for(const auto& v: data) {
namespace Implementation {
template<class Vertex, std::size_t vertexSize> void RemoveDuplicates<Vertex, vertexSize>::operator()(typename Vertex::Type epsilon) {
if(indices.empty()) return;
/* Get mesh bounds */
Vertex min = vertices[0], max = vertices[0];
for(const auto& v: vertices) {
min = Math::min(v, min); min = Math::min(v, min);
max = Math::max(v, max); max = Math::max(v, max);
} }
/* Make epsilon so large that std::size_t can index all vertices inside /* Make epsilon so large that std::size_t can index all vectors inside the
mesh bounds. */ bounds. */
epsilon = Math::max(epsilon, static_cast<typename Vertex::Type>((max-min).max()/std::numeric_limits<std::size_t>::max())); epsilon = Math::max(epsilon, typename Vector::Type((max-min).max()/std::numeric_limits<std::size_t>::max()));
/* First go with original vertex coordinates, then move them by /* Resulting index array */
epsilon/2 in each direction. */ std::vector<UnsignedInt> resultIndices(data.size());
Vertex moved; std::iota(resultIndices.begin(), resultIndices.end(), 0);
for(std::size_t moving = 0; moving <= vertexSize; ++moving) {
/* Table containing original vector index for each discretized vector.
/* Under each index is pointer to face which contains given vertex Reserving more buckets than necessary (i.e. as if each vector was
and index of vertex in the face. */ unique). */
std::unordered_map<Math::Vector<vertexSize, std::size_t>, HashedVertex, IndexHash> table; std::unordered_map<Math::Vector<Vector::Size, std::size_t>, UnsignedInt, Implementation::VectorHash<Vector::Size>> table(data.size());
/* Reserve space for all vertices */ /* Index array for each pass, new data array */
table.reserve(vertices.size()); std::vector<UnsignedInt> indices;
indices.reserve(data.size());
/* Go through all faces' vertices */
for(auto it = indices.begin(); it != indices.end(); ++it) { /* First go with original coordinates, then move them by epsilon/2 in each
/* Index of a vertex in vertexSize-dimensional table */ direction. */
std::size_t index[vertexSize]; Vector moved;
for(std::size_t ii = 0; ii != vertexSize; ++ii) for(std::size_t moving = 0; moving <= Vector::Size; ++moving) {
index[ii] = std::size_t((vertices[*it][ii]+moved[ii]-min[ii])/epsilon); /* Go through all vectors */
for(std::size_t i = 0; i != data.size(); ++i) {
/* Try inserting the vertex into table, if it already /* Try to insert new vertex to the table */
exists, change vertex pointer of the face to already const Math::Vector<Vector::Size, std::size_t> v((data[i] + moved - min)/epsilon);
existing vertex */
HashedVertex v(*it, table.size());
#ifndef CORRADE_GCC46_COMPATIBILITY #ifndef CORRADE_GCC46_COMPATIBILITY
auto result = table.emplace(Math::Vector<vertexSize, std::size_t>::from(index), v); const auto result = table.emplace(v, table.size());
#else #else
auto result = table.insert({Math::Vector<vertexSize, std::size_t>::from(index), v}); const auto result = table.insert({v, table.size()});
#endif #endif
*it = result.first->second.newIndex;
/* Add the (either new or already existing) index to index array */
indices.push_back(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];
} }
/* Shrink vertices array */ /* Shrink the data array */
std::vector<Vertex> newVertices(table.size()); CORRADE_INTERNAL_ASSERT(data.size() >= table.size());
for(auto it = table.cbegin(); it != table.cend(); ++it) data.resize(table.size());
newVertices[it->second.newIndex] = vertices[it->second.oldIndex];
std::swap(newVertices, vertices); /* Remap the resulting index array */
for(auto& i: resultIndices) i = indices[i];
/* Finished */
if(moving == Vector::Size) continue;
/* Move vertex coordinates by epsilon/2 in next direction */ /* Move vertex coordinates by epsilon/2 in next direction */
if(moving != Vertex::Size) { moved = Vector();
moved = Vertex(); moved[moving] = epsilon/2;
moved[moving] = epsilon/2;
} /* Clear the structures for next pass */
table.clear();
indices.clear();
} }
return resultIndices;
} }
#ifdef MAGNUM_BUILD_DEPRECATED
template<class Vector> void removeDuplicates(std::vector<UnsignedInt>& indices, std::vector<Vector>& data, typename Vector::Type epsilon = Math::TypeTraits<typename Vector::Type>::epsilon()) {
std::vector<UnsignedInt> uniqueIndices;
std::tie(uniqueIndices, data) = removeDuplicates(data);
indices = MeshTools::duplicate(indices, uniqueIndices);
} }
#endif
}} }}

2
src/Magnum/MeshTools/Test/CMakeLists.txt

@ -29,7 +29,7 @@ corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp)
corrade_add_test(MeshToolsFlipNormalsTest FlipNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsFlipNormalsTest FlipNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsGenerateFlatNormalsTest GenerateFlatNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib) corrade_add_test(MeshToolsGenerateFlatNormalsTest GenerateFlatNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsInterleaveTest InterleaveTest.cpp) corrade_add_test(MeshToolsInterleaveTest InterleaveTest.cpp)
corrade_add_test(MeshToolsRemoveDuplicatesTest RemoveDuplicatesTest.cpp) corrade_add_test(MeshToolsRemoveDuplicatesTest RemoveDuplicatesTest.cpp LIBRARIES Magnum)
corrade_add_test(MeshToolsSubdivideTest SubdivideTest.cpp) corrade_add_test(MeshToolsSubdivideTest SubdivideTest.cpp)
# corrade_add_test(MeshToolsSubdivideRemoveDuplicatesBenchmark SubdivideRemoveDuplicatesBenchmark.h SubdivideRemoveDuplicatesBenchmark.cpp MagnumPrimitives) # corrade_add_test(MeshToolsSubdivideRemoveDuplicatesBenchmark SubdivideRemoveDuplicatesBenchmark.h SubdivideRemoveDuplicatesBenchmark.cpp MagnumPrimitives)
corrade_add_test(MeshToolsTipsifyTest TipsifyTest.cpp LIBRARIES MagnumMeshTools) corrade_add_test(MeshToolsTipsifyTest TipsifyTest.cpp LIBRARIES MagnumMeshTools)

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

@ -25,6 +25,7 @@
#include <Corrade/TestSuite/Tester.h> #include <Corrade/TestSuite/Tester.h>
#include "Magnum/Math/Vector2.h"
#include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/RemoveDuplicates.h"
namespace Magnum { namespace MeshTools { namespace Test { namespace Magnum { namespace MeshTools { namespace Test {
@ -33,23 +34,30 @@ class RemoveDuplicatesTest: public TestSuite::Tester {
public: public:
RemoveDuplicatesTest(); RemoveDuplicatesTest();
void cleanMesh(); void removeDuplicates();
}; };
typedef Math::Vector<1, int> Vector1;
RemoveDuplicatesTest::RemoveDuplicatesTest() { RemoveDuplicatesTest::RemoveDuplicatesTest() {
addTests({&RemoveDuplicatesTest::cleanMesh}); addTests({&RemoveDuplicatesTest::removeDuplicates});
} }
void RemoveDuplicatesTest::cleanMesh() { void RemoveDuplicatesTest::removeDuplicates() {
std::vector<Vector1> positions{1, 2, 1, 4}; /* Numbers with distance 1 should be merged, numbers with distance 2 should
std::vector<UnsignedInt> indices{0, 1, 2, 1, 2, 3}; be kept. Testing both even-odd and odd-even sequence to verify that
MeshTools::removeDuplicates(indices, positions); half-epsilon translations are applied properly. */
std::vector<Vector2i> data{
{1, 0},
{2, 1},
{0, 4},
{1, 5}
};
/* Verify cleanup */ const std::vector<UnsignedInt> indices = MeshTools::removeDuplicates(data, 2);
CORRADE_VERIFY(positions == (std::vector<Vector1>{1, 2, 4})); CORRADE_COMPARE(indices, (std::vector<UnsignedInt>{0, 0, 1, 1}));
CORRADE_COMPARE(indices, (std::vector<UnsignedInt>{0, 1, 0, 1, 0, 2})); CORRADE_COMPARE(data, (std::vector<Vector2i>{
{1, 0},
{0, 4}
}));
} }
}}} }}}

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

@ -71,11 +71,6 @@ void SubdivideTest::subdivide() {
CORRADE_VERIFY(positions == (std::vector<Vector1>{0, 2, 6, 8, 1, 4, 3, 4, 7, 5})); CORRADE_VERIFY(positions == (std::vector<Vector1>{0, 2, 6, 8, 1, 4, 3, 4, 7, 5}));
CORRADE_COMPARE(indices, (std::vector<UnsignedInt>{4, 5, 6, 7, 8, 9, 0, 4, 6, 4, 1, 5, 6, 5, 2, 1, 7, 9, 7, 2, 8, 9, 8, 3})); CORRADE_COMPARE(indices, (std::vector<UnsignedInt>{4, 5, 6, 7, 8, 9, 0, 4, 6, 4, 1, 5, 6, 5, 2, 1, 7, 9, 7, 2, 8, 9, 8, 3}));
MeshTools::removeDuplicates(indices, positions);
/* Positions 0, 1, 2, 3, 4, 5, 6, 7, 8 */
CORRADE_COMPARE(positions.size(), 9);
} }
}}} }}}

5
src/Magnum/Primitives/Icosphere.cpp

@ -27,8 +27,9 @@
#include "Magnum/Mesh.h" #include "Magnum/Mesh.h"
#include "Magnum/Math/Vector3.h" #include "Magnum/Math/Vector3.h"
#include "Magnum/MeshTools/Subdivide.h" #include "Magnum/MeshTools/Duplicate.h"
#include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/RemoveDuplicates.h"
#include "Magnum/MeshTools/Subdivide.h"
#include "Magnum/Trade/MeshData3D.h" #include "Magnum/Trade/MeshData3D.h"
namespace Magnum { namespace Primitives { namespace Magnum { namespace Primitives {
@ -77,7 +78,7 @@ Trade::MeshData3D Icosphere::solid(const UnsignedInt subdivisions) {
return (a+b).normalized(); return (a+b).normalized();
}); });
MeshTools::removeDuplicates(indices, positions); indices = MeshTools::duplicate(indices, MeshTools::removeDuplicates(positions));
std::vector<Vector3> normals(positions); std::vector<Vector3> normals(positions);
return Trade::MeshData3D(MeshPrimitive::Triangles, std::move(indices), {std::move(positions)}, {std::move(normals)}, std::vector<std::vector<Vector2>>{}); return Trade::MeshData3D(MeshPrimitive::Triangles, std::move(indices), {std::move(positions)}, {std::move(normals)}, std::vector<std::vector<Vector2>>{});

Loading…
Cancel
Save