Browse Source

MeshTools: implement removeDuplicatesFuzzy() for MeshData.

pull/449/head
Vladimír Vondruš 6 years ago
parent
commit
145e055b41
  1. 143
      src/Magnum/MeshTools/RemoveDuplicates.cpp
  2. 13
      src/Magnum/MeshTools/RemoveDuplicates.h
  3. 406
      src/Magnum/MeshTools/Test/RemoveDuplicatesTest.cpp

143
src/Magnum/MeshTools/RemoveDuplicates.cpp

@ -37,6 +37,7 @@
#include "Magnum/Math/FunctionsBatch.h"
#include "Magnum/Math/Range.h"
#include "Magnum/MeshTools/Concatenate.h"
#include "Magnum/MeshTools/Duplicate.h"
#include "Magnum/MeshTools/Interleave.h"
#include "Magnum/Trade/MeshData.h"
@ -454,4 +455,146 @@ Trade::MeshData removeDuplicates(Trade::MeshData&& data) {
uniqueVertexCount};
}
Trade::MeshData removeDuplicatesFuzzy(const Trade::MeshData& data, const Float floatEpsilon, const Double doubleEpsilon) {
CORRADE_ASSERT(data.attributeCount(),
"MeshTools::removeDuplicatesFuzzy(): can't remove duplicates in an attributeless mesh",
(Trade::MeshData{MeshPrimitive::Points, 0}));
/* Turn the passed data into an owned mutable instance we can operate on.
There's a chance the original data are already like this, in which case
this will be just a passthrough. */
/** @todo concatenate() causes the resulting index type to be UnsignedInt
always, replace with owned() or some such when that's done */
Trade::MeshData owned = concatenate(std::move(data));
/* Allocate an interleaved index array for all attribs. If the mesh is
already indexed, use the existing index count and copy the original
index array there so the algorithm can operate directly on it. */
Containers::Array<UnsignedInt> combinedIndexStorage;
Containers::StridedArrayView2D<UnsignedInt> combinedIndices;
/* If the mesh is not indexed, allocate for vertex count and keep it
unitialized */
combinedIndexStorage = Containers::Array<UnsignedInt>{/*Containers::NoInit,*/
owned.vertexCount()*owned.attributeCount()};
combinedIndices = Containers::StridedArrayView2D<UnsignedInt>{
combinedIndexStorage,
{owned.vertexCount(), owned.attributeCount()}};
/* For each attribute decide if it needs to be fuzzy-deduplicated or not,
calculate the epsilon size and call the appropriate API */
const Containers::StridedArrayView2D<UnsignedInt> perAttributeIndices = combinedIndices.transposed<0, 1>();
for(UnsignedInt i = 0; i != owned.attributeCount(); ++i) {
const VertexFormat format = owned.attributeFormat(i);
CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format),
"MeshTools::removeDuplicatesFuzzy(): can't remove duplicates in" << format,
(Trade::MeshData{MeshPrimitive::Points, 0}));
const Containers::StridedArrayView1D<UnsignedInt> outputIndices = perAttributeIndices[i];
/* Floats, with special attribute-dependent handling */
const VertexFormat componentFormat = vertexFormatComponentFormat(format);
if(componentFormat == VertexFormat::Float) {
const Containers::StridedArrayView2D<Float> attribute = Containers::arrayCast<2, Float>(owned.mutableAttribute(i));
/* Calculate scaled epsilon */
Float attributeEpsilon = 0.0f;
switch(owned.attributeName(i)) {
/* These are usually in [0, 1] (color can be HDR but we
definitely don't want the epsilon to be higher there,
texture coords can be higher and repeat but the same
applies), use epsilon as-is */
case Trade::MeshAttribute::TextureCoordinates:
case Trade::MeshAttribute::Color:
attributeEpsilon = floatEpsilon;
break;
/* Those are all [-1, 1], scale the epsilon 2x */
case Trade::MeshAttribute::Normal:
case Trade::MeshAttribute::Tangent:
case Trade::MeshAttribute::Bitangent:
attributeEpsilon = 2.0f*floatEpsilon;
break;
/* These have unbounded range. Do nothing but enumerate all
these here to silence warnings about unused enum values. */
case Trade::MeshAttribute::Position:
case Trade::MeshAttribute::Custom:
break;
/* These shouldn't be floating point */
/* LCOV_EXCL_START */
case Trade::MeshAttribute::ObjectId:
CORRADE_INTERNAL_ASSERT_UNREACHABLE();
/* LCOV_EXCL_STOP */
}
/* For unbounded and custom attributes scale the epsilon by data
range */
if(attributeEpsilon == 0.0f) {
Float range = 0.0f;
for(Containers::StridedArrayView1D<const Float> component: attribute.transposed<0, 1>())
range = Math::max(Range1D{Math::minmax(component)}.size(), range);
attributeEpsilon = floatEpsilon*range;
}
removeDuplicatesFuzzyInPlaceIntoImplementation(attribute, outputIndices, attributeEpsilon);
/* Doubles. No builtin attributes support those at the moment, so
there's just the epsilon scaling based on attribute value range */
} else if(componentFormat == VertexFormat::Double) {
const Containers::StridedArrayView2D<Double> attribute = Containers::arrayCast<2, Double>(owned.mutableAttribute(i));
Double range = 0.0;
for(Containers::StridedArrayView1D<const Double> component: attribute.transposed<0, 1>())
range = Math::max(Range1Dd{Math::minmax(component)}.size(), range);
removeDuplicatesFuzzyInPlaceIntoImplementation(attribute, outputIndices, doubleEpsilon*range);
/* Other attributes (integer, packed, half floats). No fuzzy
comparison */
} else {
const Containers::StridedArrayView2D<char> attribute = owned.mutableAttribute(i);
removeDuplicatesInPlaceInto(attribute, outputIndices);
}
}
/* Make the combined index array unique */
Containers::Array<char> indexData;
UnsignedInt vertexCount;
MeshIndexType indexType;
if(!owned.isIndexed()) {
indexData = Containers::Array<char>{combinedIndices.size()[0]*sizeof(UnsignedInt)};
vertexCount = removeDuplicatesInPlaceInto(
Containers::arrayCast<2, char>(combinedIndices),
Containers::arrayCast<UnsignedInt>(indexData));
indexType = MeshIndexType::UnsignedInt;
} else {
vertexCount = removeDuplicatesIndexedInPlace(
owned.mutableIndices(),
Containers::arrayCast<2, char>(combinedIndices));
indexData = owned.releaseIndexData();
indexType = owned.indexType();
}
combinedIndices = combinedIndices.prefix(vertexCount);
Trade::MeshData layout = interleavedLayout(owned, vertexCount);
Trade::MeshIndexData indices{indexType, indexData};
Trade::MeshData out{layout.primitive(),
std::move(indexData), indices,
layout.releaseVertexData(), layout.releaseAttributeData(), vertexCount};
{
/* Duplicate the attributes according to the combined index buffer */
const Containers::StridedArrayView2D<UnsignedInt> perAttributeIndices = combinedIndices.transposed<0, 1>();
for(UnsignedInt i = 0; i != owned.attributeCount(); ++i)
duplicateInto(perAttributeIndices[i].prefix(vertexCount), owned.attribute(i), out.mutableAttribute(i));
}
return out;
}
}}

