mirror of https://github.com/mosra/magnum.git
Browse Source
Replaces the STL-heavy combineIndexedArrays(), but in a less extremely horrendous way.pull/371/head
6 changed files with 422 additions and 0 deletions
@ -0,0 +1,143 @@
|
||||
/*
|
||||
This file is part of Magnum. |
||||
|
||||
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 |
||||
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 "Combine.h" |
||||
|
||||
#include <numeric> |
||||
#include <Corrade/Containers/ArrayView.h> |
||||
#include <Corrade/Containers/Reference.h> |
||||
#include <Corrade/Utility/Algorithms.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})); |
||||
|
||||
/* 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; |
||||
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) |
||||
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); |
||||
const std::size_t vertexCount = removeDuplicatesInPlaceInto( |
||||
Containers::StridedArrayView2D<char>{combinedIndices, {indexCount, indexStride}}, |
||||
indexDataI); |
||||
|
||||
/* Allocate resulting attribute and vertex data and duplicate the
|
||||
attributes there according to the combined index buffer */ |
||||
Containers::Array<char> vertexData{Containers::NoInit, |
||||
vertexStride*vertexCount}; |
||||
Containers::Array<Trade::MeshAttributeData> attributeData{attributeCount}; |
||||
{ |
||||
std::size_t indexOffset = 0; |
||||
std::size_t attributeOffset = 0; |
||||
std::size_t vertexOffset = 0; |
||||
for(const Trade::MeshData& mesh: data) { |
||||
const UnsignedInt indexSize = mesh.isIndexed() ? |
||||
meshIndexTypeSize(mesh.indexType()) : 4; |
||||
Containers::StridedArrayView2D<const char> indices{combinedIndices, |
||||
combinedIndices.data() + indexOffset, |
||||
{vertexCount, indexSize}, |
||||
{std::ptrdiff_t(indexStride), 1}}; |
||||
|
||||
for(UnsignedInt i = 0; i != mesh.attributeCount(); ++i) { |
||||
const UnsignedInt attributeSize = vertexFormatSize(mesh.attributeFormat(i)); |
||||
Containers::StridedArrayView2D<char> dst{vertexData, |
||||
vertexData.data() + vertexOffset, |
||||
{vertexCount, attributeSize}, |
||||
{std::ptrdiff_t(vertexStride), 1}}; |
||||
duplicateInto(indices, mesh.attribute(i), dst); |
||||
vertexOffset += attributeSize; |
||||
attributeData[attributeOffset++] = Trade::MeshAttributeData{ |
||||
mesh.attributeName(i), mesh.attributeFormat(i), dst}; |
||||
} |
||||
|
||||
indexOffset += indexSize; |
||||
} |
||||
|
||||
/* Check we pre-calculated correctly */ |
||||
CORRADE_INTERNAL_ASSERT(attributeOffset == attributeCount && vertexOffset == vertexStride); |
||||
} |
||||
|
||||
return Trade::MeshData{primitive, |
||||
std::move(indexData), Trade::MeshIndexData{indexDataI}, |
||||
std::move(vertexData), std::move(attributeData)}; |
||||
} |
||||
|
||||
Trade::MeshData combineIndexedAttributes(std::initializer_list<Containers::Reference<const Trade::MeshData>> data) { |
||||
return combineIndexedAttributes(Containers::arrayView(data)); |
||||
} |
||||
|
||||
}} |
||||
@ -0,0 +1,94 @@
|
||||
#ifndef Magnum_MeshTools_Combine_h |
||||
#define Magnum_MeshTools_Combine_h |
||||
/*
|
||||
This file is part of Magnum. |
||||
|
||||
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 |
||||
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::combineIndexedAttributes() |
||||
* @m_since_latest |
||||
*/ |
||||
|
||||
#include <initializer_list> |
||||
|
||||
#include "Magnum/MeshTools/visibility.h" |
||||
#include "Magnum/Trade/Trade.h" |
||||
|
||||
namespace Magnum { namespace MeshTools { |
||||
|
||||
/**
|
||||
@brief Combine differently indexed attributes into a single mesh |
||||
@m_since_latest |
||||
|
||||
Assuming each @p data contains only unique vertex data, creates an indexed mesh |
||||
that contains all attributes from @p data combined, with duplicate vertices |
||||
removed. For example, when you have a position and a normal array, each indexed |
||||
with separate indices like this: |
||||
|
||||
@code{.cpp} |
||||
{pA, pB, pC, pD, pE, pF} // positions
|
||||
{nA, nB, nC, nD, nE, nF, nG} // normals
|
||||
|
||||
{0, 2, 5, 0, 0, 1, 3, 2, 2} // position indices
|
||||
{1, 3, 4, 1, 4, 6, 1, 3, 1} // normal indices
|
||||
@endcode |
||||
|
||||
Then the first triangle in the mesh is defined as |
||||
@cb{.cpp} {pA, nB}, {pC, nD}, {pF, nE} @ce. When combined together using this |
||||
function, the resulting mesh stays the same but there's just one index array, |
||||
indexing both positions and normals: |
||||
|
||||
@code{.cpp} |
||||
{{pA, nB}, {pC, nD}, {pF, nE}, {pA, nE}, {pB, nG}, {pD, nB}, {pC, nB}} |
||||
// unique pairs of positions and normals
|
||||
|
||||
{0, 1, 2, 0, 3, 4, 5, 1, 6} // unified indices
|
||||
@endcode |
||||
|
||||
The function preserves all vertex data including repeated or custom attributes. |
||||
The resulting mesh is interleaved, with all attributes packed tightly together. |
||||
If you need to add specific padding for alignment preservation, pass the result |
||||
to @ref interleave() and specify the paddings between attributes manually. |
||||
Similarly, for simplicity the resulting mesh has always |
||||
@ref MeshIndexType::UnsignedInt --- use @ref compressIndices(const Trade::MeshData&, MeshIndexType) |
||||
if you want to have it compressed to a smaller type. |
||||
|
||||
Expects that @p data is non-empty and all data have the same primitive and |
||||
index count. All inputs have to be indexed, although the particular |
||||
@ref MeshIndexType doesn't matter. For non-indexed attributes combining can be |
||||
done much more efficiently using @ref duplicate(const Trade::MeshData&, Containers::ArrayView<const Trade::MeshAttributeData>), |
||||
alternatively you can turn a non-indexed attribute to an indexed one first |
||||
using @ref removeDuplicatesInPlace() and then call this function. |
||||
*/ |
||||
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> data); |
||||
|
||||
/**
|
||||
* @overload |
||||
* @m_since_latest |
||||
*/ |
||||
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData combineIndexedAttributes(std::initializer_list<Containers::Reference<const Trade::MeshData>> data); |
||||
|
||||
}} |
||||
|
||||
#endif |
||||
@ -0,0 +1,179 @@
|
||||
/*
|
||||
This file is part of Magnum. |
||||
|
||||
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 |
||||
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/Containers/Reference.h> |
||||
#include <Corrade/TestSuite/Tester.h> |
||||
#include <Corrade/TestSuite/Compare/Container.h> |
||||
#include <Corrade/Utility/DebugStl.h> |
||||
|
||||
#include "Magnum/MeshTools/Combine.h" |
||||
#include "Magnum/Trade/MeshData.h" |
||||
|
||||
namespace Magnum { namespace MeshTools { namespace Test { namespace { |
||||
|
||||
struct CombineTest: TestSuite::Tester { |
||||
explicit CombineTest(); |
||||
|
||||
void combineIndexedAttributes(); |
||||
void combineIndexedAttributesIndicesOnly(); |
||||
|
||||
void combineIndexedAttributesNoMeshes(); |
||||
void combineIndexedAttributesNotIndexed(); |
||||
void combineIndexedAttributesDifferentPrimitive(); |
||||
void combineIndexedAttributesDifferentIndexCount(); |
||||
}; |
||||
|
||||
CombineTest::CombineTest() { |
||||
addTests({&CombineTest::combineIndexedAttributes, |
||||
&CombineTest::combineIndexedAttributesIndicesOnly, |
||||
|
||||
&CombineTest::combineIndexedAttributesNoMeshes, |
||||
&CombineTest::combineIndexedAttributesNotIndexed, |
||||
&CombineTest::combineIndexedAttributesDifferentPrimitive, |
||||
&CombineTest::combineIndexedAttributesDifferentIndexCount}); |
||||
} |
||||
|
||||
void CombineTest::combineIndexedAttributes() { |
||||
const UnsignedInt indicesA[]{2, 1, 2, 0}; |
||||
const Int dataA[]{2, 1, 0}; |
||||
const UnsignedByte indicesB[]{3, 4, 3, 2}; |
||||
const Short dataB[]{4, 3, 2, 1, 0}; |
||||
const UnsignedShort indicesC[]{7, 6, 7, 5}; |
||||
const Float dataC[]{0.0f, 1.0f, 2.0f, 3.0f, 4.0f, 5.0f, 6.0f, 7.0f}; |
||||
Trade::MeshData a{MeshPrimitive::LineLoop, |
||||
{}, indicesA, Trade::MeshIndexData{indicesA}, |
||||
{}, dataA, {Trade::MeshAttributeData{ |
||||
Trade::meshAttributeCustom(2), Containers::arrayView(dataA)}}}; |
||||
Trade::MeshData b{MeshPrimitive::LineLoop, |
||||
{}, indicesB, Trade::MeshIndexData{indicesB}, |
||||
{}, dataB, {Trade::MeshAttributeData{ |
||||
Trade::meshAttributeCustom(17), Containers::arrayView(dataB)}}}; |
||||
Trade::MeshData c{MeshPrimitive::LineLoop, |
||||
{}, indicesC, Trade::MeshIndexData{indicesC}, |
||||
{}, dataC, {Trade::MeshAttributeData{ |
||||
Trade::meshAttributeCustom(22), Containers::arrayView(dataC)}}}; |
||||
|
||||
Trade::MeshData result = MeshTools::combineIndexedAttributes({a, b, c}); |
||||
CORRADE_COMPARE(result.primitive(), MeshPrimitive::LineLoop); |
||||
CORRADE_VERIFY(result.isIndexed()); |
||||
CORRADE_COMPARE(result.indexType(), MeshIndexType::UnsignedInt); |
||||
CORRADE_COMPARE_AS(result.indices<UnsignedInt>(), |
||||
Containers::arrayView<UnsignedInt>({0, 1, 0, 2}), |
||||
TestSuite::Compare::Container); |
||||
|
||||
CORRADE_COMPARE(result.attributeCount(), 3); |
||||
CORRADE_COMPARE(result.attributeName(0), Trade::meshAttributeCustom(2)); |
||||
CORRADE_COMPARE(result.attributeFormat(0), VertexFormat::Int); |
||||
CORRADE_COMPARE_AS(result.attribute<Int>(0), |
||||
Containers::arrayView<Int>({0, 1, 2}), |
||||
TestSuite::Compare::Container); |
||||
CORRADE_COMPARE(result.attributeName(1), Trade::meshAttributeCustom(17)); |
||||
CORRADE_COMPARE(result.attributeFormat(1), VertexFormat::Short); |
||||
CORRADE_COMPARE_AS(result.attribute<Short>(1), |
||||
Containers::arrayView<Short>({1, 0, 2}), |
||||
TestSuite::Compare::Container); |
||||
CORRADE_COMPARE(result.attributeName(2), Trade::meshAttributeCustom(22)); |
||||
CORRADE_COMPARE(result.attributeFormat(2), VertexFormat::Float); |
||||
CORRADE_COMPARE_AS(result.attribute<Float>(2), |
||||
Containers::arrayView<Float>({7.0f, 6.0f, 5.0f}), |
||||
TestSuite::Compare::Container); |
||||
} |
||||
|
||||
void CombineTest::combineIndexedAttributesIndicesOnly() { |
||||
const UnsignedInt indicesA[]{2, 1, 2}; |
||||
const UnsignedShort indicesB[]{3, 4, 3}; |
||||
const UnsignedByte indicesC[]{7, 6, 7}; |
||||
Trade::MeshData a{MeshPrimitive::LineLoop, {}, indicesA, |
||||
Trade::MeshIndexData{indicesA}}; |
||||
Trade::MeshData b{MeshPrimitive::LineLoop, {}, indicesB, |
||||
Trade::MeshIndexData{indicesB}}; |
||||
Trade::MeshData c{MeshPrimitive::LineLoop, {}, indicesC, |
||||
Trade::MeshIndexData{indicesC}}; |
||||
|
||||
Trade::MeshData result = MeshTools::combineIndexedAttributes({a, b, c}); |
||||
CORRADE_COMPARE(result.primitive(), MeshPrimitive::LineLoop); |
||||
CORRADE_VERIFY(result.isIndexed()); |
||||
CORRADE_COMPARE(result.indexType(), MeshIndexType::UnsignedInt); |
||||
CORRADE_COMPARE_AS(result.indices<UnsignedInt>(), |
||||
Containers::arrayView<UnsignedInt>({0, 1, 0}), |
||||
TestSuite::Compare::Container); |
||||
CORRADE_COMPARE(result.attributeCount(), 0); |
||||
CORRADE_COMPARE(result.vertexCount(), 0); |
||||
} |
||||
|
||||
void CombineTest::combineIndexedAttributesNoMeshes() { |
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
MeshTools::combineIndexedAttributes({}); |
||||
CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): no meshes passed\n"); |
||||
} |
||||
|
||||
void CombineTest::combineIndexedAttributesNotIndexed() { |
||||
const UnsignedShort indices[5]{}; |
||||
Trade::MeshData a{MeshPrimitive::Lines, |
||||
{}, indices, Trade::MeshIndexData{indices}}; |
||||
Trade::MeshData b{MeshPrimitive::Lines, |
||||
{}, indices, Trade::MeshIndexData{indices}}; |
||||
Trade::MeshData c{MeshPrimitive::Lines, 5}; |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
MeshTools::combineIndexedAttributes({a, b, c}); |
||||
CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): data 2 is not indexed\n"); |
||||
} |
||||
|
||||
void CombineTest::combineIndexedAttributesDifferentPrimitive() { |
||||
const UnsignedShort indices[5]{}; |
||||
Trade::MeshData a{MeshPrimitive::Lines, |
||||
{}, indices, Trade::MeshIndexData{indices}}; |
||||
Trade::MeshData b{MeshPrimitive::Points, |
||||
{}, indices, Trade::MeshIndexData{indices}}; |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
MeshTools::combineIndexedAttributes({a, b}); |
||||
CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): data 1 is MeshPrimitive::Points but expected MeshPrimitive::Lines\n"); |
||||
} |
||||
|
||||
void CombineTest::combineIndexedAttributesDifferentIndexCount() { |
||||
const UnsignedShort indices[5]{}; |
||||
Trade::MeshData a{MeshPrimitive::Lines, |
||||
{}, indices, Trade::MeshIndexData{indices}}; |
||||
Trade::MeshData b{MeshPrimitive::Lines, |
||||
{}, indices, Trade::MeshIndexData{indices}}; |
||||
Trade::MeshData c{MeshPrimitive::Lines, |
||||
{}, indices, |
||||
Trade::MeshIndexData{Containers::arrayView(indices).prefix(4)}}; |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
MeshTools::combineIndexedAttributes({a, b, c}); |
||||
CORRADE_COMPARE(out.str(), "MeshTools::combineIndexedAttributes(): data 2 has 4 indices but expected 5\n"); |
||||
} |
||||
|
||||
}}}} |
||||
|
||||
CORRADE_TEST_MAIN(Magnum::MeshTools::Test::CombineTest) |
||||
Loading…
Reference in new issue