Browse Source

MeshTools: reworked combineIndexedArrays().

Added combineIndexArrays() function, which can combine any number of
indexarrays (i.e. number specified at runtime). The function originated
in ColladaImporter plugin and is moved here to make it useful elsewhere.
It is now used as base for combineIndexedArrays(), which is further
simplified without unnecessary functor-like implementation. Also
improved documentation.

The combineIndexedArrays() function now takes std::pair instead of
std::tuple, the previous version is alias to the new one, is marked as
deprecated and will be removed in some future release.
pull/51/head
Vladimír Vondruš 12 years ago
parent
commit
d19b3837fe
  1. 1
      src/Magnum/MeshTools/CMakeLists.txt
  2. 159
      src/Magnum/MeshTools/CombineIndexedArrays.cpp
  3. 173
      src/Magnum/MeshTools/CombineIndexedArrays.h
  4. 2
      src/Magnum/MeshTools/Test/CMakeLists.txt
  5. 36
      src/Magnum/MeshTools/Test/CombineIndexedArraysTest.cpp

1
src/Magnum/MeshTools/CMakeLists.txt

@ -31,6 +31,7 @@ set(MagnumMeshTools_SRCS
# Files compiled with different flags for main library and unit test library
set(MagnumMeshTools_GracefulAssert_SRCS
CombineIndexedArrays.cpp
FlipNormals.cpp
GenerateFlatNormals.cpp)

159
src/Magnum/MeshTools/CombineIndexedArrays.cpp

@ -0,0 +1,159 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include "CombineIndexedArrays.h"
#include <cstring>
#include <unordered_map>
#include <Corrade/Utility/Assert.h>
#include <Corrade/Utility/MurmurHash2.h>
#include "Magnum/Magnum.h"
namespace Magnum { namespace MeshTools {
namespace Implementation {
std::pair<std::vector<UnsignedInt>, std::vector<UnsignedInt>> interleaveAndCombineIndexArrays(const std::reference_wrapper<const std::vector<UnsignedInt>>* begin, const std::reference_wrapper<const std::vector<UnsignedInt>>* end) {
/* Array stride and size */
const UnsignedInt stride = end - begin;
const UnsignedInt inputSize = begin->get().size();
#ifndef CORRADE_NO_ASSERT
for(auto it = begin; it != end; ++it)
CORRADE_ASSERT(it->get().size() == inputSize, "MeshTools::combineIndexArrays(): the arrays don't have the same size", {});
#endif
/* Interleave the arrays */
std::vector<UnsignedInt> interleavedArrays;
interleavedArrays.resize(inputSize*stride);
for(UnsignedInt offset = 0; offset != stride; ++offset) {
const auto& array = (begin+offset)->get();
for(UnsignedInt i = 0; i != inputSize; ++i)
interleavedArrays[offset + i*stride] = array[i];
}
/* Combine them */
std::vector<UnsignedInt> combinedIndices;
std::tie(combinedIndices, interleavedArrays) = combineIndexArrays(interleavedArrays, stride);
return {combinedIndices, interleavedArrays};
}
}
std::vector<UnsignedInt> combineIndexArrays(const std::initializer_list<std::reference_wrapper<std::vector<UnsignedInt>>> arrays) {
/* Interleave and combine the arrays */
std::vector<UnsignedInt> combinedIndices;
std::vector<UnsignedInt> interleavedCombinedArrays;
std::tie(combinedIndices, interleavedCombinedArrays) = Implementation::interleaveAndCombineIndexArrays(
/* This will bite me hard once. */
reinterpret_cast<const std::reference_wrapper<const std::vector<UnsignedInt>>*>(arrays.begin()),
reinterpret_cast<const std::reference_wrapper<const std::vector<UnsignedInt>>*>(arrays.end()));
/* Update the original indices */
const UnsignedInt stride = arrays.size();
const UnsignedInt outputSize = interleavedCombinedArrays.size()/stride;
for(UnsignedInt offset = 0; offset != stride; ++offset) {
auto& array = (arrays.begin()+offset)->get();
CORRADE_INTERNAL_ASSERT(array.size() >= outputSize);
array.resize(outputSize);
for(UnsignedInt i = 0; i != outputSize; ++i)
array[i] = interleavedCombinedArrays[offset + i*stride];
}
return combinedIndices;
}
namespace {
class IndexHash {
public:
explicit IndexHash(const std::vector<UnsignedInt>& indices, UnsignedInt stride): indices(indices), stride(stride) {}
std::size_t operator()(UnsignedInt key) const {
return *reinterpret_cast<const std::size_t*>(Utility::MurmurHash2()(reinterpret_cast<const char*>(indices.data()+key*stride), sizeof(UnsignedInt)*stride).byteArray());
}
private:
const std::vector<UnsignedInt>& indices;
UnsignedInt stride;
};
class IndexEqual {
public:
explicit IndexEqual(const std::vector<UnsignedInt>& indices, UnsignedInt stride): indices(indices), stride(stride) {}
bool operator()(UnsignedInt a, UnsignedInt b) const {
return std::memcmp(indices.data()+a*stride, indices.data()+b*stride, sizeof(UnsignedInt)*stride) == 0;
}
private:
const std::vector<UnsignedInt>& indices;
UnsignedInt stride;
};
}
std::pair<std::vector<UnsignedInt>, std::vector<UnsignedInt>> combineIndexArrays(const std::vector<UnsignedInt>& interleavedArrays, const UnsignedInt stride) {
CORRADE_ASSERT(interleavedArrays.size() % stride == 0, "MeshTools::combineIndexArrays(): array size is not divisible by stride", {});
/* Hash map with index combinations, containing just indices into
interleavedArrays vector, hashing and comparison is done using IndexHash
and IndexEqual functors. Reserving more buckets than necessary (i.e. as
if each combination was unique). */
std::unordered_map<UnsignedInt, UnsignedInt, IndexHash, IndexEqual> indexCombinations(
interleavedArrays.size()/stride,
IndexHash(interleavedArrays, stride),
IndexEqual(interleavedArrays, stride));
/* Make the index combinations unique. Original indices into original
`interleavedArrays` array were 0, 1, 2, 3, ..., `combinedIndices`
contains new ones into new (shorter) `newInterleavedArrays` array. */
std::vector<UnsignedInt> combinedIndices;
combinedIndices.reserve(interleavedArrays.size()/stride);
std::vector<UnsignedInt> newInterleavedArrays;
for(std::size_t oldIndex = 0, end = interleavedArrays.size()/stride; oldIndex != end; ++oldIndex) {
/* Try to insert new index combination to the map */
#ifndef CORRADE_GCC46_COMPATIBILITY
const auto result = indexCombinations.emplace(oldIndex, indexCombinations.size());
#else
const auto result = indexCombinations.insert({oldIndex, indexCombinations.size()});
#endif
/* Add the (either new or already existing) index to resulting index array */
combinedIndices.push_back(result.first->second);
/* If this is new combination, copy it to new interleaved arrays */
if(result.second) newInterleavedArrays.insert(newInterleavedArrays.end(),
interleavedArrays.begin()+oldIndex*stride,
interleavedArrays.begin()+(oldIndex+1)*stride);
}
CORRADE_INTERNAL_ASSERT(combinedIndices.size() == interleavedArrays.size()/stride &&
newInterleavedArrays.size() <= interleavedArrays.size());
return {std::move(combinedIndices), std::move(newInterleavedArrays)};
}
}}

173
src/Magnum/MeshTools/CombineIndexedArrays.h

@ -26,74 +26,105 @@
*/
/** @file
* @brief Function Magnum::MeshTools::combineIndexedArrays()
* @brief Function @ref Magnum::MeshTools::combineIndexArrays(), @ref Magnum::MeshTools::combineIndexedArrays()
*/
#include <vector>
#include <numeric>
#include <functional>
#include <tuple>
#include <vector>
#include "Magnum/Math/Vector.h"
#include "Magnum/MeshTools/RemoveDuplicates.h"
#include "Magnum/Types.h"
#include "Magnum/MeshTools/visibility.h"
#ifdef MAGNUM_BUILD_DEPRECATED
#include <Corrade/Utility/Macros.h>
#endif
namespace Magnum { namespace MeshTools {
namespace Implementation {
/**
@brief Combine index arrays
Creates new combined index array and updates the original ones with translation
to new ones. For example, when you have position and normal array, each indexed
with separate indices and you want to index both of them with single index
array:
a b c d e f // positions
A B C D E F G // normals
0 2 5 0 0 1 3 2 2 // position indices
1 3 4 1 4 6 1 3 1 // normal indices
In particular, first triangle in the mesh will have positions `a c f` and
normals `B D E`. You can see that not all combinations are unique and also that
there are some vertices unused. When you pass the two index arrays above to
this function, the following combined index array is returned:
0 1 2 0 3 4 5 1 6
And the original arrays are cleaned up to have only unique combinations:
0 2 5 0 1 3 2
1 3 4 4 6 1 1
You can use these as translation table to create new vertex and normal arrays
which can be then indexed with the combined index array:
a c f a b d c
B D E E G B B
class CombineIndexedArrays {
public:
template<class ...T> std::vector<UnsignedInt> operator()(const std::tuple<const std::vector<UnsignedInt>&, std::vector<T>&>&... indexedArrays) {
/* Compute index count */
std::size_t _indexCount = indexCount(std::get<0>(indexedArrays)...);
Again, first triangle in the mesh will have positions `a c f` and normals
`B D E`.
/* Resulting index array */
std::vector<UnsignedInt> result;
result.resize(_indexCount);
std::iota(result.begin(), result.end(), 0);
This function calls @ref combineIndexArrays(std::vector<UnsignedInt>&, UnsignedInt)
internally. See also @ref combineIndexedArrays() which does the vertex data
reordering automatically.
*/
MAGNUM_MESHTOOLS_EXPORT std::vector<UnsignedInt> combineIndexArrays(std::initializer_list<std::reference_wrapper<std::vector<UnsignedInt>>> arrays);
/**
@brief Combine index arrays
Unlike above, this function takes one interleaved array instead of separate
index arrays. Continuing with the above example, you would call this function
with the following array (odd value is vertex index, even is normal index,
@p stride is thus 2):
/* All index combinations */
std::vector<Math::Vector<sizeof...(indexedArrays), UnsignedInt> > indexCombinations(_indexCount);
writeCombinedIndices(indexCombinations, std::get<0>(indexedArrays)...);
0 1 2 3 5 4 0 1 0 4 1 6 3 1 2 3 2 1
/* Make the combinations unique */
MeshTools::removeDuplicates(result, indexCombinations);
Similarly to above this function will return the following combined index
array as first pair value:
/* Write combined arrays */
writeCombinedArrays(indexCombinations, std::get<1>(indexedArrays)...);
0 1 2 0 3 4 5 1 6
return result;
}
And second pair value is the cleaned up interleaved array:
private:
template<class ...T> static std::size_t indexCount(const std::vector<UnsignedInt>& first, const std::vector<T>&... next) {
CORRADE_ASSERT(sizeof...(next) == 0 || indexCount(next...) == first.size(), "MeshTools::combineIndexedArrays(): index arrays don't have the same length, nothing done.", 0);
0 1 2 3 5 4 0 4 1 6 3 1 2 1
return first.size();
}
@see @ref combineIndexedArrays()
*/
MAGNUM_MESHTOOLS_EXPORT std::pair<std::vector<UnsignedInt>, std::vector<UnsignedInt>> combineIndexArrays(const std::vector<UnsignedInt>& interleavedArrays, UnsignedInt stride);
template<std::size_t size, class ...T> static void writeCombinedIndices(std::vector<Math::Vector<size, UnsignedInt>>& output, const std::vector<UnsignedInt>& first, const std::vector<T>&... next) {
/* Copy the data to output */
for(std::size_t i = 0; i != output.size(); ++i)
output[i][size-sizeof...(next)-1] = first[i];
namespace Implementation {
writeCombinedIndices(output, next...);
}
MAGNUM_MESHTOOLS_EXPORT std::pair<std::vector<UnsignedInt>, std::vector<UnsignedInt>> interleaveAndCombineIndexArrays(const std::reference_wrapper<const std::vector<UnsignedInt>>* begin, const std::reference_wrapper<const std::vector<UnsignedInt>>* end);
template<std::size_t size, class T, class ...U> static void writeCombinedArrays(const std::vector<Math::Vector<size, UnsignedInt>>& combinedIndices, std::vector<T>& first, std::vector<U>&... next) {
/* Rewrite output array */
std::vector<T> output;
for(std::size_t i = 0; i != combinedIndices.size(); ++i)
output.push_back(first[combinedIndices[i][size-sizeof...(next)-1]]);
std::swap(output, first);
template<class T> void writeCombinedArray(const UnsignedInt stride, const UnsignedInt offset, const std::vector<UnsignedInt>& interleavedCombinedIndexArrays, std::vector<T>& array) {
std::vector<T> output;
output.reserve(interleavedCombinedIndexArrays.size()/stride);
for(std::size_t i = 0, max = interleavedCombinedIndexArrays.size()/stride; i != max; ++i)
output.push_back(array[interleavedCombinedIndexArrays[offset + i*stride]]);
std::swap(output, array);
}
writeCombinedArrays(combinedIndices, next...);
}
/* Terminator for recursive calls */
inline void writeCombinedArrays(UnsignedInt, UnsignedInt, const std::vector<UnsignedInt>&) {}
/* Terminator functions for recursive calls */
static std::size_t indexCount() { return 0; }
template<std::size_t size> static void writeCombinedIndices(std::vector<Math::Vector<size, UnsignedInt>>&) {}
template<std::size_t size> static void writeCombinedArrays(const std::vector<Math::Vector<size, UnsignedInt>>&) {}
};
template<class T, class ...U> inline void writeCombinedArrays(UnsignedInt stride, UnsignedInt offset, const std::vector<UnsignedInt>& interleavedCombinedIndexArrays, std::vector<T>& first, std::vector<U>&... next) {
writeCombinedArray(stride, offset, interleavedCombinedIndexArrays, first);
writeCombinedArrays(stride, offset + 1, interleavedCombinedIndexArrays, next...);
}
}
@ -102,15 +133,13 @@ class CombineIndexedArrays {
@param[in,out] indexedArrays Index and attribute arrays
@return %Array with resulting indices
When you have e.g. vertex, normal and texture array, each indexed with
different indices, you can use this function to combine them to use the same
indices. The function returns array with resulting indices and replaces
original attribute arrays with combined ones.
Creates new combined index array and reorders original attribute arrays so they
can be indexed with the new single index array.
The index array must be passed as const reference (to avoid copying) and
attribute array as reference, so it can be replaced with combined data. To
avoid explicit verbose specification of tuple type, you can write it with help
of some STL functions like shown below. Also if one index array is shader by
of some STL functions like shown below. Also if one index array is shared by
more than one attribute array, just pass the index array more times. Example:
@code
std::vector<UnsignedInt> vertexIndices;
@ -120,25 +149,43 @@ std::vector<Vector3> normals;
std::vector<Vector2> textureCoordinates;
std::vector<UnsignedInt> indices = MeshTools::combineIndexedArrays(
std::make_tuple(std::cref(vertexIndices), std::ref(positions)),
std::make_tuple(std::cref(normalTextureIndices), std::ref(normals)),
std::make_tuple(std::cref(normalTextureIndices), std::ref(textureCoordinates))
std::make_pair(std::cref(vertexIndices), std::ref(positions)),
std::make_pair(std::cref(normalTextureIndices), std::ref(normals)),
std::make_pair(std::cref(normalTextureIndices), std::ref(textureCoordinates))
);
@endcode
`positions`, `normals` and `textureCoordinates` will then contain combined
attributes indexed with `indices`.
@attention The function expects that all arrays have the same size.
@todo Use `std::pair` (to avoid `std::make_tuple`), make this usable also at
runtime
See @ref combineIndexArrays() documentation for more information about the
procedure.
@todo Invent a way which avoids these overly verbose parameters (`std::pair`
doesn't help)
*/
/* Implementation note: It's done using tuples because it is more clear which
parameter is index array and which is attribute array, mainly when both are
of the same type. */
template<class ...T> std::vector<UnsignedInt> combineIndexedArrays(const std::tuple<const std::vector<UnsignedInt>&, std::vector<T>&>&... indexedArrays) {
return Implementation::CombineIndexedArrays()(indexedArrays...);
template<class ...T> std::vector<UnsignedInt> combineIndexedArrays(const std::pair<const std::vector<UnsignedInt>&, std::vector<T>&>&... indexedArrays) {
/* Interleave and combine index arrays */
std::vector<UnsignedInt> combinedIndices;
std::vector<UnsignedInt> interleavedCombinedIndexArrays;
auto i = {std::ref(indexedArrays.first)...};
std::tie(combinedIndices, interleavedCombinedIndexArrays) = Implementation::interleaveAndCombineIndexArrays(i.begin(), i.end());
/* Write combined arrays */
Implementation::writeCombinedArrays(sizeof...(T), 0, interleavedCombinedIndexArrays, indexedArrays.second...);
return combinedIndices;
}
#ifdef MAGNUM_BUILD_DEPRECATED
/**
* @copybrief combineIndexedArrays(const std::pair<const std::vector<UnsignedInt>&, std::vector<T>&>&...)
* @deprecated Use @ref Magnum::MeshTools::combineIndexedArrays(const std::pair<const std::vector<UnsignedInt>&, std::vector<T>&>&...) "combineIndexedArrays(const std::pair<const std::vector<UnsignedInt>&, std::vector<T>&>&...)" instead.
*/
template<class ...T> inline CORRADE_DEPRECATED("use combineIndexedArrays(const std::pair<const std::vector<UnsignedInt>&, std::vector<T>&>&...) instead") std::vector<UnsignedInt> combineIndexedArrays(const std::tuple<const std::vector<UnsignedInt>&, std::vector<T>&>&... indexedArrays) {
return combineIndexedArrays(std::make_pair(std::cref(std::get<0>(indexedArrays)), std::ref(std::get<1>(indexedArrays)))...);
}
#endif
}}
#endif

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

@ -23,7 +23,7 @@
# DEALINGS IN THE SOFTWARE.
#
corrade_add_test(MeshToolsCombineIndexedArraysTest CombineIndexedArraysTest.cpp)
corrade_add_test(MeshToolsCombineIndexedArraysTest CombineIndexedArraysTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsCompressIndicesTest CompressIndicesTest.cpp LIBRARIES MagnumMeshTools)
corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp)
corrade_add_test(MeshToolsFlipNormalsTest FlipNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib)

36
src/Magnum/MeshTools/Test/CombineIndexedArraysTest.cpp

@ -27,7 +27,7 @@
#include <sstream>
#include <Corrade/TestSuite/Tester.h>
#include "Magnum/Types.h"
#include "Magnum/Magnum.h"
#include "Magnum/MeshTools/CombineIndexedArrays.h"
namespace Magnum { namespace MeshTools { namespace Test {
@ -37,12 +37,14 @@ class CombineIndexedArraysTest: public TestSuite::Tester {
CombineIndexedArraysTest();
void wrongIndexCount();
void combine();
void indexArrays();
void indexedArrays();
};
CombineIndexedArraysTest::CombineIndexedArraysTest() {
addTests({&CombineIndexedArraysTest::wrongIndexCount,
&CombineIndexedArraysTest::combine});
&CombineIndexedArraysTest::indexArrays,
&CombineIndexedArraysTest::indexedArrays});
}
void CombineIndexedArraysTest::wrongIndexCount() {
@ -50,16 +52,24 @@ void CombineIndexedArraysTest::wrongIndexCount() {
Error::setOutput(&ss);
std::vector<UnsignedInt> a{0, 1, 0};
std::vector<UnsignedInt> b{3, 4};
std::vector<UnsignedInt> array;
std::vector<UnsignedInt> result = MeshTools::combineIndexedArrays(
std::make_tuple(std::cref(a), std::ref(array)),
std::make_tuple(std::cref(b), std::ref(array)));
std::vector<UnsignedInt> result = MeshTools::combineIndexArrays({a, b});
CORRADE_COMPARE(ss.str(), "MeshTools::combineIndexArrays(): the arrays don't have the same size\n");
}
void CombineIndexedArraysTest::indexArrays() {
std::vector<UnsignedInt> a{0, 1, 0};
std::vector<UnsignedInt> b{3, 4, 3};
std::vector<UnsignedInt> c{6, 7, 6};
CORRADE_COMPARE(result.size(), 0);
CORRADE_COMPARE(ss.str(), "MeshTools::combineIndexedArrays(): index arrays don't have the same length, nothing done.\n");
std::vector<UnsignedInt> result = MeshTools::combineIndexArrays({a, b, c});
CORRADE_COMPARE(result, (std::vector<UnsignedInt>{0, 1, 0}));
CORRADE_COMPARE(a, (std::vector<UnsignedInt>{0, 1}));
CORRADE_COMPARE(b, (std::vector<UnsignedInt>{3, 4}));
CORRADE_COMPARE(c, (std::vector<UnsignedInt>{6, 7}));
}
void CombineIndexedArraysTest::combine() {
void CombineIndexedArraysTest::indexedArrays() {
std::vector<UnsignedInt> a{0, 1, 0};
std::vector<UnsignedInt> b{3, 4, 3};
std::vector<UnsignedInt> c{6, 7, 6};
@ -68,9 +78,9 @@ void CombineIndexedArraysTest::combine() {
std::vector<UnsignedInt> array3{ 0, 1, 2, 3, 4, 5, 6, 7 };
std::vector<UnsignedInt> result = MeshTools::combineIndexedArrays(
std::make_tuple(std::cref(a), std::ref(array1)),
std::make_tuple(std::cref(b), std::ref(array2)),
std::make_tuple(std::cref(c), std::ref(array3)));
std::make_pair(std::cref(a), std::ref(array1)),
std::make_pair(std::cref(b), std::ref(array2)),
std::make_pair(std::cref(c), std::ref(array3)));
CORRADE_COMPARE(result, (std::vector<UnsignedInt>{0, 1, 0}));
CORRADE_COMPARE(array1, (std::vector<UnsignedInt>{0, 1}));

Loading…
Cancel
Save