13
src/Magnum/MeshTools/RemoveDuplicates.h

@ -318,6 +318,19 @@ data.
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData removeDuplicates(Trade::MeshData&& data);
/**
@brief Remove mesh data duplicates with fuzzy comparison for floating-point attributes
@m_since_latest
Compared to @ref removeDuplicates(const Trade::MeshData&), calls
@ref removeDuplicatesFuzzyInPlace() or @ref removeDuplicatesFuzzyIndexedInPlace()
on floating-point attributes. For attributes with a known range (such as
@ref Trade::MeshAttribute::Normal being always @f$ [-1, 1] @f$ in each
direction) the @p floatEpsilon / @p doubleEpsilon is scaled appropriately,
otherwise it's scaled to calculated value range.
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData removeDuplicatesFuzzy(const Trade::MeshData& data, Float floatEpsilon = Math::TypeTraits<Float>::epsilon(), Double doubleEpsilon = Math::TypeTraits<Double>::epsilon());
#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 */

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

@ -26,6 +26,7 @@
#include <algorithm>
#include <random>
#include <sstream>
#include <Corrade/Containers/GrowableArray.h>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/DebugStl.h>
@ -73,6 +74,11 @@ struct RemoveDuplicatesTest: TestSuite::Tester {
void removeDuplicatesMeshData();
void removeDuplicatesMeshDataAttributeless();
void removeDuplicatesMeshDataFuzzy();
void removeDuplicatesMeshDataFuzzyDouble();
void removeDuplicatesMeshDataFuzzyAttributeless();
void removeDuplicatesMeshDataFuzzyImplementationSpecific();
void soakTest();
void soakTestFuzzy();
@ -88,6 +94,66 @@ const struct {
{"indexed", true}
};
const struct {
const char* name;
Containers::Array<Trade::MeshAttributeData> attributes;
Float offset, scale, epsilon;
UnsignedInt vertexCount;
bool indexed;
} RemoveDuplicatesMeshDataFuzzyData[] {
{"position, normal", Containers::array({
Trade::MeshAttributeData{Trade::MeshAttribute::Position,
VertexFormat::Vector3, 0, 10, 6*sizeof(Float)},
Trade::MeshAttributeData{Trade::MeshAttribute::Normal,
VertexFormat::Vector3, 3*sizeof(Float), 10, 6*sizeof(Float)}
}), 0.0f, 1.0f, Math::TypeTraits<Float>::epsilon(), 7, false},
{"position, normal, epsilon 0", Containers::array({
Trade::MeshAttributeData{Trade::MeshAttribute::Position,
VertexFormat::Vector3, 0, 10, 6*sizeof(Float)},
Trade::MeshAttributeData{Trade::MeshAttribute::Normal,
VertexFormat::Vector3, 3*sizeof(Float), 10, 6*sizeof(Float)}
/* Only the bit-exact value gets removed */
}), 0.0f, 1.0f, 0.0f, 9, false},
{"position, normal, indexed", Containers::array({
Trade::MeshAttributeData{Trade::MeshAttribute::Position,
VertexFormat::Vector3, 0, 10, 6*sizeof(Float)},
Trade::MeshAttributeData{Trade::MeshAttribute::Normal,
VertexFormat::Vector3, 3*sizeof(Float), 10, 6*sizeof(Float)}
}), 0.0f, 1.0f, Math::TypeTraits<Float>::epsilon(), 7, true},
{"custom mat3x2, offset 100",Containers::array({
Trade::MeshAttributeData{Trade::meshAttributeCustom(42),
VertexFormat::Matrix3x2, 0, 10, 6*sizeof(Float)}
}), 100.0f, 1.0f, Math::TypeTraits<Float>::epsilon(), 7, false},
{"position + custom float[3], offset 100, scale 10, indexed",Containers::array({
Trade::MeshAttributeData{Trade::meshAttributeCustom(42),
VertexFormat::Float, 0, 10, 6*sizeof(Float), 3},
Trade::MeshAttributeData{Trade::MeshAttribute::Position,
VertexFormat::Vector3, 3*sizeof(Float), 10, 6*sizeof(Float)}
}), 100.0f, 10.0f, Math::TypeTraits<Float>::epsilon(), 7, true},
{"normal. bitangent, scale 2", Containers::array({
Trade::MeshAttributeData{Trade::MeshAttribute::Normal,
VertexFormat::Vector3, 0, 10, 6*sizeof(Float)},
Trade::MeshAttributeData{Trade::MeshAttribute::Bitangent,
VertexFormat::Vector3, 3*sizeof(Float), 10, 6*sizeof(Float)}
/* Should still fit into the epsilon as the range is [-1, 1] */
}), 0.0f, 2.0f, Math::TypeTraits<Float>::epsilon(), 7, false},
{"color, texcoord, scale 10", Containers::array({
Trade::MeshAttributeData{Trade::MeshAttribute::Color,
VertexFormat::Vector4, 0, 10, 6*sizeof(Float)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates,
VertexFormat::Vector2, 4*sizeof(Float), 10, 6*sizeof(Float)}
/* Should not fit into the epsilon, only the bit-exact value gets
removed */
}), 0.0f, 10.0f, Math::TypeTraits<Float>::epsilon(), 9, true},
{"color, texcoord, scale 10, epsilon *10",Containers::array({
Trade::MeshAttributeData{Trade::MeshAttribute::Color,
VertexFormat::Vector4, 0, 10, 6*sizeof(Float)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates,
VertexFormat::Vector2, 4*sizeof(Float), 10, 6*sizeof(Float)}
/* Fit into the epsilon again */
}), 0.0f, 10.0f, 10.0f*Math::TypeTraits<Float>::epsilon(), 7, false}
};
RemoveDuplicatesTest::RemoveDuplicatesTest() {
addTests({&RemoveDuplicatesTest::removeDuplicates,
&RemoveDuplicatesTest::removeDuplicatesNonContiguous,
@ -137,6 +203,14 @@ RemoveDuplicatesTest::RemoveDuplicatesTest() {
addTests({&RemoveDuplicatesTest::removeDuplicatesMeshDataAttributeless});
addInstancedTests({&RemoveDuplicatesTest::removeDuplicatesMeshDataFuzzy},
Containers::arraySize(RemoveDuplicatesMeshDataFuzzyData));
addTests({&RemoveDuplicatesTest::removeDuplicatesMeshDataFuzzyDouble,
&RemoveDuplicatesTest::removeDuplicatesMeshDataFuzzyAttributeless,
&RemoveDuplicatesTest::removeDuplicatesMeshDataFuzzyImplementationSpecific});
addRepeatedTests({&RemoveDuplicatesTest::soakTest,
&RemoveDuplicatesTest::soakTestFuzzy}, 10);
@ -653,6 +727,338 @@ void RemoveDuplicatesTest::removeDuplicatesMeshDataAttributeless() {
"MeshTools::removeDuplicates(): can't remove duplicates in an attributeless mesh\n");
}
void RemoveDuplicatesTest::removeDuplicatesMeshDataFuzzy() {
auto&& data = RemoveDuplicatesMeshDataFuzzyData[testCaseInstanceId()];
setTestCaseDescription(data.name);
/* Deliberately not owned and not interleaved to verify that the function
will handle this */
struct Vertex {
Short ints[10][2]{
{15, 2},
{15, 2},
{15, 2},
{2365, -2},
{-2, 2365},
{-2, 2365},
{2365, -2},
{37, 0},
{37, 0},
{37, 0}
};
Math::Vector<6, Float> floats[10]{
{0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f},
/* This one gets collapsed to the above */
{0.0f, 1.0f, 0.0f, 1.0f - Math::TypeTraits<Float>::epsilon()/4, 0.0f, 0.0f},
/* This one not */
{0.0f, 1.0f, 0.0f, 1.0f - Math::TypeTraits<Float>::epsilon()*4, 0.0f, 0.0f},
/* These are bit-equivalent, but not all get collapsed because the
ints are different */
{0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
{0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
{0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
{0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
/* Same as above, only at a smaller scale */
{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f + Math::TypeTraits<Float>::epsilon()/2, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f + Math::TypeTraits<Float>::epsilon()*2, 0.0f, 0.0f, 0.0f},
};
UnsignedByte intsAgain[10]{
33,
33,
33,
15,
15,
15,
17,
223,
223,
223
};
UnsignedInt objectId[10]{ 15, 15, 15, 15, 15, 15, 15, 15, 15, 15 };
} vertexData[1];
const UnsignedShort indexData[]{1, 2, 5, 9, 7, 6, 4, 7, 5, 0, 3, 8, 3};
/* Scale and offset the floats */
for(Math::Vector<6, Float>& f: vertexData->floats)
f = f*data.scale + Math::Vector<6, Float>{data.offset};
/* Create a combined attribute list */
Containers::Array<Trade::MeshAttributeData> attributes;
arrayAppend(attributes, Trade::MeshAttributeData{
Trade::meshAttributeCustom(15), VertexFormat::Short, 0, 10, 4, 2});
for(const Trade::MeshAttributeData& a: data.attributes)
arrayAppend(attributes, Trade::MeshAttributeData{
a.name(), a.format(), offsetof(Vertex, floats) + a.offset({}), 10, a.stride(), a.arraySize()});
arrayAppend(attributes, Trade::MeshAttributeData{
Trade::meshAttributeCustom(16), VertexFormat::UnsignedByte, offsetof(Vertex, intsAgain), 10, 1});
arrayAppend(attributes, Trade::MeshAttributeData{
Trade::MeshAttribute::ObjectId, VertexFormat::UnsignedInt, offsetof(Vertex, objectId), 10, 4});
Containers::ArrayView<const void> indexView;
Trade::MeshIndexData indices;
if(data.indexed) {
indexView = indexData;
indices = Trade::MeshIndexData{indexData};
}
Trade::MeshData mesh{MeshPrimitive::Lines,
{}, indexView, indices,
{}, vertexData, std::move(attributes)};
Trade::MeshData unique = MeshTools::removeDuplicatesFuzzy(mesh,
data.epsilon);
CORRADE_COMPARE(unique.primitive(), MeshPrimitive::Lines);
CORRADE_VERIFY(unique.isIndexed());
if(data.indexed)
CORRADE_COMPARE(unique.indexCount(), mesh.indexCount());
else
CORRADE_COMPARE(unique.indexCount(), mesh.vertexCount());
CORRADE_COMPARE(unique.indexType(), MeshIndexType::UnsignedInt);
CORRADE_COMPARE(unique.attributeCount(), 3 + data.attributes.size());
/* Verify that all attributes have expected metadata and are interleaved */
for(UnsignedInt i = 0; i != unique.attributeCount(); ++i) {
CORRADE_ITERATION(i);
CORRADE_COMPARE(unique.attributeStride(i), 4 + 6*sizeof(float) + 5);
}
CORRADE_COMPARE(unique.attributeFormat(0), VertexFormat::Short);
CORRADE_COMPARE(unique.attributeOffset(0), 0);
CORRADE_COMPARE(unique.attributeName(0), Trade::meshAttributeCustom(15));
CORRADE_COMPARE(unique.attributeArraySize(0), 2);
for(std::size_t i = 0; i != data.attributes.size(); ++i) {
CORRADE_ITERATION(i);
CORRADE_COMPARE(unique.attributeFormat(1 + i), data.attributes[i].format());
CORRADE_COMPARE(unique.attributeOffset(1 + i), 4 + data.attributes[i].offset({}));
CORRADE_COMPARE(unique.attributeName(1 + i), data.attributes[i].name());
CORRADE_COMPARE(unique.attributeArraySize(1 + i), data.attributes[i].arraySize());
}
CORRADE_COMPARE(unique.attributeFormat(1 + data.attributes.size()), VertexFormat::UnsignedByte);
CORRADE_COMPARE(unique.attributeOffset(1 + data.attributes.size()), 4 + 6*sizeof(Float));
CORRADE_COMPARE(unique.attributeName(1 + data.attributes.size()), Trade::meshAttributeCustom(16));
CORRADE_COMPARE(unique.attributeFormat(2 + data.attributes.size()), VertexFormat::UnsignedInt);
CORRADE_COMPARE(unique.attributeOffset(2 + data.attributes.size()), 5 + 6*sizeof(Float));
CORRADE_COMPARE(unique.attributeName(2 + data.attributes.size()), Trade::MeshAttribute::ObjectId);
/* The data differ depending on how much is actually removed */
if(data.vertexCount == 7) {
if(data.indexed) CORRADE_COMPARE_AS(unique.indices<UnsignedInt>(),
Containers::arrayView<UnsignedInt>({0, 1, 3, 6, 5, 4, 3, 5, 3, 0, 2, 5, 2}),
TestSuite::Compare::Container);
else CORRADE_COMPARE_AS(unique.indices<UnsignedInt>(),
Containers::arrayView<UnsignedInt>({0, 0, 1, 2, 3, 3, 4, 5, 5, 6}),
TestSuite::Compare::Container);
/* Compare the integer data through the attribute API */
CORRADE_COMPARE_AS((Containers::arrayCast<1, const Vector2s>(unique.attribute<Short[]>(Trade::meshAttributeCustom(15)))),
Containers::arrayView<Vector2s>({
{15, 2},
{15, 2},
{2365, -2},
{-2, 2365},
{2365, -2},
{37, 0},
{37, 0}
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(unique.attribute<UnsignedByte>(Trade::meshAttributeCustom(16)),
Containers::arrayView<UnsignedByte>({
33,
33,
15,
15,
17,
223,
223
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(unique.attribute<UnsignedInt>(Trade::MeshAttribute::ObjectId),
Containers::arrayView<UnsignedInt>({
15, 15, 15, 15, 15, 15, 15
}), TestSuite::Compare::Container);
/* Compare the float/double data as a single block independently of the
attribute layout */
Math::Vector<6, Float> expectedFloats[]{
{0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 0.0f},
{0.0f, 1.0f, 0.0f, 1.0f - Math::TypeTraits<Float>::epsilon()*4, 0.0f, 0.0f},
{0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
{0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
{0.5f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f},
{0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f},
{0.0f, 0.0f, 0.0f + Math::TypeTraits<Float>::epsilon()*2, 0.0f, 0.0f, 0.0f},
};
for(Math::Vector<6, Float>& f: expectedFloats)
f = f*data.scale + Math::Vector<6, Float>{data.offset};
/** @todo i need some feature like "gimme just the top-level dimension" */
Containers::StridedArrayView1D<const char> floats{unique.vertexData(),
unique.vertexData() + unique.attributeOffset(1),
unique.vertexCount(), unique.attributeStride(1)};
CORRADE_COMPARE_AS((Containers::arrayCast<const Math::Vector<6, Float>>(floats)),
Containers::arrayView(expectedFloats),
TestSuite::Compare::Container);
} else if(data.vertexCount == 9) {
if(data.indexed) CORRADE_COMPARE_AS(unique.indices<UnsignedInt>(),
Containers::arrayView<UnsignedInt>({1, 2, 4, 8, 6, 5, 4, 6, 4, 0, 3, 7, 3}),
TestSuite::Compare::Container);
else CORRADE_COMPARE_AS(unique.indices<UnsignedInt>(),
Containers::arrayView<UnsignedInt>({0, 1, 2, 3, 4, 4, 5, 6, 7, 8}),
TestSuite::Compare::Container);
/* Not testing the rest, it's verified well enough in the other cases */
}
}
void RemoveDuplicatesTest::removeDuplicatesMeshDataFuzzyDouble() {
/* Deliberately not owned and not interleaved to verify that the function
will handle this */
const struct Vertex {
/* Epsilon enlarged a lot to ensure the cell size isn't too small for
the grid size to fit into 32 bit std::size_t */
Math::Vector<3, Double> doubles[10]{
{110.0, 100.0, 100.0},
/* This one gets collapsed to the above */
{110.0 - 250000*Math::TypeTraits<Double>::epsilon()/2, 100.0, 100.0},
/* This one not */
{110.0 - 250000*Math::TypeTraits<Double>::epsilon()*2, 100.0, 100.0},
/* These are bit-equivalent, but not all get collapsed because the
ints are different */
{100.0, 100.0, 110.0},
{100.0, 100.0, 110.0},
{100.0, 100.0, 110.0},
{100.0, 100.0, 110.0},
/* Same as above, only at a smaller scale */
{100.0, 100.0, 100.0},
{100.0, 100.0, 100.0 + 250000*Math::TypeTraits<Double>::epsilon()/2},
{100.0, 100.0, 100.0 + 250000*Math::TypeTraits<Double>::epsilon()*2},
};
UnsignedByte objectId[10]{
33,
33,
33,
15,
16,
15,
17,
223,
223,
223
};
} vertexData[1];
Trade::MeshData mesh{MeshPrimitive::Points,
{}, vertexData, {
Trade::MeshAttributeData{Trade::meshAttributeCustom(10),
VertexFormat::Double, 0, 10, 3*sizeof(Double)},
Trade::MeshAttributeData{Trade::meshAttributeCustom(11),
VertexFormat::Double, sizeof(Double), 10, 3*sizeof(Double), 2},
Trade::MeshAttributeData{
Trade::MeshAttribute::ObjectId, VertexFormat::UnsignedByte, offsetof(Vertex, objectId), 10, 1}
}};
Trade::MeshData unique = MeshTools::removeDuplicatesFuzzy(mesh,
Math::TypeTraits<Float>::epsilon(),
/* Epsilon enlarged a lot to ensure the cell size isn't too small for
the grid size to fit into 32 bit std::size_t */
25000*Math::TypeTraits<Double>::epsilon());
CORRADE_COMPARE(unique.primitive(), MeshPrimitive::Points);
CORRADE_VERIFY(unique.isIndexed());
CORRADE_COMPARE(unique.indexCount(), mesh.vertexCount());
CORRADE_COMPARE(unique.indexType(), MeshIndexType::UnsignedInt);
CORRADE_COMPARE(unique.attributeCount(), 3);
/* Verify that all attributes have expected metadata and are interleaved */
for(UnsignedInt i = 0; i != unique.attributeCount(); ++i) {
CORRADE_ITERATION(i);
CORRADE_COMPARE(unique.attributeStride(i), 3*sizeof(Double) + 1);
}
CORRADE_COMPARE(unique.attributeFormat(0), VertexFormat::Double);
CORRADE_COMPARE(unique.attributeOffset(0), 0);
CORRADE_COMPARE(unique.attributeName(0), Trade::meshAttributeCustom(10));
CORRADE_COMPARE(unique.attributeFormat(1), VertexFormat::Double);
CORRADE_COMPARE(unique.attributeOffset(1), sizeof(Double));
CORRADE_COMPARE(unique.attributeName(1), Trade::meshAttributeCustom(11));
CORRADE_COMPARE(unique.attributeArraySize(1), 2);
CORRADE_COMPARE(unique.attributeFormat(2), VertexFormat::UnsignedByte);
CORRADE_COMPARE(unique.attributeOffset(2), 3*sizeof(Double));
CORRADE_COMPARE(unique.attributeName(2), Trade::MeshAttribute::ObjectId);
CORRADE_COMPARE_AS(unique.indices<UnsignedInt>(),
Containers::arrayView<UnsignedInt>({0, 0, 1, 2, 3, 2, 4, 5, 5, 6}),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(unique.attribute<UnsignedByte>(Trade::MeshAttribute::ObjectId),
Containers::arrayView<UnsignedByte>({
33,
33,
15,
16,
17,
223,
223
}), TestSuite::Compare::Container);
/* Compare the float/double data as a single block independently of the
attribute layout */
Math::Vector<3, Double> expectedFloats[]{
{110.0, 100.0, 100.0},
{110.0 - 250000*Math::TypeTraits<Double>::epsilon()*2, 100.0, 100.0},
{100.0, 100.0, 110.0},
{100.0, 100.0, 110.0},
{100.0, 100.0, 110.0},
{100.0, 100.0, 100.0},
{100.0, 100.0, 100.0 + 250000*Math::TypeTraits<Double>::epsilon()*2},
};
/** @todo i need some feature like "gimme just the top-level dimension" */
Containers::StridedArrayView1D<const char> floats{unique.vertexData(),
unique.vertexData() + unique.attributeOffset(0),
unique.vertexCount(), unique.attributeStride(0)};
CORRADE_COMPARE_AS((Containers::arrayCast<const Math::Vector<3, Double>>(floats)),
Containers::arrayView(expectedFloats),
TestSuite::Compare::Container);
}
void RemoveDuplicatesTest::removeDuplicatesMeshDataFuzzyAttributeless() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
std::ostringstream out;
Error redirectError{&out};
MeshTools::removeDuplicatesFuzzy(Trade::MeshData{MeshPrimitive::Points, 10});
CORRADE_COMPARE(out.str(),
"MeshTools::removeDuplicatesFuzzy(): can't remove duplicates in an attributeless mesh\n");
}
void RemoveDuplicatesTest::removeDuplicatesMeshDataFuzzyImplementationSpecific() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
std::ostringstream out;
Error redirectError{&out};
CORRADE_EXPECT_FAIL("The function currently uses concatenate() to make data owned, which fails earlier with a different (and confusing) assert message.");
CORRADE_VERIFY(false);
// MeshTools::removeDuplicatesFuzzy(Trade::MeshData{MeshPrimitive::Points,
// nullptr, {Trade::MeshAttributeData{Trade::MeshAttribute::Position,
// vertexFormatWrap(0x1234), nullptr}}});
// CORRADE_COMPARE(out.str(),
// "MeshTools::removeDuplicatesFuzzy(): can't remove duplicates in an implementation-specific format 0x1234\n");
}
void RemoveDuplicatesTest::soakTest() {
/* Array of 100 unique items with 10 duplicates each, randomly shuffled */
UnsignedInt data[1000];

Loading…
Cancel
Save