Browse Source

MeshTools: implemented combineFaceAttributes().

pull/371/head
Vladimír Vondruš 6 years ago
parent
commit
7857388949
  1. 4
      doc/changelog.dox
  2. 158
      src/Magnum/MeshTools/Combine.cpp
  3. 21
      src/Magnum/MeshTools/Combine.h
  4. 211
      src/Magnum/MeshTools/Test/CombineTest.cpp

4
doc/changelog.dox

@ -130,7 +130,9 @@ See also:
- New @ref MeshTools::removeDuplicatesInPlace() variant that works on
discrete data in addition to floating-point
- New @ref MeshTools::combineIndexedAttributes() tool for combining
differently indexed attributes into a single index buffer
differently indexed attributes into a single index buffer, and
@ref MeshTools::combineFaceAttributes() for converting per-face attributes
into per-vertex
@subsubsection changelog-latest-new-platform Platform libraries

158
src/Magnum/MeshTools/Combine.cpp

@ -30,64 +30,25 @@
#include <Corrade/Containers/Reference.h>
#include <Corrade/Utility/Algorithms.h>
#include "Magnum/MeshTools/Interleave.h"
#include "Magnum/MeshTools/Duplicate.h"
#include "Magnum/MeshTools/RemoveDuplicates.h"
#include "Magnum/Trade/MeshData.h"
namespace Magnum { namespace MeshTools {
Trade::MeshData combineIndexedAttributes(const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> data) {
CORRADE_ASSERT(!data.empty(), "MeshTools::combineIndexedAttributes(): no meshes passed", (Trade::MeshData{MeshPrimitive{}, 0}));
namespace {
/* Decide on the output primitive and index count, calculated total
combined index type size and also the count and stride of all
attributes */
MeshPrimitive primitive;
UnsignedInt indexCount;
std::size_t indexStride = 0;
std::size_t attributeCount = 0;
Trade::MeshData combineIndexedImplementation(const MeshPrimitive primitive, Containers::Array<char>& combinedIndices, const UnsignedInt indexCount, const UnsignedInt indexStride, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> data) {
/* Calculate attribute count and vertex stride */
UnsignedInt attributeCount = 0;
UnsignedInt vertexStride = 0;
for(std::size_t i = 0; i != data.size(); ++i) {
CORRADE_ASSERT(data[i]->isIndexed(),
"MeshTools::combineIndexedAttributes(): data" << i << "is not indexed",
(Trade::MeshData{MeshPrimitive{}, 0}));
if(i == 0) {
primitive = data[i]->primitive();
indexCount = data[i]->indexCount();
} else {
CORRADE_ASSERT(data[i]->primitive() == primitive,
"MeshTools::combineIndexedAttributes(): data" << i << "is" << data[i]->primitive() << "but expected" << primitive, (Trade::MeshData{MeshPrimitive{}, 0}));
CORRADE_ASSERT(data[i]->indexCount() == indexCount,
"MeshTools::combineIndexedAttributes(): data" << i << "has" << data[i]->indexCount() << "indices but expected" << indexCount, (Trade::MeshData{MeshPrimitive{}, 0}));
}
indexStride += meshIndexTypeSize(data[i]->indexType());
attributeCount += data[i]->attributeCount();
for(std::size_t j = 0; j != data[i]->attributeCount(); ++j)
for(UnsignedInt j = 0; j != data[i]->attributeCount(); ++j)
vertexStride += vertexFormatSize(data[i]->attributeFormat(j));
}
/* Create a combined index array */
Containers::Array<char> combinedIndices{Containers::NoInit,
indexCount*indexStride};
{
std::size_t indexOffset = 0;
for(const Trade::MeshData& mesh: data) {
const UnsignedInt indexSize = meshIndexTypeSize(mesh.indexType());
Containers::StridedArrayView2D<char> dst{combinedIndices,
combinedIndices.data() + indexOffset,
{indexCount, indexSize},
{std::ptrdiff_t(indexStride), 1}};
Utility::copy(mesh.indices(), dst);
indexOffset += indexSize;
}
/* Check we pre-calculated correctly */
CORRADE_INTERNAL_ASSERT(indexOffset == indexStride);
}
/** @todo handle alignment in the above somehow (duplicate() will fail when
reading 32-bit values from odd addresses on some platforms) */
/* Make the combined index array unique */
Containers::Array<char> indexData{indexCount*sizeof(UnsignedInt)};
const auto indexDataI = Containers::arrayCast<UnsignedInt>(indexData);
@ -136,8 +97,115 @@ Trade::MeshData combineIndexedAttributes(const Containers::ArrayView<const Conta
std::move(vertexData), std::move(attributeData), vertexCount};
}
}
Trade::MeshData combineIndexedAttributes(const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> data) {
CORRADE_ASSERT(!data.empty(),
"MeshTools::combineIndexedAttributes(): no meshes passed",
(Trade::MeshData{MeshPrimitive{}, 0}));
/* Decide on the output primitive and index count, calculated total
combined index type size */
MeshPrimitive primitive;
UnsignedInt indexCount;
UnsignedInt indexStride = 0;
for(std::size_t i = 0; i != data.size(); ++i) {
CORRADE_ASSERT(data[i]->isIndexed(),
"MeshTools::combineIndexedAttributes(): data" << i << "is not indexed",
(Trade::MeshData{MeshPrimitive{}, 0}));
if(i == 0) {
primitive = data[i]->primitive();
indexCount = data[i]->indexCount();
} else {
CORRADE_ASSERT(data[i]->primitive() == primitive,
"MeshTools::combineIndexedAttributes(): data" << i << "is" << data[i]->primitive() << "but expected" << primitive, (Trade::MeshData{MeshPrimitive{}, 0}));
CORRADE_ASSERT(data[i]->indexCount() == indexCount,
"MeshTools::combineIndexedAttributes(): data" << i << "has" << data[i]->indexCount() << "indices but expected" << indexCount, (Trade::MeshData{MeshPrimitive{}, 0}));
}
indexStride += meshIndexTypeSize(data[i]->indexType());
}
/** @todo handle alignment in the above somehow (duplicate() will fail when
reading 32-bit values from odd addresses on some platforms) */
/* Create a combined index array */
Containers::Array<char> combinedIndices{Containers::NoInit,
indexCount*indexStride};
{
std::size_t indexOffset = 0;
for(const Trade::MeshData& mesh: data) {
const UnsignedInt indexSize = meshIndexTypeSize(mesh.indexType());
Containers::StridedArrayView2D<char> dst{combinedIndices,
combinedIndices.data() + indexOffset,
{indexCount, indexSize},
{std::ptrdiff_t(indexStride), 1}};
Utility::copy(mesh.indices(), dst);
indexOffset += indexSize;
}
/* Check we pre-calculated correctly */
CORRADE_INTERNAL_ASSERT(indexOffset == indexStride);
}
return combineIndexedImplementation(primitive, combinedIndices, indexCount, indexStride, data);
}
Trade::MeshData combineIndexedAttributes(std::initializer_list<Containers::Reference<const Trade::MeshData>> data) {
return combineIndexedAttributes(Containers::arrayView(data));
}
Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, const Trade::MeshData& faceAttributes) {
CORRADE_ASSERT(mesh.isIndexed(),
"MeshTools::combineFaceAttributes(): vertex mesh is not indexed",
(Trade::MeshData{MeshPrimitive{}, 0}));
CORRADE_ASSERT(mesh.primitive() == MeshPrimitive::Triangles && faceAttributes.primitive() == MeshPrimitive::Faces,
"MeshTools::combineFaceAttributes(): expected a MeshPrimitive::Triangles mesh and a MeshPrimitive::Faces mesh but got" << mesh.primitive() << "and" << faceAttributes.primitive(),
(Trade::MeshData{MeshPrimitive{}, 0}));
const UnsignedInt meshIndexCount = mesh.indexCount();
const UnsignedInt faceIndexCount = faceAttributes.isIndexed() ?
faceAttributes.indexCount() : faceAttributes.vertexCount();
CORRADE_ASSERT(faceIndexCount*3 == meshIndexCount,
"MeshTools::combineFaceAttributes(): expected" << meshIndexCount/3 << "face entries for" << meshIndexCount << "indices but got" << faceIndexCount,
(Trade::MeshData{MeshPrimitive{}, 0}));
/* Make a combined index array. First copy the mesh indices as-is. */
const UnsignedInt meshIndexSize = meshIndexTypeSize(mesh.indexType());
const UnsignedInt faceIndexSize = faceAttributes.isIndexed() ?
meshIndexTypeSize(faceAttributes.indexType()) : 4;
const UnsignedInt indexStride = meshIndexSize + faceIndexSize;
Containers::Array<char> combinedIndices{meshIndexCount*indexStride};
Utility::copy(mesh.indices(),
Containers::StridedArrayView2D<char>{combinedIndices, {meshIndexCount, meshIndexSize}, {std::ptrdiff_t(indexStride), 1}});
/* Then, if the face attributes are not indexed, remove duplicates and put
the resulting indices into the combined array above. For simplicity
assume face data are interleaved. */
Containers::StridedArrayView3D<char> combinedFaceIndices{combinedIndices,
combinedIndices.data() + meshIndexSize,
{3, faceIndexCount, faceIndexSize},
{std::ptrdiff_t(indexStride), 3*std::ptrdiff_t(indexStride), 1}};
if(!faceAttributes.isIndexed()) {
/** @todo this could go into a dedicated removeDuplicates(MeshData)
feature at some point, which would handle everything including
in-place / non-in-place, indexed / non-indexed etc. */
CORRADE_ASSERT(isInterleaved(faceAttributes),
"MeshTools::combineFaceAttributes(): face attributes are not interleaved",
(Trade::MeshData{MeshPrimitive{}, 0}));
removeDuplicatesInto(interleavedData(faceAttributes), Containers::arrayCast<1, UnsignedInt>(combinedFaceIndices[0]));
/* Otherwise, simply copy the indices directly */
} else Utility::copy(faceAttributes.indices(), combinedFaceIndices[0]);
/* Duplicate the vertex index to the other two vertices of each triangle */
Utility::copy(combinedFaceIndices[0], combinedFaceIndices[1]);
Utility::copy(combinedFaceIndices[0], combinedFaceIndices[2]);
/* Then combine the two into a single buffer */
return combineIndexedImplementation(mesh.primitive(), combinedIndices,
meshIndexCount, indexStride,
Containers::arrayView<const Containers::Reference<const Trade::MeshData>>({
mesh, faceAttributes
}));
}
}}

