Browse Source

MeshTools: new utilities for filtering MeshData attribute lists.

Apmong other things where it's useful for end users as a more convenient
alternative to recreating the MeshData by hand, I need to use this
inside the transform() utilities to preserve all index buffer properties
without copypasting nasty code everywhere.
pull/547/head
Vladimír Vondruš 4 years ago
parent
commit
9daadd79fa
  1. 3
      doc/changelog.dox
  2. 2
      src/Magnum/MeshTools/CMakeLists.txt
  3. 216
      src/Magnum/MeshTools/FilterAttributes.cpp
  4. 140
      src/Magnum/MeshTools/FilterAttributes.h
  5. 2
      src/Magnum/MeshTools/Test/CMakeLists.txt
  6. 570
      src/Magnum/MeshTools/Test/FilterAttributesTest.cpp

3
doc/changelog.dox

@ -158,6 +158,9 @@ See also:
- Added @ref MeshTools::generateQuadIndices() for quad triangulation
including non-convex and non-planar quads
- New @ref MeshTools::filterOnlyAttributes() and
@ref MeshTools::filterExceptAttributes() utilities for filtering mesh data
attribute lists
- New family of @ref MeshTools::transform2D(), @ref MeshTools::transform3D()
and @ref MeshTools::transformTextureCoordinates2D() APIs for converting
positions, normals, tangents, bitangents and texture coordinates directly

2
src/Magnum/MeshTools/CMakeLists.txt

