Browse Source

MeshTools: implement interleave() taking a MeshData.

pull/371/head
Vladimír Vondruš 6 years ago
parent
commit
6c2fb3facb
  1. 2
      doc/changelog.dox
  2. 103
      src/Magnum/MeshTools/Interleave.cpp
  3. 52
      src/Magnum/MeshTools/Interleave.h
  4. 238
      src/Magnum/MeshTools/Test/InterleaveTest.cpp

2
doc/changelog.dox

@ -113,6 +113,8 @@ See also:
@ref Trade::MeshData is interleaved
- Added @ref MeshTools::interleavedLayout() for convenient creation of an
interleaved mesh layout using the new @ref Trade::MeshData API
- Added @ref MeshTools::interleave(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>)
that works directly on the new @ref Trade::MeshData API
- Added @ref MeshTools::subdivideInPlace() for allocation-less mesh
subdivision
- New @ref MeshTools::removeDuplicatesInPlace() variant that works on

103
src/Magnum/MeshTools/Interleave.cpp

@ -25,6 +25,8 @@
#include "Interleave.h"
#include <Corrade/Utility/Algorithms.h>
#include "Magnum/Math/Functions.h"
#include "Magnum/Trade/MeshData.h"
@ -134,4 +136,105 @@ Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt
return interleavedLayout(data, vertexCount, Containers::arrayView(extra));
}
Trade::MeshData interleave(Trade::MeshData&& data, const Containers::ArrayView<const Trade::MeshAttributeData> extra) {
/* If there are no attributes and no index buffer, bail -- the vertex count
is the only property we can transfer. If this wouldn't be done, the
return at the end would assert as vertex count is only passed implicitly
via attributes (which there are none). */
if(!data.attributeCount() && extra.empty() && !data.isIndexed())
return Trade::MeshData{data.primitive(), data.vertexCount()};
/* Transfer the indices unchanged, in case the mesh is indexed */
Containers::Array<char> indexData;
Trade::MeshIndexData indices;
if(data.isIndexed()) {
/* If we can steal the data, do it */
if(data.indexDataFlags() & Trade::DataFlag::Owned) {
indices = Trade::MeshIndexData{data.indices()};
indexData = data.releaseIndexData();
} else {
indexData = Containers::Array<char>{data.indexData().size()};
Utility::copy(data.indexData(), indexData);
indices = Trade::MeshIndexData{data.indexType(),
Containers::ArrayView<const void>{indexData + data.indexOffset(), data.indices().size()[0]*data.indices().size()[1]}};
}
}
const bool interleaved = isInterleaved(data);
/* If the mesh is already interleaved and we don't have anything extra,
steal that data as well */
Containers::Array<char> vertexData;
Containers::Array<Trade::MeshAttributeData> attributeData;
if(interleaved && extra.empty() && (data.vertexDataFlags() & Trade::DataFlag::Owned)) {
attributeData = data.releaseAttributeData();
vertexData = data.releaseVertexData();
/* Otherwise do it the hard way */
} else {
/* Calculate the layout */
Trade::MeshData layout = interleavedLayout(data, data.vertexCount(), extra);
/* Copy existing attributes to new locations */
for(UnsignedInt i = 0; i != data.attributeCount(); ++i)
Utility::copy(data.attribute(i), layout.mutableAttribute(i));
/* Mix in the extra attributes */
UnsignedInt attributeIndex = data.attributeCount();
for(UnsignedInt i = 0; i != extra.size(); ++i) {
/* Padding, ignore */
if(extra[i].format() == VertexFormat{}) continue;
/* Copy the attribute in, if it is non-empty, otherwise keep the
memory uninitialized */
if(extra[i].data()) {
CORRADE_ASSERT(extra[i].data().size() == data.vertexCount(),
"MeshTools::interleave(): extra attribute" << i << "expected to have" << data.vertexCount() << "items but got" << extra[i].data().size(),
(Trade::MeshData{MeshPrimitive::Triangles, 0}));
const Containers::StridedArrayView2D<const char> attribute =
Containers::arrayCast<2, const char>(extra[i].data(), vertexFormatSize(extra[i].format()));
Utility::copy(attribute, layout.mutableAttribute(attributeIndex));
}
++attributeIndex;
}
/* Release the data from the layout to pack them into the output */
vertexData = layout.releaseVertexData();
attributeData = layout.releaseAttributeData();
}
return Trade::MeshData{data.primitive(), std::move(indexData), indices,
std::move(vertexData), std::move(attributeData)};
}
Trade::MeshData interleave(Trade::MeshData&& data, const std::initializer_list<Trade::MeshAttributeData> extra) {
return interleave(std::move(data), Containers::arrayView(extra));
}
Trade::MeshData interleave(const Trade::MeshData& data, const Containers::ArrayView<const Trade::MeshAttributeData> extra) {
Containers::ArrayView<const char> indexData;
Trade::MeshIndexData indices;
if(data.isIndexed()) {
indexData = data.indexData();
indices = Trade::MeshIndexData{data.indices()};
/* If there's neither an index array nor any attributes in the original
mesh, we need to pass vertex count explicitly (MeshData asserts on that
to avoid it getting lost.) */
} else if(!data.attributeCount()) {
return interleave(Trade::MeshData{data.primitive(), data.vertexCount()}, extra);
}
return interleave(Trade::MeshData{data.primitive(),
{}, indexData, indices,
{}, data.vertexData(), Trade::meshAttributeDataNonOwningArray(data.attributeData())
}, extra);
}
Trade::MeshData interleave(const Trade::MeshData& data, const std::initializer_list<Trade::MeshAttributeData> extra) {
return interleave(std::move(data), Containers::arrayView(extra));
}
}}