21
src/Magnum/MeshTools/Combine.h

@ -26,7 +26,7 @@
*/
/** @file
* @brief Function @ref Magnum::MeshTools::combineIndexedAttributes()
* @brief Function @ref Magnum::MeshTools::combineIndexedAttributes(), @ref Magnum::MeshTools::combineFaceAttributes()
* @m_since_latest
*/
@ -89,6 +89,25 @@ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(const Container
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(std::initializer_list<Containers::Reference<const Trade::MeshData>> data);
/**
@brief Combine per-face attributes into an existing mesh
@m_since_latest
The resulting mesh will have all per-face attributes turned into per-vertex
attributes, leaving only unique combinations and adjusting the index buffer
accordingly. The resulting mesh has the same amount of indices, but likely
more vertices.
Expects that @p mesh is indexed @ref MeshPrimitive::Triangles and
@p faceAttributes is indexed @ref MeshPrimitive::Faces, index count of the
latter corresponding to index count of the former. If @p faceAttributes is
indexed, it's assumed to have the data unique; if it's not indexed, it's first
made unique using @ref removeDuplicates() and in that case it's expected to
be interleaved.
@see @ref isInterleaved()
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineFaceAttributes(const Trade::MeshData& mesh, const Trade::MeshData& faceAttributes);
}}
#endif

211
src/Magnum/MeshTools/Test/CombineTest.cpp

@ -29,6 +29,7 @@
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/DebugStl.h>
#include "Magnum/Math/Color.h"
#include "Magnum/MeshTools/Combine.h"
#include "Magnum/Trade/MeshData.h"
@ -44,6 +45,20 @@ struct CombineTest: TestSuite::Tester {
void combineIndexedAttributesNotIndexed();
void combineIndexedAttributesDifferentPrimitive();
void combineIndexedAttributesDifferentIndexCount();
void combineFaceAttributes();
void combineFaceAttributesMeshNotIndexed();
void combineFaceAttributesUnexpectedPrimitive();
void combineFaceAttributesUnexpectedFaceCount();
void combineFaceAttributesFacesNotInterleaved();
};
constexpr struct {
const char* name;
bool indexed;
} CombineFaceAttributesData[] {
{"", false},
{"indexed faces", true}
};
CombineTest::CombineTest() {
@ -54,6 +69,14 @@ CombineTest::CombineTest() {
&CombineTest::combineIndexedAttributesNotIndexed,
&CombineTest::combineIndexedAttributesDifferentPrimitive,
&CombineTest::combineIndexedAttributesDifferentIndexCount});
addInstancedTests({&CombineTest::combineFaceAttributes},
Containers::arraySize(CombineFaceAttributesData));
addTests({&CombineTest::combineFaceAttributesMeshNotIndexed,
&CombineTest::combineFaceAttributesUnexpectedPrimitive,
&CombineTest::combineFaceAttributesUnexpectedFaceCount,
&CombineTest::combineFaceAttributesFacesNotInterleaved});
}
void CombineTest::combineIndexedAttributes() {
@ -174,6 +197,194 @@ void CombineTest::combineIndexedAttributesDifferentIndexCount() {
CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): data 2 has 4 indices but expected 5\n");
}
void CombineTest::combineFaceAttributes() {
auto&& data = CombineFaceAttributesData[testCaseInstanceId()];
setTestCaseDescription(data.name);
using namespace Math::Literals;
/*
9 ------- 8
5 ------- 4 \ / 6
\ / \ \ C / / \
\ C / \ \ / / \
\ / B \ \ / / B \
\ / \ 7 / \
1 ------- 3 ==> 3 ------- 5
/ \ / 2 \ /
/ \ B / / \ \ B /
/ A \ / / \ \ /
/ \ / / A \ \ /
0 ------- 2 / \ 4
0 ------- 1
*/
const UnsignedShort indices[]{
0, 2, 1,
1, 2, 3,
1, 3, 4,
1, 4, 5
};
const Vector2 positions[] {
{0.0f, 0.0f},
{0.5f, 1.0f},
{1.0f, 0.0f},
{1.5f, 1.0f},
{1.0f, 2.0f},
{0.0f, 2.0f}
};
const struct FaceData {
Color3 color;
Byte id;
} faceData[] {
{0xaaaaaa_rgbf, 'A'},
{0xbbbbbb_rgbf, 'B'},
{0xbbbbbb_rgbf, 'B'},
{0xcccccc_rgbf, 'C'}
};
const UnsignedByte faceIndices[] { 0, 1, 1, 2 };
const FaceData faceDataIndexed[] {
{0xaaaaaa_rgbf, 'A'},
{0xbbbbbb_rgbf, 'B'},
{0xcccccc_rgbf, 'C'}
};
const Trade::MeshData mesh{MeshPrimitive::Triangles,
{}, indices, Trade::MeshIndexData{indices},
{}, positions, {
Trade::MeshAttributeData{Trade::MeshAttribute::Position,
Containers::arrayView(positions)}
}};
const Trade::MeshData faceAttributes{MeshPrimitive::Faces,
{}, faceData, {
Trade::MeshAttributeData{Trade::MeshAttribute::Color,
Containers::StridedArrayView1D<const Color3>{faceData,
&faceData[0].color, 4, sizeof(FaceData)}},
Trade::MeshAttributeData{Trade::meshAttributeCustom(25),
Containers::StridedArrayView1D<const Byte>{faceData,
&faceData[0].id, 4, sizeof(FaceData)}},
}};
const Trade::MeshData faceAttributesIndexed{MeshPrimitive::Faces,
{}, faceIndices, Trade::MeshIndexData{faceIndices},
{}, faceDataIndexed, {
Trade::MeshAttributeData{Trade::MeshAttribute::Color,
Containers::StridedArrayView1D<const Color3>{faceDataIndexed,
&faceDataIndexed[0].color, 3, sizeof(FaceData)}},
Trade::MeshAttributeData{Trade::meshAttributeCustom(25),
Containers::StridedArrayView1D<const Byte>{faceDataIndexed,
&faceDataIndexed[0].id, 3, sizeof(FaceData)}},
}};
Trade::MeshData combined = data.indexed ?
MeshTools::combineFaceAttributes(mesh, faceAttributesIndexed) :
MeshTools::combineFaceAttributes(mesh, faceAttributes);
CORRADE_COMPARE(combined.attributeCount(), 3);
CORRADE_COMPARE(combined.indexType(), MeshIndexType::UnsignedInt);
CORRADE_COMPARE_AS(combined.indices<UnsignedInt>(),
Containers::arrayView<UnsignedInt>({
0, 1, 2,
3, 4, 5,
3, 5, 6,
7, 8, 9
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(combined.attribute<Vector2>(Trade::MeshAttribute::Position),
Containers::arrayView<Vector2>({
{0.0f, 0.0f},
{1.0f, 0.0f},
{0.5f, 1.0f},
{0.5f, 1.0f},
{1.0f, 0.0f},
{1.5f, 1.0f},
{1.0f, 2.0f},
{0.5f, 1.0f},
{1.0f, 2.0f},
{0.0f, 2.0f}
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(combined.attribute<Color3>(Trade::MeshAttribute::Color),
Containers::arrayView<Color3>({
0xaaaaaa_rgbf, 0xaaaaaa_rgbf, 0xaaaaaa_rgbf,
0xbbbbbb_rgbf, 0xbbbbbb_rgbf, 0xbbbbbb_rgbf, 0xbbbbbb_rgbf,
0xcccccc_rgbf, 0xcccccc_rgbf, 0xcccccc_rgbf
}), TestSuite::Compare::Container);
CORRADE_COMPARE_AS(combined.attribute<Byte>(Trade::meshAttributeCustom(25)),
Containers::arrayView<Byte>({
'A', 'A', 'A',
'B', 'B', 'B', 'B',
'C', 'C', 'C'
}), TestSuite::Compare::Container);
}
void CombineTest::combineFaceAttributesMeshNotIndexed() {
const Trade::MeshData mesh{MeshPrimitive::Triangles, 3};
const Trade::MeshData faceAttributes{MeshPrimitive::Faces, 0};
std::ostringstream out;
Error redirectError{&out};
MeshTools::combineFaceAttributes(mesh, faceAttributes);
CORRADE_COMPARE(out.str(),
"MeshTools::combineFaceAttributes(): vertex mesh is not indexed\n");
}
void CombineTest::combineFaceAttributesUnexpectedPrimitive() {
const UnsignedInt indices[] { 0, 0, 0 };
const Trade::MeshData a{MeshPrimitive::Triangles,
{}, indices, Trade::MeshIndexData{indices}, 1};
const Trade::MeshData b{MeshPrimitive::Lines,
{}, indices, Trade::MeshIndexData{indices}, 1};
const Trade::MeshData faceA{MeshPrimitive::Instances, 0};
const Trade::MeshData faceB{MeshPrimitive::Faces, 0};
std::ostringstream out;
Error redirectError{&out};
MeshTools::combineFaceAttributes(a, faceA);
MeshTools::combineFaceAttributes(b, faceB);
CORRADE_COMPARE(out.str(),
"MeshTools::combineFaceAttributes(): expected a MeshPrimitive::Triangles mesh and a MeshPrimitive::Faces mesh but got MeshPrimitive::Triangles and MeshPrimitive::Instances\n"
"MeshTools::combineFaceAttributes(): expected a MeshPrimitive::Triangles mesh and a MeshPrimitive::Faces mesh but got MeshPrimitive::Lines and MeshPrimitive::Faces\n");
}
void CombineTest::combineFaceAttributesUnexpectedFaceCount() {
const UnsignedInt indices[] { 0, 0, 0 };
const Trade::MeshData mesh{MeshPrimitive::Triangles,
{}, indices, Trade::MeshIndexData{indices}, 1};
const Trade::MeshData faceAttributes{MeshPrimitive::Faces, 2};
std::ostringstream out;
Error redirectError{&out};
MeshTools::combineFaceAttributes(mesh, faceAttributes);
CORRADE_COMPARE(out.str(),
"MeshTools::combineFaceAttributes(): expected 1 face entries for 3 indices but got 2\n");
}
void CombineTest::combineFaceAttributesFacesNotInterleaved() {
using namespace Math::Literals;
const UnsignedInt indices[] { 0, 0, 0, 0, 0, 0 };
const Trade::MeshData mesh{MeshPrimitive::Triangles,
{}, indices, Trade::MeshIndexData{indices}, 1};
const struct {
Color3 color[2];
Byte id[2];
} faceData[]{{
{0xaaaaaa_rgbf, 0xbbbbbb_rgbf},
{'A', 'B'}
}};
const Trade::MeshData faceAttributes{MeshPrimitive::Faces,
{}, faceData, {
Trade::MeshAttributeData{Trade::MeshAttribute::Color,
Containers::arrayView(faceData[0].color)},
Trade::MeshAttributeData{Trade::meshAttributeCustom(25),
Containers::arrayView(faceData[0].id)}
}};
std::ostringstream out;
Error redirectError{&out};
MeshTools::combineFaceAttributes(mesh, faceAttributes);
CORRADE_COMPARE(out.str(),
"MeshTools::combineFaceAttributes(): face attributes are not interleaved\n");
}
}}}}
CORRADE_TEST_MAIN(Magnum::MeshTools::Test::CombineTest)

Loading…
Cancel
Save