@ -33,6 +33,7 @@ set(MagnumMeshTools_GracefulAssert_SRCS
CompressIndices.cpp
Concatenate.cpp
Duplicate.cpp
FilterAttributes.cpp
FlipNormals.cpp
GenerateIndices.cpp
GenerateNormals.cpp
@ -46,6 +47,7 @@ set(MagnumMeshTools_HEADERS
CompressIndices.h
Concatenate.h
Duplicate.h
FilterAttributes.h
FlipNormals.h
GenerateIndices.h
GenerateNormals.h

216
src/Magnum/MeshTools/FilterAttributes.cpp

@ -0,0 +1,216 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 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 "FilterAttributes.h"
#include <Corrade/Containers/ArrayView.h>
#include <Corrade/Containers/GrowableArray.h>
#include "Magnum/Trade/MeshData.h"
namespace Magnum { namespace MeshTools {
namespace {
bool hasAttribute(const Containers::ArrayView<const Trade::MeshAttribute> attributes, const Trade::MeshAttribute attribute) {
for(const Trade::MeshAttribute i: attributes)
if(i == attribute) return true;
return false;
}
/** @todo drop this insanity in favor of a BitArray */
bool hasAttribute(const Containers::ArrayView<const UnsignedInt> attributes, const UnsignedInt attribute) {
for(const UnsignedInt i: attributes)
if(i == attribute) return true;
return false;
}
}
Trade::MeshData filterOnlyAttributes(const Trade::MeshData& data, const Containers::ArrayView<const Trade::MeshAttribute> attributes) {
/* Not asserting here for existence of attributes since that'd be another
O(n^2) operation */
/** @todo but that's not consistent with the ID-based variant, or maybe do
this via a BitArray in the second loop and check that all attributes
got referenced afterwards? */
/* Pick just attributes from the list */
Containers::Array<Trade::MeshAttributeData> filtered;
arrayReserve(filtered, data.attributeCount());
for(UnsignedInt i = 0; i != data.attributeCount(); ++i) {
if(hasAttribute(attributes, data.attributeName(i)))
arrayAppend(filtered, data.attributeData(i));
}
/* Convert back to a default deleter to make this usable in plugins */
arrayShrink(filtered, DefaultInit);
/* Can't do just Trade::MeshIndexData{data.indices()} as that would discard
implementation-specific types. And can't do
Trade::meshIndexData{data.indexType(), view}
because asking for index type would assert on non-indexed meshes. */
Trade::MeshIndexData indices;
if(data.isIndexed()) indices = Trade::MeshIndexData{
data.indexType(),
Containers::StridedArrayView1D<const void>{
data.indexData(),
data.indexData().data() + data.indexOffset(),
data.indexCount(),
data.indexStride()}};
return Trade::MeshData{data.primitive(),
{}, data.indexData(), indices,
{}, data.vertexData(), std::move(filtered),
data.vertexCount()};
}
Trade::MeshData filterOnlyAttributes(const Trade::MeshData& data, std::initializer_list<Trade::MeshAttribute> attributes) {
return filterOnlyAttributes(data, Containers::arrayView(attributes));
}
Trade::MeshData filterOnlyAttributes(const Trade::MeshData& data, const Containers::ArrayView<const UnsignedInt> attributes) {
#ifndef CORRADE_NO_ASSERT
for(const UnsignedInt i: attributes) CORRADE_ASSERT(i < data.attributeCount(),
"MeshTools::filterOnlyAttributes(): index" << i << "out of range for" << data.attributeCount() << "attributes",
(Trade::MeshData{MeshPrimitive{}, 0}));
#endif
/* Pick just attributes from the list */
Containers::Array<Trade::MeshAttributeData> filtered;
arrayReserve(filtered, data.attributeCount());
for(UnsignedInt i = 0; i != data.attributeCount(); ++i) {
if(hasAttribute(attributes, i))
arrayAppend(filtered, data.attributeData(i));
}
/* Convert back to a default deleter to make this usable in plugins */
arrayShrink(filtered, DefaultInit);
/* Can't do just Trade::MeshIndexData{data.indices()} as that would discard
implementation-specific types. And can't do
Trade::meshIndexData{data.indexType(), view}
because asking for index type would assert on non-indexed meshes. */
Trade::MeshIndexData indices;
if(data.isIndexed()) indices = Trade::MeshIndexData{
data.indexType(),
Containers::StridedArrayView1D<const void>{
data.indexData(),
data.indexData().data() + data.indexOffset(),
data.indexCount(),
data.indexStride()}};
return Trade::MeshData{data.primitive(),
{}, data.indexData(), indices,
{}, data.vertexData(), std::move(filtered),
data.vertexCount()};
}
Trade::MeshData filterOnlyAttributes(const Trade::MeshData& data, std::initializer_list<UnsignedInt> attributes) {
return filterOnlyAttributes(data, Containers::arrayView(attributes));
}
Trade::MeshData filterExceptAttributes(const Trade::MeshData& data, const Containers::ArrayView<const Trade::MeshAttribute> attributes) {
/* Not asserting here for existence of attributes since that'd be another
O(n^2) operation */
/** @todo but that's not consistent with the ID-based variant, or maybe do
this via a BitArray in the second loop and check that all attributes
got referenced afterwards? */
/* Pick just attributes from the list */
Containers::Array<Trade::MeshAttributeData> filtered;
arrayReserve(filtered, data.attributeCount());
for(UnsignedInt i = 0; i != data.attributeCount(); ++i) {
if(!hasAttribute(attributes, data.attributeName(i)))
arrayAppend(filtered, data.attributeData(i));
}
/* Convert back to a default deleter to make this usable in plugins */
arrayShrink(filtered, DefaultInit);
/* Can't do just Trade::MeshIndexData{data.indices()} as that would discard
implementation-specific types. And can't do
Trade::meshIndexData{data.indexType(), view}
because asking for index type would assert on non-indexed meshes. */
Trade::MeshIndexData indices;
if(data.isIndexed()) indices = Trade::MeshIndexData{
data.indexType(),
Containers::StridedArrayView1D<const void>{
data.indexData(),
data.indexData().data() + data.indexOffset(),
data.indexCount(),
data.indexStride()}};
return Trade::MeshData{data.primitive(),
{}, data.indexData(), indices,
{}, data.vertexData(), std::move(filtered),
data.vertexCount()};
}
Trade::MeshData filterExceptAttributes(const Trade::MeshData& data, std::initializer_list<Trade::MeshAttribute> attributes) {
return filterExceptAttributes(data, Containers::arrayView(attributes));
}
Trade::MeshData filterExceptAttributes(const Trade::MeshData& data, const Containers::ArrayView<const UnsignedInt> attributes) {
#ifndef CORRADE_NO_ASSERT
for(const UnsignedInt i: attributes) CORRADE_ASSERT(i < data.attributeCount(),
"MeshTools::filterExceptAttributes(): index" << i << "out of range for" << data.attributeCount() << "attributes",
(Trade::MeshData{MeshPrimitive{}, 0}));
#endif
/* Pick just attributes from the list */
Containers::Array<Trade::MeshAttributeData> filtered;
arrayReserve(filtered, data.attributeCount());
for(UnsignedInt i = 0; i != data.attributeCount(); ++i) {
if(!hasAttribute(attributes, i))
arrayAppend(filtered, data.attributeData(i));
}
/* Convert back to a default deleter to make this usable in plugins */
arrayShrink(filtered, DefaultInit);
/* Can't do just Trade::MeshIndexData{data.indices()} as that would discard
implementation-specific types. And can't do
Trade::meshIndexData{data.indexType(), view}
because asking for index type would assert on non-indexed meshes. */
Trade::MeshIndexData indices;
if(data.isIndexed()) indices = Trade::MeshIndexData{
data.indexType(),
Containers::StridedArrayView1D<const void>{
data.indexData(),
data.indexData().data() + data.indexOffset(),
data.indexCount(),
data.indexStride()}};
return Trade::MeshData{data.primitive(),
{}, data.indexData(), indices,
{}, data.vertexData(), std::move(filtered),
data.vertexCount()};
}
Trade::MeshData filterExceptAttributes(const Trade::MeshData& data, std::initializer_list<UnsignedInt> attributes) {
return filterExceptAttributes(data, Containers::arrayView(attributes));
}
}}

140
src/Magnum/MeshTools/FilterAttributes.h

@ -0,0 +1,140 @@
#ifndef Magnum_MeshTools_FilterAttributes_h
#define Magnum_MeshTools_FilterAttributes_h
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 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.
*/
/** @file
* @brief Function @ref Magnum::MeshTools::filterOnlyAttributes(), @ref Magnum::MeshTools::filterExceptAttributes()
* @m_since_latest
*/
#include <initializer_list>
#include "Magnum/MeshTools/visibility.h"
#include "Magnum/Trade/Trade.h"
namespace Magnum { namespace MeshTools {
/**
@brief Filter a mesh to contain only the selected subset of named attributes
@m_since_latest
Returns a non-owning reference to the vertex and index buffer from @p data with
only the attributes that are listed in @p attributes. The index buffer, if
present, is left untouched. Attributes from the list that are not present in
@p data are skipped. All duplicates of a listed attribute are kept --- if you
want a different behavior, use the @ref filterOnlyAttributes(const Trade::MeshData&, Containers::ArrayView<const UnsignedInt>)
overload and pick attributes by their IDs instead.
This function only operates on the attribute metadata --- if you'd like to have
the vertex data repacked to contain just the remaining attributes as well, pass
the output to @ref interleave(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>, InterleaveFlags) "interleave()"
without @ref InterleaveFlag::PreserveInterleavedAttributes set.
@see @ref reference()
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData filterOnlyAttributes(const Trade::MeshData& data, Containers::ArrayView<const Trade::MeshAttribute> attributes);
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData filterOnlyAttributes(const Trade::MeshData& data, std::initializer_list<Trade::MeshAttribute> attributes);
/**
@brief Filter a mesh to contain only the selected subset of attributes
@m_since_latest
Returns a non-owning reference to the vertex and index buffer from @p data with
only the attribute IDs listed in @p attributes. IDs specified more than once
don't result in given attribute being added multiple times. The index buffer,
if present, is left untouched. All attribute IDs are expected to be smaller
than @ref Trade::MeshData::attributeCount() const.
This function only operates on the attribute metadata --- if you'd like to have
the vertex data repacked to contain just the remaining attributes as well, pass
the output to @ref interleave(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>, InterleaveFlags) "interleave()"
without @ref InterleaveFlag::PreserveInterleavedAttributes set.
@see @ref reference()
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData filterOnlyAttributes(const Trade::MeshData& data, Containers::ArrayView<const UnsignedInt> attributes);
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData filterOnlyAttributes(const Trade::MeshData& data, std::initializer_list<UnsignedInt> attributes);
/**
@brief Filter a mesh to contain everything except the selected subset of named attributes
@m_since_latest
Returns a non-owning reference to the vertex and index buffer from @p data with
only the attributes that are not listed in @p attributes. The index buffer, if
present, is left untouched. Attributes from the list that are not present in
@p data are skipped. All duplicates of a listed attribute are removed --- if
you want a different behavior, use the @ref filterExceptAttributes(const Trade::MeshData&, Containers::ArrayView<const UnsignedInt>)
overload and pick attributes by their IDs instead. If @p attributes is empty,
the behavior is equivalent to @ref reference().
This function only operates on the attribute metadata --- if you'd like to have
the vertex data repacked to contain just the remaining attributes as well, pass
the output to @ref interleave(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>, InterleaveFlags) "interleave()"
without @ref InterleaveFlag::PreserveInterleavedAttributes set.
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData filterExceptAttributes(const Trade::MeshData& data, Containers::ArrayView<const Trade::MeshAttribute> attributes);
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData filterExceptAttributes(const Trade::MeshData& data, std::initializer_list<Trade::MeshAttribute> attributes);
/**
@brief Filter a mesh to contain everything except the selected subset of attributes
@m_since_latest
Returns a non-owning reference to the vertex and index buffer from @p data with
only the attribute IDs that are not listed in @p attributes. IDs specified
multiple times behave like if specified just once. The index buffer, if
present, is left untouched. All attribute IDs are expected to be smaller than
@ref Trade::MeshData::attributeCount() const. If @p attributes is empty, the
behavior is equivalent to @ref reference().
This function only operates on the attribute metadata --- if you'd like to have
the vertex data repacked to contain just the remaining attributes as well, pass
the output to @ref interleave(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>, InterleaveFlags) "interleave()"
without @ref InterleaveFlag::PreserveInterleavedAttributes set.
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData filterExceptAttributes(const Trade::MeshData& data, Containers::ArrayView<const UnsignedInt> attributes);
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData filterExceptAttributes(const Trade::MeshData& data, std::initializer_list<UnsignedInt> attributes);
}}
#endif

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

@ -27,6 +27,7 @@ corrade_add_test(MeshToolsCombineTest CombineTest.cpp LIBRARIES MagnumMeshToolsT
corrade_add_test(MeshToolsCompressIndicesTest CompressIndicesTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsConcatenateTest ConcatenateTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsFilterAttributesTest FilterAttributesTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsFlipNormalsTest FlipNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsGenerateIndicesTest GenerateIndicesTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsGenerateNormalsTest GenerateNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib MagnumPrimitives)
@ -51,6 +52,7 @@ set_target_properties(
MeshToolsCompressIndicesTest
MeshToolsConcatenateTest
MeshToolsDuplicateTest
MeshToolsFilterAttributesTest
MeshToolsFlipNormalsTest
MeshToolsGenerateIndicesTest
MeshToolsGenerateNormalsTest

570
src/Magnum/MeshTools/Test/FilterAttributesTest.cpp

@ -0,0 +1,570 @@
/*
This file is part of Magnum.
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019,
2020, 2021 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 <sstream>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/Utility/DebugStl.h>
#include "Magnum/Math/Vector4.h"
#include "Magnum/MeshTools/FilterAttributes.h"
#include "Magnum/Trade/MeshData.h"
namespace Magnum { namespace MeshTools { namespace Test { namespace {
struct FilterAttributesTest: TestSuite::Tester {
explicit FilterAttributesTest();
void filterOnlyAttributeNames();
void filterOnlyAttributeNamesNoIndexData();
void filterOnlyAttributeNamesNoAttributeData();
void filterOnlyAttributeIds();
void filterOnlyAttributeIdsOutOfBounds();
void filterOnlyAttributeIdsNoIndexData();
void filterOnlyAttributeIdsNoAttributeData();
void filterExceptAttributeNames();
void filterExceptAttributeNamesNoIndexData();
void filterExceptAttributeNamesNoAttributeData();
void filterExceptAttributeIds();
void filterExceptAttributeIdsOutOfBounds();
void filterExceptAttributeIdsNoIndexData();
void filterExceptAttributeIdsNoAttributeData();
};
const struct {
const char* name;
MeshIndexType indexType;
} ImplementationSpecificIndexTypeData[]{
{"", MeshIndexType::UnsignedShort},
{"implementation-specific index type", meshIndexTypeWrap(0xcaca)}
};
FilterAttributesTest::FilterAttributesTest() {
addInstancedTests({&FilterAttributesTest::filterOnlyAttributeNames},
Containers::arraySize(ImplementationSpecificIndexTypeData));
addTests({&FilterAttributesTest::filterOnlyAttributeNamesNoIndexData,
&FilterAttributesTest::filterOnlyAttributeNamesNoAttributeData});
addInstancedTests({&FilterAttributesTest::filterOnlyAttributeIds},
Containers::arraySize(ImplementationSpecificIndexTypeData));
addTests({&FilterAttributesTest::filterOnlyAttributeIdsOutOfBounds,
&FilterAttributesTest::filterOnlyAttributeIdsNoIndexData,
&FilterAttributesTest::filterOnlyAttributeIdsNoAttributeData});
addInstancedTests({&FilterAttributesTest::filterExceptAttributeNames},
Containers::arraySize(ImplementationSpecificIndexTypeData));
addTests({&FilterAttributesTest::filterExceptAttributeNamesNoIndexData,
&FilterAttributesTest::filterExceptAttributeNamesNoAttributeData});
addInstancedTests({&FilterAttributesTest::filterExceptAttributeIds},
Containers::arraySize(ImplementationSpecificIndexTypeData));
addTests({&FilterAttributesTest::filterExceptAttributeIdsOutOfBounds,
&FilterAttributesTest::filterExceptAttributeIdsNoIndexData,
&FilterAttributesTest::filterExceptAttributeIdsNoAttributeData});
}
struct Vertex {
Vector3 position;
Vector4 tangent;
Vector2 textureCoordinates1, textureCoordinates2;
};
void FilterAttributesTest::filterOnlyAttributeNames() {
auto&& data = ImplementationSpecificIndexTypeData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Containers::Array<char> indexData{5*sizeof(UnsignedShort)};
Containers::StridedArrayView1D<UnsignedShort> indices = Containers::arrayCast<UnsignedShort>(indexData);
Containers::Array<char> vertexData{3*sizeof(Vertex)};
Containers::StridedArrayView1D<Vertex> vertices = Containers::arrayCast<Vertex>(vertexData);
Trade::MeshData mesh{MeshPrimitive::TriangleStrip,
std::move(indexData), Trade::MeshIndexData{data.indexType, indices},
std::move(vertexData), {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, vertices.slice(&Vertex::position)},
Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, vertices.slice(&Vertex::tangent)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, vertices.slice(&Vertex::textureCoordinates1)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, vertices.slice(&Vertex::textureCoordinates2)},
}};
Trade::MeshData filtered = filterOnlyAttributes(mesh, {
Trade::MeshAttribute::Position,
Trade::MeshAttribute::Normal, /* not present, ignored */
Trade::MeshAttribute::TextureCoordinates, /* present twice */
});
CORRADE_COMPARE(filtered.primitive(), MeshPrimitive::TriangleStrip);
CORRADE_VERIFY(filtered.isIndexed());
CORRADE_COMPARE(filtered.indexCount(), 5);
CORRADE_COMPARE(filtered.indexType(), data.indexType);
CORRADE_COMPARE(filtered.indexData().data(), indices.data());
CORRADE_COMPARE(filtered.indexDataFlags(), Trade::DataFlags{});
CORRADE_COMPARE(filtered.vertexCount(), 3);
CORRADE_COMPARE(filtered.vertexData().data(), vertices.data());
CORRADE_COMPARE(filtered.vertexDataFlags(), Trade::DataFlags{});
/* Testing just the offset if it matches expectations, the
MeshAttributeData is copied directly so no metadata should get lost */
CORRADE_COMPARE(filtered.attributeCount(), 3);
CORRADE_COMPARE(filtered.attributeName(0), Trade::MeshAttribute::Position);
CORRADE_COMPARE(filtered.attributeOffset(0), offsetof(Vertex, position));
CORRADE_COMPARE(filtered.attributeName(1), Trade::MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(filtered.attributeOffset(1), offsetof(Vertex, textureCoordinates1));
CORRADE_COMPARE(filtered.attributeName(2), Trade::MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(filtered.attributeOffset(2), offsetof(Vertex, textureCoordinates2));
/* The attribute data should not be a growable array to make this usable in
plugins */
Containers::Array<Trade::MeshAttributeData> attributeData = filtered.releaseAttributeData();
CORRADE_VERIFY(!attributeData.deleter());
}
void FilterAttributesTest::filterOnlyAttributeNamesNoIndexData() {
/* A trivial subset of filterOnlyAttributeNames() testing it doesn't blow
up if the mesh is not indexed */
Containers::Array<char> vertexData{3*sizeof(Vertex)};
Containers::StridedArrayView1D<Vertex> vertices = Containers::arrayCast<Vertex>(vertexData);
Trade::MeshData mesh{MeshPrimitive::TriangleFan,
std::move(vertexData), {
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, vertices.slice(&Vertex::textureCoordinates1)}
}};
Trade::MeshData filtered = filterOnlyAttributes(mesh, {
Trade::MeshAttribute::TextureCoordinates
});
CORRADE_COMPARE(filtered.primitive(), MeshPrimitive::TriangleFan);
CORRADE_VERIFY(!filtered.isIndexed());
/* Consistent with behavior in reference() for index-less meshes */
CORRADE_COMPARE(filtered.indexDataFlags(), Trade::DataFlags{});
CORRADE_COMPARE(filtered.vertexCount(), 3);
CORRADE_COMPARE(filtered.vertexData().data(), vertices.data());
CORRADE_COMPARE(filtered.vertexDataFlags(), Trade::DataFlags{});
/* Testing just the offset if it matches expectations, the
MeshAttributeData is copied directly so no metadata should get lost */
CORRADE_COMPARE(filtered.attributeCount(), 1);
CORRADE_COMPARE(filtered.attributeName(0), Trade::MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(filtered.attributeOffset(0), offsetof(Vertex, textureCoordinates1));
}
void FilterAttributesTest::filterOnlyAttributeNamesNoAttributeData() {
Containers::Array<char> indexData{5*sizeof(UnsignedShort)};
Containers::StridedArrayView1D<UnsignedShort> indices = Containers::arrayCast<UnsignedShort>(indexData);
Trade::MeshData mesh{MeshPrimitive::Points,
std::move(indexData), Trade::MeshIndexData{indices}, 15};
Trade::MeshData filtered = filterOnlyAttributes(mesh, {
Trade::MeshAttribute::Position
});
CORRADE_COMPARE(filtered.primitive(), MeshPrimitive::Points);
CORRADE_VERIFY(filtered.isIndexed());
CORRADE_COMPARE(filtered.indexCount(), 5);
CORRADE_COMPARE(filtered.indexType(), MeshIndexType::UnsignedShort);
CORRADE_COMPARE(filtered.indexData().data(), indices.data());
CORRADE_COMPARE(filtered.indexDataFlags(), Trade::DataFlags{});
/* The vertex count should get preserved even if there are no attributes */
CORRADE_COMPARE(filtered.vertexCount(), 15);
/* Consistent with behavior in reference() for vertex-less meshes */
CORRADE_COMPARE(filtered.vertexDataFlags(), Trade::DataFlags{});
CORRADE_COMPARE(filtered.attributeCount(), 0);
}
void FilterAttributesTest::filterOnlyAttributeIds() {
auto&& data = ImplementationSpecificIndexTypeData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Containers::Array<char> indexData{5*sizeof(UnsignedShort)};
Containers::StridedArrayView1D<UnsignedShort> indices = Containers::arrayCast<UnsignedShort>(indexData);
Containers::Array<char> vertexData{3*sizeof(Vertex)};
Containers::StridedArrayView1D<Vertex> vertices = Containers::arrayCast<Vertex>(vertexData);
Trade::MeshData mesh{MeshPrimitive::TriangleStrip,
std::move(indexData), Trade::MeshIndexData{data.indexType, indices},
std::move(vertexData), {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, vertices.slice(&Vertex::position)},
Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, vertices.slice(&Vertex::tangent)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, vertices.slice(&Vertex::textureCoordinates1)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, vertices.slice(&Vertex::textureCoordinates2)},
}};
Trade::MeshData filtered = filterOnlyAttributes(mesh, {
/* The attribute 1 is specified twice, but that won't result in the
same attribute being added twice */
1, 1, 3
});
CORRADE_COMPARE(filtered.primitive(), MeshPrimitive::TriangleStrip);
CORRADE_VERIFY(filtered.isIndexed());
CORRADE_COMPARE(filtered.indexCount(), 5);
CORRADE_COMPARE(filtered.indexType(), data.indexType);
CORRADE_COMPARE(filtered.indexData().data(), indices.data());
CORRADE_COMPARE(filtered.indexDataFlags(), Trade::DataFlags{});
CORRADE_COMPARE(filtered.vertexCount(), 3);
CORRADE_COMPARE(filtered.vertexData().data(), vertices.data());
CORRADE_COMPARE(filtered.vertexDataFlags(), Trade::DataFlags{});
/* Testing just the offset if it matches expectations, the
MeshAttributeData is copied directly so no metadata should get lost */
CORRADE_COMPARE(filtered.attributeCount(), 2);
CORRADE_COMPARE(filtered.attributeName(0), Trade::MeshAttribute::Tangent);
CORRADE_COMPARE(filtered.attributeOffset(0), offsetof(Vertex, tangent));
CORRADE_COMPARE(filtered.attributeName(1), Trade::MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(filtered.attributeOffset(1), offsetof(Vertex, textureCoordinates2));
/* The attribute data should not be a growable array to make this usable in
plugins */
Containers::Array<Trade::MeshAttributeData> attributeData = filtered.releaseAttributeData();
CORRADE_VERIFY(!attributeData.deleter());
}
void FilterAttributesTest::filterOnlyAttributeIdsOutOfBounds() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
Vertex vertices[3]{};
Trade::MeshData mesh{MeshPrimitive::TriangleFan,
{}, vertices, {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::stridedArrayView(vertices).slice(&Vertex::position)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, Containers::stridedArrayView(vertices).slice(&Vertex::textureCoordinates1)}
}};
std::ostringstream out;
Error redirectError{&out};
filterOnlyAttributes(mesh, {0, 0, 2});
CORRADE_COMPARE(out.str(), "MeshTools::filterOnlyAttributes(): index 2 out of range for 2 attributes\n");
}
void FilterAttributesTest::filterOnlyAttributeIdsNoIndexData() {
/* A trivial subset of filterOnlyAttributeIds() testing it doesn't blow up
if the mesh is not indexed */
Containers::Array<char> vertexData{3*sizeof(Vertex)};
Containers::StridedArrayView1D<Vertex> vertices = Containers::arrayCast<Vertex>(vertexData);
Trade::MeshData mesh{MeshPrimitive::TriangleFan,
std::move(vertexData), {
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, vertices.slice(&Vertex::textureCoordinates1)}
}};
Trade::MeshData filtered = filterOnlyAttributes(mesh, {
0
});
CORRADE_COMPARE(filtered.primitive(), MeshPrimitive::TriangleFan);
CORRADE_VERIFY(!filtered.isIndexed());
/* Consistent with behavior in reference() for index-less meshes */
CORRADE_COMPARE(filtered.indexDataFlags(), Trade::DataFlags{});
CORRADE_COMPARE(filtered.vertexCount(), 3);
CORRADE_COMPARE(filtered.vertexData().data(), vertices.data());
CORRADE_COMPARE(filtered.vertexDataFlags(), Trade::DataFlags{});
/* Testing just the offset if it matches expectations, the
MeshAttributeData is copied directly so no metadata should get lost */
CORRADE_COMPARE(filtered.attributeCount(), 1);
CORRADE_COMPARE(filtered.attributeName(0), Trade::MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(filtered.attributeOffset(0), offsetof(Vertex, textureCoordinates1));
}
void FilterAttributesTest::filterOnlyAttributeIdsNoAttributeData() {
Containers::Array<char> indexData{5*sizeof(UnsignedShort)};
Containers::StridedArrayView1D<UnsignedShort> indices = Containers::arrayCast<UnsignedShort>(indexData);
Trade::MeshData mesh{MeshPrimitive::Points,
std::move(indexData), Trade::MeshIndexData{indices}, 15};
Trade::MeshData filtered = filterOnlyAttributes(mesh, std::initializer_list<UnsignedInt>{});
CORRADE_COMPARE(filtered.primitive(), MeshPrimitive::Points);
CORRADE_VERIFY(filtered.isIndexed());
CORRADE_COMPARE(filtered.indexCount(), 5);
CORRADE_COMPARE(filtered.indexType(), MeshIndexType::UnsignedShort);
CORRADE_COMPARE(filtered.indexData().data(), indices.data());
CORRADE_COMPARE(filtered.indexDataFlags(), Trade::DataFlags{});
/* The vertex count should get preserved even if there are no attributes */
CORRADE_COMPARE(filtered.vertexCount(), 15);
/* Consistent with behavior in reference() for vertex-less meshes */
CORRADE_COMPARE(filtered.vertexDataFlags(), Trade::DataFlags{});
CORRADE_COMPARE(filtered.attributeCount(), 0);
}
void FilterAttributesTest::filterExceptAttributeNames() {
auto&& data = ImplementationSpecificIndexTypeData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Containers::Array<char> indexData{5*sizeof(UnsignedShort)};
Containers::StridedArrayView1D<UnsignedShort> indices = Containers::arrayCast<UnsignedShort>(indexData);
Containers::Array<char> vertexData{3*sizeof(Vertex)};
Containers::StridedArrayView1D<Vertex> vertices = Containers::arrayCast<Vertex>(vertexData);
Trade::MeshData mesh{MeshPrimitive::TriangleStrip,
std::move(indexData), Trade::MeshIndexData{data.indexType, indices},
std::move(vertexData), {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, vertices.slice(&Vertex::position)},
Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, vertices.slice(&Vertex::tangent)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, vertices.slice(&Vertex::textureCoordinates1)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, vertices.slice(&Vertex::textureCoordinates2)},
/* Positions again, just under a different name. Should be kept. */
Trade::MeshAttributeData{Trade::meshAttributeCustom(0xbaf), vertices.slice(&Vertex::position)},
}};
Trade::MeshData filtered = filterExceptAttributes(mesh, {
Trade::MeshAttribute::Position,
Trade::MeshAttribute::Normal, /* not present, ignored */
Trade::MeshAttribute::TextureCoordinates, /* present twice */
});
CORRADE_COMPARE(filtered.primitive(), MeshPrimitive::TriangleStrip);
CORRADE_VERIFY(filtered.isIndexed());
CORRADE_COMPARE(filtered.indexCount(), 5);
CORRADE_COMPARE(filtered.indexType(), data.indexType);
CORRADE_COMPARE(filtered.indexData().data(), indices.data());
CORRADE_COMPARE(filtered.indexDataFlags(), Trade::DataFlags{});
CORRADE_COMPARE(filtered.vertexCount(), 3);
CORRADE_COMPARE(filtered.vertexData().data(), vertices.data());
CORRADE_COMPARE(filtered.vertexDataFlags(), Trade::DataFlags{});
/* Testing just the offset if it matches expectations, the
MeshAttributeData is copied directly so no metadata should get lost */
CORRADE_COMPARE(filtered.attributeCount(), 2);
CORRADE_COMPARE(filtered.attributeName(0), Trade::MeshAttribute::Tangent);
CORRADE_COMPARE(filtered.attributeOffset(0), offsetof(Vertex, tangent));
CORRADE_COMPARE(filtered.attributeName(1), Trade::meshAttributeCustom(0xbaf));
CORRADE_COMPARE(filtered.attributeOffset(1), offsetof(Vertex, position));
/* The attribute data should not be a growable array to make this usable in
plugins */
Containers::Array<Trade::MeshAttributeData> attributeData = filtered.releaseAttributeData();
CORRADE_VERIFY(!attributeData.deleter());
}
void FilterAttributesTest::filterExceptAttributeNamesNoIndexData() {
/* A trivial subset of filterExceptAttributeNames() testing it doesn't blow
up if the mesh is not indexed */
Containers::Array<char> vertexData{3*sizeof(Vertex)};
Containers::StridedArrayView1D<Vertex> vertices = Containers::arrayCast<Vertex>(vertexData);
Trade::MeshData mesh{MeshPrimitive::TriangleFan,
std::move(vertexData), {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, vertices.slice(&Vertex::position)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, vertices.slice(&Vertex::textureCoordinates1)}
}};
Trade::MeshData filtered = filterExceptAttributes(mesh, {
Trade::MeshAttribute::Position
});
CORRADE_COMPARE(filtered.primitive(), MeshPrimitive::TriangleFan);
CORRADE_VERIFY(!filtered.isIndexed());
/* Consistent with behavior in reference() for index-less meshes */
CORRADE_COMPARE(filtered.indexDataFlags(), Trade::DataFlags{});
CORRADE_COMPARE(filtered.vertexCount(), 3);
CORRADE_COMPARE(filtered.vertexData().data(), vertices.data());
CORRADE_COMPARE(filtered.vertexDataFlags(), Trade::DataFlags{});
/* Testing just the offset if it matches expectations, the
MeshAttributeData is copied directly so no metadata should get lost */
CORRADE_COMPARE(filtered.attributeCount(), 1);
CORRADE_COMPARE(filtered.attributeName(0), Trade::MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(filtered.attributeOffset(0), offsetof(Vertex, textureCoordinates1));
}
void FilterAttributesTest::filterExceptAttributeNamesNoAttributeData() {
Containers::Array<char> indexData{5*sizeof(UnsignedShort)};
Containers::StridedArrayView1D<UnsignedShort> indices = Containers::arrayCast<UnsignedShort>(indexData);
Trade::MeshData mesh{MeshPrimitive::Points,
std::move(indexData), Trade::MeshIndexData{indices}, 15};
Trade::MeshData filtered = filterExceptAttributes(mesh, {
Trade::MeshAttribute::Position
});
CORRADE_COMPARE(filtered.primitive(), MeshPrimitive::Points);
CORRADE_VERIFY(filtered.isIndexed());
CORRADE_COMPARE(filtered.indexCount(), 5);
CORRADE_COMPARE(filtered.indexType(), MeshIndexType::UnsignedShort);
CORRADE_COMPARE(filtered.indexData().data(), indices.data());
CORRADE_COMPARE(filtered.indexDataFlags(), Trade::DataFlags{});
/* The vertex count should get preserved even if there are no attributes */
CORRADE_COMPARE(filtered.vertexCount(), 15);
/* Consistent with behavior in reference() for vertex-less meshes */
CORRADE_COMPARE(filtered.vertexDataFlags(), Trade::DataFlags{});
CORRADE_COMPARE(filtered.attributeCount(), 0);
}
void FilterAttributesTest::filterExceptAttributeIds() {
auto&& data = ImplementationSpecificIndexTypeData[testCaseInstanceId()];
setTestCaseDescription(data.name);
Containers::Array<char> indexData{5*sizeof(UnsignedShort)};
Containers::StridedArrayView1D<UnsignedShort> indices = Containers::arrayCast<UnsignedShort>(indexData);
Containers::Array<char> vertexData{3*sizeof(Vertex)};
Containers::StridedArrayView1D<Vertex> vertices = Containers::arrayCast<Vertex>(vertexData);
Trade::MeshData mesh{MeshPrimitive::TriangleStrip,
std::move(indexData), Trade::MeshIndexData{data.indexType, indices},
std::move(vertexData), {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, vertices.slice(&Vertex::position)},
Trade::MeshAttributeData{Trade::MeshAttribute::Tangent, vertices.slice(&Vertex::tangent)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, vertices.slice(&Vertex::textureCoordinates1)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, vertices.slice(&Vertex::textureCoordinates2)},
}};
Trade::MeshData filtered = filterExceptAttributes(mesh, {
/* The attribute 1 is specified twice, but that won't result in
attribute 1 being removed and then again */
1, 1, 3
});
CORRADE_COMPARE(filtered.primitive(), MeshPrimitive::TriangleStrip);
CORRADE_VERIFY(filtered.isIndexed());
CORRADE_COMPARE(filtered.indexCount(), 5);
CORRADE_COMPARE(filtered.indexType(), data.indexType);
CORRADE_COMPARE(filtered.indexData().data(), indices.data());
CORRADE_COMPARE(filtered.indexDataFlags(), Trade::DataFlags{});
CORRADE_COMPARE(filtered.vertexCount(), 3);
CORRADE_COMPARE(filtered.vertexData().data(), vertices.data());
CORRADE_COMPARE(filtered.vertexDataFlags(), Trade::DataFlags{});
/* Testing just the offset if it matches expectations, the
MeshAttributeData is copied directly so no metadata should get lost */
CORRADE_COMPARE(filtered.attributeCount(), 2);
CORRADE_COMPARE(filtered.attributeName(0), Trade::MeshAttribute::Position);
CORRADE_COMPARE(filtered.attributeOffset(0), offsetof(Vertex, position));
CORRADE_COMPARE(filtered.attributeName(1), Trade::MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(filtered.attributeOffset(1), offsetof(Vertex, textureCoordinates1));
/* The attribute data should not be a growable array to make this usable in
plugins */
Containers::Array<Trade::MeshAttributeData> attributeData = filtered.releaseAttributeData();
CORRADE_VERIFY(!attributeData.deleter());
}
void FilterAttributesTest::filterExceptAttributeIdsOutOfBounds() {
#ifdef CORRADE_NO_ASSERT
CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions");
#endif
Vertex vertices[3]{};
Trade::MeshData mesh{MeshPrimitive::TriangleFan,
{}, vertices, {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::stridedArrayView(vertices).slice(&Vertex::position)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, Containers::stridedArrayView(vertices).slice(&Vertex::textureCoordinates1)}
}};
std::ostringstream out;
Error redirectError{&out};
filterExceptAttributes(mesh, {0, 0, 2});
CORRADE_COMPARE(out.str(), "MeshTools::filterExceptAttributes(): index 2 out of range for 2 attributes\n");
}
void FilterAttributesTest::filterExceptAttributeIdsNoIndexData() {
/* A trivial subset of filterExceptAttributeIds() testing it doesn't blow up
if the mesh is not indexed */
Containers::Array<char> vertexData{3*sizeof(Vertex)};
Containers::StridedArrayView1D<Vertex> vertices = Containers::arrayCast<Vertex>(vertexData);
Trade::MeshData mesh{MeshPrimitive::TriangleFan,
std::move(vertexData), {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, vertices.slice(&Vertex::position)},
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, vertices.slice(&Vertex::textureCoordinates1)}
}};
Trade::MeshData filtered = filterExceptAttributes(mesh, {
0
});
CORRADE_COMPARE(filtered.primitive(), MeshPrimitive::TriangleFan);
CORRADE_VERIFY(!filtered.isIndexed());
/* Consistent with behavior in reference() for index-less meshes */
CORRADE_COMPARE(filtered.indexDataFlags(), Trade::DataFlags{});
CORRADE_COMPARE(filtered.vertexCount(), 3);
CORRADE_COMPARE(filtered.vertexData().data(), vertices.data());
CORRADE_COMPARE(filtered.vertexDataFlags(), Trade::DataFlags{});
/* Testing just the offset if it matches expectations, the
MeshAttributeData is copied directly so no metadata should get lost */
CORRADE_COMPARE(filtered.attributeCount(), 1);
CORRADE_COMPARE(filtered.attributeName(0), Trade::MeshAttribute::TextureCoordinates);
CORRADE_COMPARE(filtered.attributeOffset(0), offsetof(Vertex, textureCoordinates1));
}
void FilterAttributesTest::filterExceptAttributeIdsNoAttributeData() {
Containers::Array<char> indexData{5*sizeof(UnsignedShort)};
Containers::StridedArrayView1D<UnsignedShort> indices = Containers::arrayCast<UnsignedShort>(indexData);
Trade::MeshData mesh{MeshPrimitive::Points,
std::move(indexData), Trade::MeshIndexData{indices}, 15};
Trade::MeshData filtered = filterExceptAttributes(mesh, std::initializer_list<UnsignedInt>{});
CORRADE_COMPARE(filtered.primitive(), MeshPrimitive::Points);
CORRADE_VERIFY(filtered.isIndexed());
CORRADE_COMPARE(filtered.indexCount(), 5);
CORRADE_COMPARE(filtered.indexType(), MeshIndexType::UnsignedShort);
CORRADE_COMPARE(filtered.indexData().data(), indices.data());
CORRADE_COMPARE(filtered.indexDataFlags(), Trade::DataFlags{});
/* The vertex count should get preserved even if there are no attributes */
CORRADE_COMPARE(filtered.vertexCount(), 15);
/* Consistent with behavior in reference() for vertex-less meshes */
CORRADE_COMPARE(filtered.vertexDataFlags(), Trade::DataFlags{});
CORRADE_COMPARE(filtered.attributeCount(), 0);
}
}}}}
CORRADE_TEST_MAIN(Magnum::MeshTools::Test::FilterAttributesTest)
Loading…
Cancel
Save