52
src/Magnum/MeshTools/Interleave.h

@ -145,7 +145,12 @@ would be 21 bytes, causing possible performance loss.
@see @ref interleaveInto()
*/
template<class T, class ...U> Containers::Array<char> interleave(const T& first, const U&... next)
template<class T, class ...U
#ifndef DOXYGEN_GENERATING_OUTPUT
/* So it doesn't clash with the MeshData variant */
, class = typename std::enable_if<Utility::IsIterable<T>::value>::type
#endif
> Containers::Array<char> interleave(const T& first, const U&... next)
{
/* Compute buffer size and stride */
const std::size_t attributeCount = Implementation::AttributeCount{}(first, next...);
@ -234,6 +239,51 @@ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(const Trade::MeshData&
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleavedLayout(const Trade::MeshData& data, UnsignedInt vertexCount, std::initializer_list<Trade::MeshAttributeData> extra);
/**
@brief Interleave mesh data
@m_since_latest
Returns a copy of @p data with all attributes interleaved but everything else
(indices, primitive type, ...) kept as-is. The @p extra attributes, if any, are
interleaved together with existing attributes (or, in case the attribute view
is empty, only the corresponding space for given attribute type is reserved,
with memory left uninitialized). The data layouting is done by
@ref interleavedLayout(), see its documentation for detailed behavior
description.
Expects that each attribute in @p extra has either the same amount of elements
as @p data vertex count or has none.
@see @ref isInterleaved(), @ref Trade::MeshData::attributeData()
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(const Trade::MeshData& data, Containers::ArrayView<const Trade::MeshAttributeData> extra = {});
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(const Trade::MeshData& data, std::initializer_list<Trade::MeshAttributeData> extra);
/**
@brief Interleave mesh data
@m_since_latest
Compared to @ref interleave(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>)
this function can transfer ownership of @p data index buffer (in case it is
owned) and vertex buffer (in case it is owned, already interleaved and there's
no @p extra attributes) to the returned instance instead of making copies of
them.
@see @ref isInterleaved(), @ref Trade::MeshData::indexDataFlags(),
@ref Trade::MeshData::vertexDataFlags(),
@ref Trade::MeshData::attributeData()
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(Trade::MeshData&& data, Containers::ArrayView<const Trade::MeshAttributeData> extra = {});
/**
* @overload
* @m_since_latest
*/
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData interleave(Trade::MeshData&& data, std::initializer_list<Trade::MeshAttributeData> extra);
}}
#endif

238
src/Magnum/MeshTools/Test/InterleaveTest.cpp

@ -26,6 +26,7 @@
#include <sstream>
#include <vector>
#include <Corrade/TestSuite/Tester.h>
#include <Corrade/TestSuite/Compare/Container.h>
#include <Corrade/Utility/Endianness.h>
#include <Corrade/Utility/Debug.h>
#include <Corrade/Utility/DebugStl.h>
@ -65,6 +66,16 @@ struct InterleaveTest: Corrade::TestSuite::Tester {
void interleavedLayoutAlreadyInterleavedAliased();
void interleavedLayoutAlreadyInterleavedExtra();
void interleavedLayoutNothing();
void interleaveMeshData();
void interleaveMeshDataIndexed();
void interleaveMeshDataExtra();
void interleaveMeshDataExtraEmpty();
void interleaveMeshDataExtraOriginalEmpty();
void interleaveMeshDataExtraWrongCount();
void interleaveMeshDataAlreadyInterleavedMove();
void interleaveMeshDataAlreadyInterleavedMoveNonOwned();
void interleaveMeshDataNothing();
};
InterleaveTest::InterleaveTest() {
@ -93,7 +104,17 @@ InterleaveTest::InterleaveTest() {
&InterleaveTest::interleavedLayoutAlreadyInterleaved,
&InterleaveTest::interleavedLayoutAlreadyInterleavedAliased,
&InterleaveTest::interleavedLayoutAlreadyInterleavedExtra,
&InterleaveTest::interleavedLayoutNothing});
&InterleaveTest::interleavedLayoutNothing,
&InterleaveTest::interleaveMeshData,
&InterleaveTest::interleaveMeshDataIndexed,
&InterleaveTest::interleaveMeshDataExtra,
&InterleaveTest::interleaveMeshDataExtraEmpty,
&InterleaveTest::interleaveMeshDataExtraOriginalEmpty,
&InterleaveTest::interleaveMeshDataExtraWrongCount,
&InterleaveTest::interleaveMeshDataAlreadyInterleavedMove,
&InterleaveTest::interleaveMeshDataAlreadyInterleavedMoveNonOwned,
&InterleaveTest::interleaveMeshDataNothing});
}
void InterleaveTest::attributeCount() {
@ -566,6 +587,221 @@ void InterleaveTest::interleavedLayoutNothing() {
CORRADE_COMPARE(layout.vertexData().size(), 0);
}
void InterleaveTest::interleaveMeshData() {
struct {
Vector2 positions[3];
Vector3 normals[3];
} vertexData{
{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}},
{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()}
};
Trade::MeshData data{MeshPrimitive::TriangleFan, {},
Containers::arrayView(&vertexData, sizeof(vertexData)), {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(vertexData.positions)},
Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(vertexData.normals)}
}};
Trade::MeshData interleaved = MeshTools::interleave(data);
CORRADE_VERIFY(MeshTools::isInterleaved(interleaved));
CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan);
CORRADE_VERIFY(!interleaved.isIndexed());
/* No reason to not be like this */
CORRADE_COMPARE(interleaved.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned);
CORRADE_COMPARE(interleaved.attributeCount(), 2);
CORRADE_COMPARE_AS(interleaved.attribute<Vector2>(Trade::MeshAttribute::Position),
Containers::stridedArrayView(vertexData.positions),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(interleaved.attribute<Vector3>(Trade::MeshAttribute::Normal),
Containers::stridedArrayView(vertexData.normals),
TestSuite::Compare::Container);
}
void InterleaveTest::interleaveMeshDataIndexed() {
/* Testing also offset */
UnsignedShort indexData[50 + 3];
indexData[50] = 0;
indexData[51] = 2;
indexData[52] = 1;
Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}};
Trade::MeshData data{MeshPrimitive::TriangleFan,
{}, Containers::arrayView(indexData), Trade::MeshIndexData{Containers::arrayView(indexData).suffix(50)},
{}, Containers::arrayView(positions), {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)}
}};
Trade::MeshData interleaved = MeshTools::interleave(data);
CORRADE_VERIFY(MeshTools::isInterleaved(interleaved));
CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan);
CORRADE_VERIFY(interleaved.isIndexed());
CORRADE_COMPARE(interleaved.indexType(), MeshIndexType::UnsignedShort);
CORRADE_COMPARE(interleaved.indexData().size(), 106);
CORRADE_COMPARE_AS(interleaved.indices<UnsignedShort>(),
Containers::arrayView(indexData).suffix(50),
TestSuite::Compare::Container);
CORRADE_COMPARE(interleaved.attributeCount(), 1);
CORRADE_COMPARE_AS(interleaved.attribute<Vector2>(Trade::MeshAttribute::Position),
Containers::stridedArrayView(positions),
TestSuite::Compare::Container);
}
void InterleaveTest::interleaveMeshDataExtra() {
Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}};
Trade::MeshData data{MeshPrimitive::TriangleFan,
{}, Containers::arrayView(positions), {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)}
}};
const Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis(), Vector3::zAxis()};
Trade::MeshData interleaved = MeshTools::interleave(data, {
Trade::MeshAttributeData{10},
Trade::MeshAttributeData{Trade::MeshAttribute::Normal, Containers::arrayView(normals)}
});
CORRADE_VERIFY(MeshTools::isInterleaved(interleaved));
CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan);
CORRADE_VERIFY(!interleaved.isIndexed());
/* No reason to not be like this */
CORRADE_COMPARE(interleaved.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned);
CORRADE_COMPARE(interleaved.attributeCount(), 2);
CORRADE_COMPARE_AS(interleaved.attribute<Vector2>(Trade::MeshAttribute::Position),
Containers::stridedArrayView(positions),
TestSuite::Compare::Container);
CORRADE_COMPARE_AS(interleaved.attribute<Vector3>(Trade::MeshAttribute::Normal),
Containers::stridedArrayView(normals),
TestSuite::Compare::Container);
}
void InterleaveTest::interleaveMeshDataExtraEmpty() {
Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}};
Trade::MeshData data{MeshPrimitive::TriangleFan,
{}, Containers::arrayView(positions), {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)}
}};
Trade::MeshData interleaved = MeshTools::interleave(data, {
Trade::MeshAttributeData{4},
Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, nullptr}
});
CORRADE_VERIFY(MeshTools::isInterleaved(interleaved));
CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan);
CORRADE_VERIFY(!interleaved.isIndexed());
/* No reason to not be like this */
CORRADE_COMPARE(interleaved.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned);
CORRADE_COMPARE(interleaved.attributeCount(), 2);
CORRADE_COMPARE_AS(interleaved.attribute<Vector2>(Trade::MeshAttribute::Position),
Containers::stridedArrayView(positions),
TestSuite::Compare::Container);
CORRADE_COMPARE(interleaved.attributeStride(Trade::MeshAttribute::Normal), 24);
CORRADE_COMPARE(interleaved.attributeOffset(Trade::MeshAttribute::Normal), 12);
}
void InterleaveTest::interleaveMeshDataExtraOriginalEmpty() {
Trade::MeshData data{MeshPrimitive::TriangleFan, 3};
/* Verify the original vertex count gets passed through */
Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}};
Trade::MeshData interleaved = MeshTools::interleave(data, {
Trade::MeshAttributeData{4},
Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)}
});
CORRADE_VERIFY(MeshTools::isInterleaved(interleaved));
CORRADE_COMPARE(interleaved.primitive(), MeshPrimitive::TriangleFan);
CORRADE_VERIFY(!interleaved.isIndexed());
/* No reason to not be like this */
CORRADE_COMPARE(interleaved.vertexDataFlags(), Trade::DataFlag::Mutable|Trade::DataFlag::Owned);
CORRADE_COMPARE(interleaved.attributeCount(), 1);
CORRADE_COMPARE_AS(interleaved.attribute<Vector2>(Trade::MeshAttribute::Position),
Containers::stridedArrayView(positions),
TestSuite::Compare::Container);
}
void InterleaveTest::interleaveMeshDataExtraWrongCount() {
Vector2 positions[]{{1.3f, 0.3f}, {0.87f, 1.1f}, {1.0f, -0.5f}};
Trade::MeshData data{MeshPrimitive::TriangleFan,
{}, Containers::arrayView(positions), {
Trade::MeshAttributeData{Trade::MeshAttribute::Position, Containers::arrayView(positions)}
}};
const Vector3 normals[]{Vector3::xAxis(), Vector3::yAxis()};
std::ostringstream out;
Error redirectError{&out};
MeshTools::interleave(data, {
Trade::MeshAttributeData{10},
Trade::MeshAttributeData{Trade::MeshAttribute::Normal, VertexFormat::Vector3, Containers::arrayView(normals)}
});
CORRADE_COMPARE(out.str(), "MeshTools::interleave(): extra attribute 1 expected to have 3 items but got 2\n");
}
void InterleaveTest::interleaveMeshDataAlreadyInterleavedMove() {
Containers::Array<char> indexData{4};
auto indexView = Containers::arrayCast<UnsignedShort>(indexData);
Containers::Array<char> vertexData{3*24};
Containers::StridedArrayView1D<Vector2> positionView{vertexData,
reinterpret_cast<Vector2*>(vertexData.data()), 3, 24};
Containers::StridedArrayView1D<Vector3> normalView{vertexData,
reinterpret_cast<Vector3*>(vertexData.data() + 10), 3, 24};
auto attributeData = Containers::array({
Trade::MeshAttributeData{Trade::MeshAttribute::Position, positionView},
Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normalView}
});
const Trade::MeshAttributeData* attributePointer = attributeData;
Trade::MeshData data{MeshPrimitive::TriangleFan,
std::move(indexData), Trade::MeshIndexData{indexView},
std::move(vertexData), std::move(attributeData)};
CORRADE_VERIFY(MeshTools::isInterleaved(data));
/* {} just to cover the initializer_list overload :P */
Trade::MeshData interleaved = MeshTools::interleave(std::move(data), {});
CORRADE_VERIFY(MeshTools::isInterleaved(interleaved));
CORRADE_COMPARE(interleaved.indexCount(), 2);
CORRADE_COMPARE(interleaved.attributeCount(), 2);
CORRADE_COMPARE(interleaved.vertexCount(), 3);
/* Things got just moved without copying */
CORRADE_VERIFY(interleaved.indexData().data() == static_cast<const void*>(indexView.data()));
CORRADE_VERIFY(interleaved.attributeData().data() == attributePointer);
CORRADE_VERIFY(interleaved.vertexData().data() == positionView.data());
}
void InterleaveTest::interleaveMeshDataAlreadyInterleavedMoveNonOwned() {
Containers::Array<char> indexData{4};
auto indexView = Containers::arrayCast<UnsignedShort>(indexData);
Containers::Array<char> vertexData{3*24};
Containers::StridedArrayView1D<Vector2> positionView{vertexData,
reinterpret_cast<Vector2*>(vertexData.data()), 3, 24};
Containers::StridedArrayView1D<Vector3> normalView{vertexData,
reinterpret_cast<Vector3*>(vertexData.data() + 10), 3, 24};
auto attributeData = Containers::array({
Trade::MeshAttributeData{Trade::MeshAttribute::Position, positionView},
Trade::MeshAttributeData{Trade::MeshAttribute::Normal, normalView}
});
const Trade::MeshAttributeData* attributePointer = attributeData;
Trade::MeshData data{MeshPrimitive::TriangleFan,
{}, indexData, Trade::MeshIndexData{indexView},
{}, vertexData, std::move(attributeData)};
CORRADE_VERIFY(MeshTools::isInterleaved(data));
Trade::MeshData interleaved = MeshTools::interleave(std::move(data));
CORRADE_VERIFY(MeshTools::isInterleaved(interleaved));
CORRADE_COMPARE(interleaved.indexCount(), 2);
CORRADE_COMPARE(interleaved.attributeCount(), 2);
CORRADE_COMPARE(interleaved.vertexCount(), 3);
/* The moved data array doesn't own these so things got copied */
CORRADE_VERIFY(interleaved.indexData().data() != static_cast<const void*>(indexView.data()));
CORRADE_VERIFY(interleaved.attributeData().data() != attributePointer);
CORRADE_VERIFY(interleaved.vertexData().data() != positionView.data());
}
void InterleaveTest::interleaveMeshDataNothing() {
Trade::MeshData interleaved = MeshTools::interleave(Trade::MeshData{MeshPrimitive::Points, 2});
CORRADE_VERIFY(MeshTools::isInterleaved(interleaved));
CORRADE_COMPARE(interleaved.attributeCount(), 0);
CORRADE_COMPARE(interleaved.vertexCount(), 2);
CORRADE_VERIFY(!interleaved.vertexData());
CORRADE_COMPARE(interleaved.vertexData().size(), 0);
}
}}}}
CORRADE_TEST_MAIN(Magnum::MeshTools::Test::InterleaveTest)

Loading…
Cancel
Save