mirror of https://github.com/mosra/magnum.git
9 changed files with 1052 additions and 13 deletions
@ -0,0 +1,247 @@
|
||||
/*
|
||||
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 "Concatenate.h" |
||||
|
||||
#include <numeric> |
||||
#include <unordered_map> |
||||
#include <Corrade/Utility/Algorithms.h> |
||||
|
||||
namespace Magnum { namespace MeshTools { |
||||
|
||||
namespace Implementation { |
||||
|
||||
std::pair<UnsignedInt, UnsignedInt> concatenateIndexVertexCount(const Trade::MeshData& first, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> next) { |
||||
UnsignedInt indexCount = first.isIndexed() ? first.indexCount() : 0; |
||||
UnsignedInt vertexCount = first.vertexCount(); |
||||
for(const Trade::MeshData& mesh: next) { |
||||
/* If the mesh is indexed, add to index count. If this is the first
|
||||
indexed mesh, all previous meshes will have a trivial index buffer |
||||
generated for all their vertices */ |
||||
if(mesh.isIndexed()) { |
||||
if(!indexCount) indexCount = vertexCount; |
||||
indexCount += mesh.indexCount(); |
||||
|
||||
/* Otherwise, if some earlier mesh was indexed, this mesh will have a
|
||||
trivial index buffer generated for all its vertices */ |
||||
} else if(indexCount) indexCount += mesh.vertexCount(); |
||||
|
||||
vertexCount += mesh.vertexCount(); |
||||
} |
||||
|
||||
return {indexCount, vertexCount}; |
||||
} |
||||
|
||||
/* std::hash for enumeration types is only since C++14, so we need to make our
|
||||
own. It's amazing how extremely verbose this can get, ugh. */ |
||||
struct MeshAttributeHash: std::hash<typename std::underlying_type<Trade::MeshAttribute>::type> { |
||||
std::size_t operator()(Trade::MeshAttribute value) const { |
||||
return std::hash<typename std::underlying_type<Trade::MeshAttribute>::type>::operator()(static_cast<typename std::underlying_type<Trade::MeshAttribute>::type>(value)); |
||||
} |
||||
}; |
||||
|
||||
Trade::MeshData concatenate(Containers::Array<char>&& indexData, const UnsignedInt vertexCount, Containers::Array<char>&& vertexData, Containers::Array<Trade::MeshAttributeData>&& attributeData, const Trade::MeshData& first, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> next, const char* const assertPrefix, const std::size_t meshIndexOffset) { |
||||
#ifdef CORRADE_NO_ASSERT |
||||
static_cast<void>(assertPrefix); |
||||
static_cast<void>(meshIndexOffset); |
||||
#endif |
||||
|
||||
/* Convert the attributes from offset-only and zero vertex count to
|
||||
absolute, referencing the vertex data array */ |
||||
for(Trade::MeshAttributeData& attribute: attributeData) { |
||||
attribute = Trade::MeshAttributeData{ |
||||
attribute.name(), attribute.format(), |
||||
Containers::StridedArrayView1D<void>{vertexData, |
||||
vertexData + attribute.offset(vertexData), |
||||
vertexCount, attribute.stride()}}; |
||||
} |
||||
|
||||
/* Only list primitives are supported currently */ |
||||
/** @todo delegate to `indexTriangleStrip()` (`duplicate*()`?) etc when
|
||||
those are done */ |
||||
CORRADE_ASSERT( |
||||
first.primitive() != MeshPrimitive::LineStrip && |
||||
first.primitive() != MeshPrimitive::LineLoop && |
||||
first.primitive() != MeshPrimitive::TriangleStrip && |
||||
first.primitive() != MeshPrimitive::TriangleFan, |
||||
assertPrefix << first.primitive() << "is not supported, turn it into a plain indexed mesh first", |
||||
(Trade::MeshData{MeshPrimitive{}, 0})); |
||||
|
||||
/* Populate the resulting instance with what we have. It'll be used below
|
||||
for convenient access to vertex / index data */ |
||||
auto indices = Containers::arrayCast<UnsignedInt>(indexData); |
||||
Trade::MeshData out{first.primitive(), |
||||
/* If the index array is empty, we're creating a non-indexed mesh (not
|
||||
an indexed mesh with zero indices) */ |
||||
std::move(indexData), indices.empty() ? |
||||
Trade::MeshIndexData{} : Trade::MeshIndexData{indices}, |
||||
std::move(vertexData), std::move(attributeData), vertexCount}; |
||||
/* Create an attribute map. Yes, this is an inevitable fugly thing that
|
||||
allocates like mad, while everything else is zero-alloc. |
||||
Containers::HashMap can't be here soon enough. */ |
||||
std::unordered_multimap<Trade::MeshAttribute, std::pair<UnsignedInt, bool>, MeshAttributeHash> attributeMap; |
||||
attributeMap.reserve(out.attributeCount()); |
||||
for(UnsignedInt i = 0; i != out.attributeCount(); ++i) |
||||
attributeMap.emplace(out.attributeName(i), std::make_pair(i, false)); |
||||
|
||||
/* Go through all meshes and put all attributes and index arrays together.
|
||||
The first mesh might get separately and thus can't be a part of the |
||||
view, so abuse the *defined* unsigned integer overflow to add it to the |
||||
loop. This probably breaks all coding guidelines on earth tho. */ |
||||
std::size_t indexOffset = 0; |
||||
std::size_t vertexOffset = 0; |
||||
for(std::size_t i = ~std::size_t{}; i != next.size(); ++i) { |
||||
const Trade::MeshData& mesh = i == ~std::size_t{} ? first : next[i].get(); |
||||
|
||||
/* This won't fire for i == ~std::size_t{}, as that's where
|
||||
out.primitive() comes from */ |
||||
CORRADE_ASSERT(mesh.primitive() == out.primitive(), |
||||
assertPrefix << "expected" << out.primitive() << "but got" << mesh.primitive() << "in mesh" << i + meshIndexOffset, |
||||
(Trade::MeshData{MeshPrimitive{}, 0})); |
||||
|
||||
/* If the mesh is indexed, copy the indices over, expanded to 32bit */ |
||||
if(mesh.isIndexed()) { |
||||
Containers::ArrayView<UnsignedInt> dst = indices.slice(indexOffset, indexOffset + mesh.indexCount()); |
||||
mesh.indicesInto(dst); |
||||
indexOffset += mesh.indexCount(); |
||||
|
||||
/* Adjust indices for current vertex offset */ |
||||
for(UnsignedInt& index: dst) index += vertexOffset; |
||||
|
||||
/* Otherwise, if we need an index buffer (meaning at least one of the
|
||||
meshes is indexed), generate a trivial index buffer */ |
||||
} else if(!indices.empty()) { |
||||
std::iota(indices + indexOffset, indices + indexOffset + mesh.vertexCount(), UnsignedInt(vertexOffset)); |
||||
indexOffset += mesh.vertexCount(); |
||||
} |
||||
|
||||
/* Reset markers saying which attribute has already been copied */ |
||||
for(auto it = attributeMap.begin(); it != attributeMap.end(); ++it) |
||||
it->second.second = false; |
||||
|
||||
/* Copy attributes to their destination, skipping ones that don't have
|
||||
any equivalent in the destination mesh */ |
||||
for(UnsignedInt src = 0; src != mesh.attributeCount(); ++src) { |
||||
/* Go through destination attributes of the same name and find the
|
||||
earliest one that hasn't been copied yet */ |
||||
auto range = attributeMap.equal_range(mesh.attributeName(src)); |
||||
UnsignedInt dst = ~UnsignedInt{}; |
||||
auto found = attributeMap.end(); |
||||
for(auto it = range.first; it != range.second; ++it) { |
||||
if(it->second.second) continue; |
||||
|
||||
/* The range is unordered so we need to go through everything
|
||||
and pick one with smallest ID */ |
||||
if(it->second.first < dst) { |
||||
dst = it->second.first; |
||||
found = it; |
||||
} |
||||
} |
||||
|
||||
/* No corresponding attribute found, continue */ |
||||
if(dst == ~UnsignedInt{}) continue; |
||||
|
||||
/* Check format compatibility. This won't fire for i ==
|
||||
~std::size_t{}, as that's where out.primitive() comes from */ |
||||
CORRADE_ASSERT(out.attributeFormat(dst) == mesh.attributeFormat(src), |
||||
assertPrefix << "expected" << out.attributeFormat(dst) << "for attribute" << dst << "(" << Debug::nospace << out.attributeName(dst) << Debug::nospace << ") but got" << mesh.attributeFormat(src) << "in mesh" << i + meshIndexOffset << "attribute" << src, |
||||
(Trade::MeshData{MeshPrimitive{}, 0})); |
||||
|
||||
/* Copy the data to a slice of the output, mark the attribute as
|
||||
copied */ |
||||
Utility::copy(mesh.attribute(src), out.mutableAttribute(dst) |
||||
.slice(vertexOffset, vertexOffset + mesh.vertexCount())); |
||||
found->second.second = true; |
||||
} |
||||
|
||||
/* Update vertex offset for the next mesh */ |
||||
vertexOffset += mesh.vertexCount(); |
||||
} |
||||
|
||||
return out; |
||||
} |
||||
|
||||
} |
||||
|
||||
Trade::MeshData concatenate(Trade::MeshData&& first, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> next) { |
||||
/* If there's just a single non-empty mesh and its data is owned, pass it
|
||||
through, as it passes the guarantee that the returned data is always |
||||
owned. If it's empty, it doesn't matter that we drag it through the rest |
||||
as there will be no heavy allocation / copy made (and that also makes |
||||
tests easier to write). */ |
||||
if(first.indexDataFlags() & Trade::DataFlag::Owned && |
||||
first.vertexDataFlags() & Trade::DataFlag::Owned && |
||||
first.attributeCount() && first.vertexCount() && next.empty()) |
||||
return std::move(first); |
||||
|
||||
/* Calculate final attribute stride and offsets. Make a non-owning copy of
|
||||
the attribute data to avoid interleavedLayout() stealing the original |
||||
(we still need it to be able to reference the original data). If there's |
||||
no attributes in the original array, pass just vertex count --- |
||||
otherwise MeshData will assert on that to avoid it getting lost. */ |
||||
Containers::Array<Trade::MeshAttributeData> attributeData; |
||||
if(first.attributeCount()) |
||||
attributeData = Implementation::interleavedLayout(Trade::MeshData{first.primitive(), |
||||
{}, first.vertexData(), |
||||
Trade::meshAttributeDataNonOwningArray(first.attributeData())}, {}); |
||||
else attributeData = |
||||
Implementation::interleavedLayout(Trade::MeshData{first.primitive(), |
||||
first.vertexCount()}, {}); |
||||
|
||||
/* Calculate total index/vertex count and allocate the target memory.
|
||||
Index data are allocated with NoInit as the whole array will be written, |
||||
however vertex data might have holes and thus it's zero-initialized. */ |
||||
const std::pair<UnsignedInt, UnsignedInt> indexVertexCount = Implementation::concatenateIndexVertexCount(first, next); |
||||
Containers::Array<char> indexData{Containers::NoInit, |
||||
indexVertexCount.first*sizeof(UnsignedInt)}; |
||||
Containers::Array<char> vertexData{Containers::ValueInit, |
||||
attributeData.empty() ? 0 : (attributeData[0].stride()*indexVertexCount.second)}; |
||||
return Implementation::concatenate(std::move(indexData), indexVertexCount.second, std::move(vertexData), std::move(attributeData), first, next, "MeshTools::concatenate():", 0); |
||||
} |
||||
|
||||
Trade::MeshData concatenate(Trade::MeshData&& first, std::initializer_list<Containers::Reference<const Trade::MeshData>> next) { |
||||
return concatenate(std::move(first), Containers::arrayView(next)); |
||||
} |
||||
|
||||
Trade::MeshData concatenate(const Trade::MeshData& first, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> next) { |
||||
Containers::ArrayView<const char> indexData; |
||||
Trade::MeshIndexData indices; |
||||
if(first.isIndexed()) { |
||||
indexData = first.indexData(); |
||||
indices = Trade::MeshIndexData{first.indices()}; |
||||
} |
||||
|
||||
return concatenate(Trade::MeshData{first.primitive(), |
||||
{}, indexData, indices, |
||||
{}, first.vertexData(), Trade::meshAttributeDataNonOwningArray(first.attributeData()), |
||||
first.vertexCount(), |
||||
}, next); |
||||
} |
||||
|
||||
Trade::MeshData concatenate(const Trade::MeshData& first, std::initializer_list<Containers::Reference<const Trade::MeshData>> next) { |
||||
return concatenate(first, Containers::arrayView(next)); |
||||
} |
||||
|
||||
}} |
||||
@ -0,0 +1,153 @@
|
||||
#ifndef Magnum_MeshTools_Concatenate_h |
||||
#define Magnum_MeshTools_Concatenate_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::concatenate(), @ref Magnum::MeshTools::concatenateInto() |
||||
* @m_since_latest |
||||
*/ |
||||
|
||||
#include <Corrade/Containers/GrowableArray.h> |
||||
#include <Corrade/Containers/Reference.h> |
||||
|
||||
#include "Magnum/MeshTools/Interleave.h" |
||||
#include "Magnum/Trade/MeshData.h" |
||||
|
||||
namespace Magnum { namespace MeshTools { |
||||
|
||||
namespace Implementation { |
||||
MAGNUM_MESHTOOLS_EXPORT std::pair<UnsignedInt, UnsignedInt> concatenateIndexVertexCount(const Trade::MeshData& first, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> next); |
||||
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Containers::Array<char>&& indexData, UnsignedInt vertexCount, Containers::Array<char>&& vertexData, Containers::Array<Trade::MeshAttributeData>&& attributeData, const Trade::MeshData& first, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> next, const char* assertPrefix, std::size_t meshIndexOffset); |
||||
} |
||||
|
||||
/**
|
||||
@brief Concatenate meshes together |
||||
@m_since_latest |
||||
|
||||
The returned mesh contains vertices from all meshes concatenated together. If |
||||
any mesh is indexed, the resulting mesh is indexed as well, with indices |
||||
adjusted for vertex offsets of particular meshes. The behavior is undefined if |
||||
any mesh has indices out of bounds for its particular vertex count. |
||||
|
||||
All attributes from the @p first mesh are taken; for each mesh in @p next, |
||||
attributes present in @p first are copied, superfluous attributes ignored and |
||||
missing attributes zeroed out. Matching attributes are expected to have the |
||||
same type, all meshes are expected to have the same primitive. The vertex data |
||||
are concatenated in the same order as passed, with no duplicate removal. |
||||
Returned instance vertex and index data flags always have both |
||||
@ref Trade::DataFlag::Owned and @ref Trade::DataFlag::Mutable to guarante |
||||
mutable access to particular parts of the concatenated mesh --- for example for |
||||
applying transformations. |
||||
|
||||
If an index buffer is needed, @ref MeshIndexType::UnsignedInt is always used. |
||||
Call @ref compressIndices(const Trade::MeshData&, MeshIndexType) on the result |
||||
to compress it to a smaller type, if desired. |
||||
@see @ref concatenate(Trade::MeshData&&, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>>), |
||||
@ref concatenateInto() |
||||
*/ |
||||
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(const Trade::MeshData& first, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> next = {}); |
||||
|
||||
/**
|
||||
* @overload |
||||
* @m_since_latest |
||||
*/ |
||||
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(const Trade::MeshData& first, std::initializer_list<Containers::Reference<const Trade::MeshData>> next); |
||||
|
||||
/**
|
||||
@brief Concatenate meshes together |
||||
@m_since_latest |
||||
|
||||
Compared to @ref concatenate(const Trade::MeshData&, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>>), |
||||
if @p first has both vertex and index data owned and @p next is empty, it's |
||||
passed through without any extra allocations or other work. This can be used |
||||
for example to ensure a mesh is mutable in order to do various modifications on |
||||
its data: |
||||
|
||||
@snippet MagnumMeshTools.cpp concatenate-make-mutable |
||||
*/ |
||||
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Trade::MeshData&& first, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> next = {}); |
||||
|
||||
/**
|
||||
* @overload |
||||
* @m_since_latest |
||||
*/ |
||||
MAGNUM_MESHTOOLS_EXPORT Trade::MeshData concatenate(Trade::MeshData&& first, std::initializer_list<Containers::Reference<const Trade::MeshData>> next); |
||||
|
||||
/**
|
||||
@brief Concatenate a list of meshes into a pre-existing destination, enlarging it if necessary |
||||
@tparam Allocator Allocator to use |
||||
@param[in,out] destination Destination mesh from which the output arrays as |
||||
well as desired attribute layout is taken |
||||
@param[in] meshes Meshes to concatenate |
||||
@m_since_latest |
||||
|
||||
Compared to @ref concatenate(const Trade::MeshData&, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>>) this |
||||
function resizes existing index and vertex buffers in @p destination using |
||||
@ref Containers::arrayResize() and given @p allocator, and reuses its |
||||
atttribute data array instead of always allocating new ones. Only the attribute |
||||
layout from @p destination is used, all vertex/index data are taken from |
||||
@p meshes. Expects that @p meshes contains at least one item. |
||||
*/ |
||||
template<template<class> class Allocator = Containers::ArrayAllocator> void concatenateInto(Trade::MeshData& destination, const Containers::ArrayView<const Containers::Reference<const Trade::MeshData>> meshes) { |
||||
CORRADE_ASSERT(!meshes.empty(), |
||||
"MeshTools::concatenateInto(): no meshes passed", ); |
||||
|
||||
std::pair<UnsignedInt, UnsignedInt> indexVertexCount = Implementation::concatenateIndexVertexCount(meshes[0], meshes.suffix(1)); |
||||
|
||||
Containers::Array<char> indexData; |
||||
if(indexVertexCount.first) { |
||||
indexData = destination.releaseIndexData(); |
||||
/* Everything is overwritten here so we don't need to zero-out the
|
||||
memory */ |
||||
Containers::arrayResize<Allocator>(indexData, Containers::NoInit, indexVertexCount.first*sizeof(UnsignedInt)); |
||||
} |
||||
|
||||
Containers::Array<Trade::MeshAttributeData> attributeData = Implementation::interleavedLayout(std::move(destination), {}); |
||||
Containers::Array<char> vertexData; |
||||
if(!attributeData.empty() && indexVertexCount.second) { |
||||
const UnsignedInt attributeStride = attributeData[0].stride(); |
||||
vertexData = destination.releaseVertexData(); |
||||
/* Resize to 0 and then to the desired size to zero-out whatever was
|
||||
there, otherwise attributes that are not present in `meshes` would |
||||
be garbage */ |
||||
Containers::arrayResize<Allocator>(vertexData, 0); |
||||
Containers::arrayResize<Allocator>(vertexData, Containers::ValueInit, attributeStride*indexVertexCount.second); |
||||
} |
||||
|
||||
destination = Implementation::concatenate(std::move(indexData), indexVertexCount.second, std::move(vertexData), std::move(attributeData), meshes[0], meshes.suffix(1), "MeshTools::concatenateInto():", 1); |
||||
} |
||||
|
||||
/**
|
||||
* @overload |
||||
* @m_since_latest |
||||
*/ |
||||
template<template<class> class Allocator = Containers::ArrayAllocator> void concatenateInto(Trade::MeshData& destination, const std::initializer_list<Containers::Reference<const Trade::MeshData>> meshes) { |
||||
concatenateInto<Allocator>(destination, Containers::arrayView(meshes)); |
||||
} |
||||
|
||||
}} |
||||
|
||||
#endif |
||||
@ -0,0 +1,595 @@
|
||||
/*
|
||||
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/TestSuite/Tester.h> |
||||
#include <Corrade/TestSuite/Compare/Container.h> |
||||
#include <Corrade/Utility/DebugStl.h> |
||||
|
||||
#include "Magnum/Math/Color.h" |
||||
#include "Magnum/MeshTools/Concatenate.h" |
||||
|
||||
namespace Magnum { namespace MeshTools { namespace Test { namespace { |
||||
|
||||
struct ConcatenateTest: TestSuite::Tester { |
||||
explicit ConcatenateTest(); |
||||
|
||||
void concatenate(); |
||||
void concatenateNotIndexed(); |
||||
void concatenateNoAttributes(); |
||||
void concatenateNoAttributesNotIndexed(); |
||||
void concatenateOne(); |
||||
void concatenateOneRvalue(); |
||||
void concatenateInto(); |
||||
void concatenateIntoNoIndexArray(); |
||||
void concatenateIntoNonOwnedAttributeArray(); |
||||
|
||||
void concatenateUnsupportedPrimitive(); |
||||
void concatenateInconsistentPrimitive(); |
||||
void concatenateInconsistentAttributeType(); |
||||
void concatenateIntoNoMeshes(); |
||||
}; |
||||
|
||||
ConcatenateTest::ConcatenateTest() { |
||||
addTests({&ConcatenateTest::concatenate, |
||||
&ConcatenateTest::concatenateNotIndexed, |
||||
&ConcatenateTest::concatenateNoAttributes, |
||||
&ConcatenateTest::concatenateNoAttributesNotIndexed, |
||||
&ConcatenateTest::concatenateOne, |
||||
&ConcatenateTest::concatenateOneRvalue, |
||||
&ConcatenateTest::concatenateInto, |
||||
&ConcatenateTest::concatenateIntoNoIndexArray, |
||||
&ConcatenateTest::concatenateIntoNonOwnedAttributeArray, |
||||
|
||||
&ConcatenateTest::concatenateUnsupportedPrimitive, |
||||
&ConcatenateTest::concatenateInconsistentPrimitive, |
||||
&ConcatenateTest::concatenateInconsistentAttributeType, |
||||
&ConcatenateTest::concatenateIntoNoMeshes}); |
||||
} |
||||
|
||||
/* MSVC 2015 doesn't like unnamed bitfields in local structs, so thhis has to
|
||||
be outside */ |
||||
struct VertexDataA { |
||||
Vector2 texcoords1; |
||||
Vector2 texcoords2; |
||||
Int:32; |
||||
Vector3 position; |
||||
}; |
||||
|
||||
void ConcatenateTest::concatenate() { |
||||
using namespace Math::Literals; |
||||
|
||||
/* First is non-indexed, this layout (including the gap) will be
|
||||
preserved */ |
||||
const VertexDataA vertexDataA[]{ |
||||
{{0.1f, 0.2f}, {0.5f, 0.6f}, {1.0f, 2.0f, 3.0f}}, |
||||
{{0.3f, 0.4f}, {0.7f, 0.8f}, {4.0f, 5.0f, 6.0f}} |
||||
}; |
||||
Trade::MeshData a{MeshPrimitive::Points, {}, vertexDataA, { |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, |
||||
Containers::stridedArrayView(vertexDataA, |
||||
&vertexDataA[0].texcoords1, 2, sizeof(VertexDataA))}, |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, |
||||
Containers::stridedArrayView(vertexDataA, |
||||
&vertexDataA[0].texcoords2, 2, sizeof(VertexDataA))}, |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
Containers::stridedArrayView(vertexDataA, |
||||
&vertexDataA[0].position, 2, sizeof(VertexDataA))}, |
||||
}}; |
||||
|
||||
/* Second is indexed, has only one texture coordinate of the two, an extra
|
||||
color (which gets ignored) and misses the position (which will be |
||||
zero-filled) */ |
||||
const struct VertexDataB { |
||||
Color4 color; |
||||
Vector2 texcoords1; |
||||
} vertexDataB[]{ |
||||
{0x112233_rgbf, {0.15f, 0.25f}}, |
||||
{0x445566_rgbf, {0.35f, 0.45f}}, |
||||
{0x778899_rgbf, {0.55f, 0.65f}}, |
||||
{0xaabbcc_rgbf, {0.75f, 0.85f}} |
||||
}; |
||||
const UnsignedShort indicesB[]{0, 2, 1, 0, 3, 2}; |
||||
Trade::MeshData b{MeshPrimitive::Points, |
||||
{}, indicesB, Trade::MeshIndexData{indicesB}, {}, vertexDataB, { |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Color, |
||||
Containers::stridedArrayView(vertexDataB, |
||||
&vertexDataB[0].color, 4, sizeof(VertexDataB))}, |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, |
||||
Containers::stridedArrayView(vertexDataB, |
||||
&vertexDataB[0].texcoords1, 4, sizeof(VertexDataB))}, |
||||
}}; |
||||
|
||||
/* Third is again non-indexed, has one texcoord attribute more (which will
|
||||
get ignored). Additionally, attribute memory order is inversed and mixed |
||||
together to verify the attributes are picked based on declaration order, |
||||
not memory order. */ |
||||
const struct VertexDataC { |
||||
Vector2 texcoords2; |
||||
Vector3 position; |
||||
Vector2 texcoords3; |
||||
Vector2 texcoords1; |
||||
} vertexDataC[]{ |
||||
{{0.425f, 0.475f}, {1.5f, 2.5f, 3.5f}, {0.725f, 0.775f}, {0.125f, 0.175f}}, |
||||
{{0.525f, 0.575f}, {4.5f, 5.5f, 6.5f}, {0.825f, 0.875f}, {0.225f, 0.275f}}, |
||||
{{0.625f, 0.675f}, {7.5f, 8.5f, 9.5f}, {0.925f, 0.975f}, {0.325f, 0.375f}}, |
||||
}; |
||||
Trade::MeshData c{MeshPrimitive::Points, {}, vertexDataC, { |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, |
||||
Containers::stridedArrayView(vertexDataC, |
||||
&vertexDataC[0].texcoords1, 3, sizeof(VertexDataC))}, |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
Containers::stridedArrayView(vertexDataC, |
||||
&vertexDataC[0].position, 3, sizeof(VertexDataC))}, |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, |
||||
Containers::stridedArrayView(vertexDataC, |
||||
&vertexDataC[0].texcoords2, 3, sizeof(VertexDataC))}, |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, |
||||
Containers::stridedArrayView(vertexDataC, |
||||
&vertexDataC[0].texcoords3, 3, sizeof(VertexDataC))}, |
||||
}}; |
||||
|
||||
Trade::MeshData dst = MeshTools::concatenate(a, {b, c}); |
||||
CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); |
||||
CORRADE_COMPARE(dst.attributeCount(), 3); |
||||
CORRADE_COMPARE_AS(dst.attribute<Vector3>(Trade::MeshAttribute::Position), |
||||
Containers::arrayView<Vector3>({ |
||||
{1.0f, 2.0f, 3.0f}, |
||||
{4.0f, 5.0f, 6.0f}, |
||||
{}, {}, {}, {}, /* Missing in the second mesh */ |
||||
{1.5f, 2.5f, 3.5f}, |
||||
{4.5f, 5.5f, 6.5f}, |
||||
{7.5f, 8.5f, 9.5f} |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_COMPARE_AS(dst.attribute<Vector2>(Trade::MeshAttribute::TextureCoordinates), |
||||
Containers::arrayView<Vector2>({ |
||||
{0.1f, 0.2f}, |
||||
{0.3f, 0.4f}, |
||||
{0.15f, 0.25f}, |
||||
{0.35f, 0.45f}, |
||||
{0.55f, 0.65f}, |
||||
{0.75f, 0.85f}, |
||||
{0.125f, 0.175f}, |
||||
{0.225f, 0.275f}, |
||||
{0.325f, 0.375f} |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_COMPARE_AS(dst.attribute<Vector2>(Trade::MeshAttribute::TextureCoordinates, 1), |
||||
Containers::arrayView<Vector2>({ |
||||
{0.5f, 0.6f}, |
||||
{0.7f, 0.8f}, |
||||
{}, {}, {}, {}, /* Missing in the second mesh */ |
||||
{0.425f, 0.475f}, |
||||
{0.525f, 0.575f}, |
||||
{0.625f, 0.675f} |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_VERIFY(dst.isIndexed()); |
||||
CORRADE_COMPARE(dst.indexType(), MeshIndexType::UnsignedInt); |
||||
CORRADE_COMPARE_AS(dst.indices<UnsignedInt>(), |
||||
Containers::arrayView<UnsignedInt>({ |
||||
0, 1, /* implicit for the first nonindexed mesh */ |
||||
2, 4, 3, 2, 5, 4, /* offset for the second indexed mesh */ |
||||
6, 7, 8 /* implicit + offset for the third mesh */ |
||||
}), TestSuite::Compare::Container); |
||||
|
||||
/* The original interleaved layout should be preserved */ |
||||
CORRADE_VERIFY(isInterleaved(dst)); |
||||
CORRADE_COMPARE(dst.attributeStride(0), sizeof(VertexDataA)); |
||||
CORRADE_COMPARE(dst.attributeOffset(0), 0); |
||||
CORRADE_COMPARE(dst.attributeOffset(1), sizeof(Vector2)); |
||||
CORRADE_COMPARE(dst.attributeOffset(2), 2*sizeof(Vector2) + 4); |
||||
} |
||||
|
||||
void ConcatenateTest::concatenateNotIndexed() { |
||||
const Vector3 positionA[]{ |
||||
{1.0f, 2.0f, 3.0f}, |
||||
{4.0f, 5.0f, 6.0f} |
||||
}; |
||||
Trade::MeshData a{MeshPrimitive::Points, {}, positionA, { |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
Containers::arrayView(positionA)} |
||||
}}; |
||||
|
||||
const Vector3 positionB[]{ |
||||
{1.5f, 2.5f, 3.5f}, |
||||
{4.5f, 5.5f, 6.5f}, |
||||
{7.5f, 8.5f, 9.5f}, |
||||
}; |
||||
Trade::MeshData b{MeshPrimitive::Points, {}, positionB, { |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
Containers::arrayView(positionB)} |
||||
}}; |
||||
|
||||
Trade::MeshData dst = MeshTools::concatenate(a, {b, b}); |
||||
CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); |
||||
CORRADE_COMPARE(dst.attributeCount(), 1); |
||||
CORRADE_COMPARE_AS(dst.attribute<Vector3>(Trade::MeshAttribute::Position), |
||||
Containers::arrayView<Vector3>({ |
||||
{1.0f, 2.0f, 3.0f}, |
||||
{4.0f, 5.0f, 6.0f}, |
||||
{1.5f, 2.5f, 3.5f}, |
||||
{4.5f, 5.5f, 6.5f}, |
||||
{7.5f, 8.5f, 9.5f}, |
||||
{1.5f, 2.5f, 3.5f}, |
||||
{4.5f, 5.5f, 6.5f}, |
||||
{7.5f, 8.5f, 9.5f} |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_VERIFY(!dst.isIndexed()); |
||||
} |
||||
|
||||
void ConcatenateTest::concatenateNoAttributes() { |
||||
/* Compared to concatenate(), now the first and last is indexed */ |
||||
const UnsignedShort indicesA[]{1, 0}; |
||||
Trade::MeshData a{MeshPrimitive::Points, {}, indicesA, Trade::MeshIndexData{indicesA}, 2}; |
||||
|
||||
/* Second is not indexed, just a vertex count */ |
||||
Trade::MeshData b{MeshPrimitive::Points, 6}; |
||||
|
||||
const UnsignedByte indicesC[]{1, 0, 1, 0}; |
||||
Trade::MeshData c{MeshPrimitive::Points, {}, indicesC, Trade::MeshIndexData{indicesC}, 2}; |
||||
|
||||
Trade::MeshData dst = MeshTools::concatenate(a, {b, c}); |
||||
CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); |
||||
CORRADE_COMPARE(dst.attributeCount(), 0); |
||||
CORRADE_COMPARE(dst.vertexCount(), 10); |
||||
CORRADE_VERIFY(!dst.vertexData()); |
||||
CORRADE_VERIFY(dst.isIndexed()); |
||||
CORRADE_COMPARE(dst.indexType(), MeshIndexType::UnsignedInt); |
||||
CORRADE_COMPARE_AS(dst.indices<UnsignedInt>(), |
||||
Containers::arrayView<UnsignedInt>({ |
||||
1, 0, |
||||
2, 3, 4, 5, 6, 7, |
||||
9, 8, 9, 8 |
||||
}), TestSuite::Compare::Container); |
||||
} |
||||
|
||||
void ConcatenateTest::concatenateNoAttributesNotIndexed() { |
||||
Trade::MeshData a{MeshPrimitive::Points, 3}; |
||||
Trade::MeshData b{MeshPrimitive::Points, 6}; |
||||
Trade::MeshData c{MeshPrimitive::Points, 2}; |
||||
|
||||
Trade::MeshData dst = MeshTools::concatenate(a, {b, c}); |
||||
CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); |
||||
CORRADE_COMPARE(dst.attributeCount(), 0); |
||||
CORRADE_COMPARE(dst.vertexCount(), 11); |
||||
CORRADE_VERIFY(!dst.vertexData()); |
||||
CORRADE_VERIFY(!dst.isIndexed()); |
||||
} |
||||
|
||||
/* MSVC 2015 doesn't like unnamed bitfields in local structs, so thhis has to
|
||||
be outside */ |
||||
struct VertexDataNonInterleaved { |
||||
Vector2 texcoords1[2]; |
||||
Vector2 texcoords2[2]; |
||||
Int:32; |
||||
Int:32; |
||||
Vector3 position[2]; |
||||
}; |
||||
|
||||
void ConcatenateTest::concatenateOne() { |
||||
const VertexDataNonInterleaved vertexData[]{{ |
||||
{{0.1f, 0.2f}, |
||||
{0.3f, 0.4f}}, |
||||
{{0.5f, 0.6f}, |
||||
{0.7f, 0.8f}}, |
||||
{{1.0f, 2.0f, 3.0f}, |
||||
{4.0f, 5.0f, 6.0f}} |
||||
}}; |
||||
const UnsignedByte indices[]{1, 0, 1}; |
||||
Trade::MeshData a{MeshPrimitive::Points, |
||||
{}, indices, Trade::MeshIndexData{indices}, {}, vertexData, { |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, |
||||
Containers::arrayView(vertexData[0].texcoords1)}, |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::TextureCoordinates, |
||||
Containers::arrayView(vertexData[0].texcoords2)}, |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
Containers::arrayView(vertexData[0].position)}, |
||||
}}; |
||||
|
||||
Trade::MeshData dst = MeshTools::concatenate(a); |
||||
CORRADE_COMPARE(dst.primitive(), MeshPrimitive::Points); |
||||
CORRADE_COMPARE(dst.attributeCount(), 3); |
||||
CORRADE_COMPARE_AS(dst.attribute<Vector3>(Trade::MeshAttribute::Position), |
||||
Containers::arrayView<Vector3>({ |
||||
{1.0f, 2.0f, 3.0f}, |
||||
{4.0f, 5.0f, 6.0f} |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_COMPARE_AS(dst.attribute<Vector2>(Trade::MeshAttribute::TextureCoordinates), |
||||
Containers::arrayView<Vector2>({ |
||||
{0.1f, 0.2f}, |
||||
{0.3f, 0.4f} |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_COMPARE_AS(dst.attribute<Vector2>(Trade::MeshAttribute::TextureCoordinates, 1), |
||||
Containers::arrayView<Vector2>({ |
||||
{0.5f, 0.6f}, |
||||
{0.7f, 0.8f} |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_VERIFY(dst.isIndexed()); |
||||
CORRADE_COMPARE(dst.indexType(), MeshIndexType::UnsignedInt); |
||||
CORRADE_COMPARE_AS(dst.indices<UnsignedInt>(), |
||||
Containers::arrayView<UnsignedInt>({ |
||||
1, 0, 1 |
||||
}), TestSuite::Compare::Container); |
||||
|
||||
/* The mesh should get interleaved (w/o gaps) and owned */ |
||||
CORRADE_VERIFY(isInterleaved(dst)); |
||||
CORRADE_COMPARE(dst.attributeStride(0), 2*sizeof(Vector2) + sizeof(Vector3)); |
||||
CORRADE_COMPARE(dst.indexDataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); |
||||
CORRADE_COMPARE(dst.vertexDataFlags(), Trade::DataFlag::Owned|Trade::DataFlag::Mutable); |
||||
} |
||||
|
||||
void ConcatenateTest::concatenateOneRvalue() { |
||||
Containers::Array<char> vertexData{sizeof(Vector2)*4}; |
||||
auto positions = Containers::arrayCast<Vector2>(vertexData); |
||||
Containers::Array<char> indexData{sizeof(UnsignedInt)*6}; |
||||
auto indices = Containers::arrayCast<UnsignedInt>(indexData); |
||||
Trade::MeshAttributeData attributeData[]{ |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, positions} |
||||
}; |
||||
|
||||
/* The result should be just a pass-through, as both index and vertex data
|
||||
are already owned */ |
||||
Trade::MeshData dst = MeshTools::concatenate(Trade::MeshData{ |
||||
MeshPrimitive::Triangles, |
||||
std::move(indexData), Trade::MeshIndexData{indices}, |
||||
std::move(vertexData), Trade::meshAttributeDataNonOwningArray(attributeData)}, |
||||
/* Explicitly pass an empty init list to ensure this overload is
|
||||
covered as well */ |
||||
std::initializer_list<Containers::Reference<const Trade::MeshData>>{}); |
||||
CORRADE_COMPARE(dst.indexData().data(), static_cast<void*>(indices.data())); |
||||
CORRADE_COMPARE(dst.vertexData().data(), static_cast<void*>(positions.data())); |
||||
} |
||||
|
||||
void ConcatenateTest::concatenateInto() { |
||||
Containers::Array<Trade::MeshAttributeData> attributeData{2}; |
||||
Containers::Array<char> vertexData; |
||||
Containers::Array<char> indexData; |
||||
arrayResize(vertexData, Containers::DirectInit, (sizeof(Vector2) + sizeof(Vector3))*7, '\xff'); |
||||
arrayResize(vertexData, 0); |
||||
arrayResize(indexData, Containers::DirectInit, sizeof(UnsignedInt)*9, '\xff'); |
||||
arrayResize(indexData, 0); |
||||
const void* attributeDataPointer = attributeData; |
||||
const void* vertexDataPointer = vertexData; |
||||
const void* indexDataPointer = indexData; |
||||
|
||||
attributeData[0] = Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
VertexFormat::Vector2, nullptr}; |
||||
attributeData[1] = Trade::MeshAttributeData{Trade::MeshAttribute::Normal, |
||||
VertexFormat::Vector3, nullptr}; |
||||
Trade::MeshIndexData indices{MeshIndexType::UnsignedInt, indexData}; |
||||
Trade::MeshData dst{MeshPrimitive::Triangles, |
||||
std::move(indexData), indices, |
||||
std::move(vertexData), std::move(attributeData)}; |
||||
|
||||
const Vector2 positionsA[]{ |
||||
{-1.0f, -1.0f}, |
||||
{ 1.0f, -1.0f}, |
||||
{-1.0f, 1.0f}, |
||||
{ 1.0f, 1.0f} |
||||
}; |
||||
const UnsignedShort indicesA[]{ |
||||
0, 1, 2, 2, 1, 3 |
||||
}; |
||||
Trade::MeshData a{MeshPrimitive::Triangles, |
||||
{}, indicesA, Trade::MeshIndexData{indicesA}, |
||||
{}, positionsA, { |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
Containers::arrayView(positionsA)} |
||||
}}; |
||||
|
||||
const Vector2 positionsB[]{ |
||||
{-1.0f, -1.0f}, |
||||
{ 1.0f, -1.0f}, |
||||
{ 0.0f, 1.0f} |
||||
}; |
||||
Trade::MeshData b{MeshPrimitive::Triangles, |
||||
{}, positionsB, { |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
Containers::arrayView(positionsB)} |
||||
}}; |
||||
|
||||
MeshTools::concatenateInto(dst, {a, b}); |
||||
CORRADE_COMPARE(dst.attributeCount(), 2); |
||||
CORRADE_COMPARE_AS(dst.attribute<Vector2>(Trade::MeshAttribute::Position), |
||||
Containers::arrayView<Vector2>({ |
||||
{-1.0f, -1.0f}, |
||||
{ 1.0f, -1.0f}, |
||||
{-1.0f, 1.0f}, |
||||
{ 1.0f, 1.0f}, |
||||
{-1.0f, -1.0f}, |
||||
{ 1.0f, -1.0f}, |
||||
{ 0.0f, 1.0f} |
||||
}), TestSuite::Compare::Container); |
||||
/* The normal isn't present in any attribute and thus should be zeroed out
|
||||
(*not* the whatever garbage present there from before) */ |
||||
CORRADE_COMPARE_AS(dst.attribute<Vector3>(Trade::MeshAttribute::Normal), |
||||
Containers::arrayView<Vector3>({ |
||||
{}, {}, {}, {}, {}, {}, {} |
||||
}), TestSuite::Compare::Container); |
||||
CORRADE_VERIFY(dst.isIndexed()); |
||||
CORRADE_COMPARE_AS(dst.indices<UnsignedInt>(), |
||||
Containers::arrayView<UnsignedInt>({ |
||||
0, 1, 2, 2, 1, 3, |
||||
4, 5, 6 |
||||
}), TestSuite::Compare::Container); |
||||
|
||||
/* Verify that no reallocation happened */ |
||||
CORRADE_COMPARE(dst.attributeData().size(), 2); |
||||
CORRADE_COMPARE(dst.attributeData().data(), attributeDataPointer); |
||||
CORRADE_COMPARE(dst.vertexData().size(), 7*(sizeof(Vector2) + sizeof(Vector3))); |
||||
CORRADE_COMPARE(dst.vertexData().data(), vertexDataPointer); |
||||
CORRADE_COMPARE(dst.indexData().size(), 9*sizeof(UnsignedInt)); |
||||
CORRADE_COMPARE(dst.indexData().data(), indexDataPointer); |
||||
} |
||||
|
||||
void ConcatenateTest::concatenateIntoNoIndexArray() { |
||||
Containers::Array<Trade::MeshAttributeData> attributeData{1}; |
||||
Containers::Array<char> vertexData; |
||||
Containers::Array<char> indexData; |
||||
arrayReserve(vertexData, sizeof(Vector2)*3); |
||||
arrayReserve(indexData, sizeof(UnsignedInt)); |
||||
const void* attributeDataPointer = attributeData; |
||||
const void* vertexDataPointer = vertexData; |
||||
|
||||
attributeData[0] = Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
VertexFormat::Vector2, nullptr}; |
||||
Trade::MeshIndexData indices{MeshIndexType::UnsignedInt, indexData}; |
||||
Trade::MeshData dst{MeshPrimitive::Triangles, |
||||
std::move(indexData), indices, |
||||
std::move(vertexData), std::move(attributeData)}; |
||||
CORRADE_VERIFY(dst.isIndexed()); |
||||
|
||||
const Vector2 positions[]{ |
||||
{-1.0f, -1.0f}, |
||||
{ 1.0f, -1.0f}, |
||||
{ 0.0f, 1.0f} |
||||
}; |
||||
Trade::MeshData a{MeshPrimitive::Triangles, |
||||
{}, positions, { |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
Containers::arrayView(positions)} |
||||
}}; |
||||
|
||||
MeshTools::concatenateInto(dst, {a}); |
||||
CORRADE_COMPARE(dst.attributeCount(), 1); |
||||
CORRADE_COMPARE_AS(dst.attribute<Vector2>(Trade::MeshAttribute::Position), |
||||
Containers::arrayView<Vector2>({ |
||||
{-1.0f, -1.0f}, |
||||
{ 1.0f, -1.0f}, |
||||
{ 0.0f, 1.0f} |
||||
}), TestSuite::Compare::Container); |
||||
|
||||
/* The index array gets removed, but no reallocation happens for the other
|
||||
two */ |
||||
CORRADE_VERIFY(!dst.isIndexed()); |
||||
CORRADE_COMPARE(dst.attributeData().size(), 1); |
||||
CORRADE_COMPARE(dst.attributeData().data(), attributeDataPointer); |
||||
CORRADE_COMPARE(dst.vertexData().size(), 3*sizeof(Vector2)); |
||||
CORRADE_COMPARE(dst.vertexData().data(), vertexDataPointer); |
||||
} |
||||
|
||||
void ConcatenateTest::concatenateIntoNonOwnedAttributeArray() { |
||||
Containers::Array<char> vertexData; |
||||
arrayReserve(vertexData, sizeof(Vector2)*3); |
||||
const void* vertexDataPointer = vertexData; |
||||
|
||||
const Trade::MeshAttributeData attributeData[]{ |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
VertexFormat::Vector2, nullptr} |
||||
}; |
||||
Trade::MeshData dst{MeshPrimitive::Triangles, |
||||
std::move(vertexData), Trade::meshAttributeDataNonOwningArray(attributeData)}; |
||||
|
||||
const Vector2 positions[]{ |
||||
{-1.0f, -1.0f}, |
||||
{ 1.0f, -1.0f}, |
||||
{ 0.0f, 1.0f} |
||||
}; |
||||
Trade::MeshData a{MeshPrimitive::Triangles, |
||||
{}, positions, { |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
Containers::arrayView(positions)} |
||||
}}; |
||||
|
||||
MeshTools::concatenateInto(dst, {a}); |
||||
CORRADE_COMPARE(dst.attributeCount(), 1); |
||||
CORRADE_COMPARE_AS(dst.attribute<Vector2>(Trade::MeshAttribute::Position), |
||||
Containers::arrayView<Vector2>({ |
||||
{-1.0f, -1.0f}, |
||||
{ 1.0f, -1.0f}, |
||||
{ 0.0f, 1.0f} |
||||
}), TestSuite::Compare::Container); |
||||
|
||||
/* Reallocation happens only for the attribute data as it's not owned */ |
||||
CORRADE_VERIFY(!dst.isIndexed()); |
||||
CORRADE_COMPARE(dst.attributeData().size(), 1); |
||||
CORRADE_VERIFY(dst.attributeData().data() != attributeData); |
||||
CORRADE_COMPARE(dst.vertexData().size(), 3*sizeof(Vector2)); |
||||
CORRADE_COMPARE(dst.vertexData().data(), vertexDataPointer); |
||||
} |
||||
|
||||
void ConcatenateTest::concatenateUnsupportedPrimitive() { |
||||
Trade::MeshData a{MeshPrimitive::TriangleStrip, 0}; |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
MeshTools::concatenate(a); |
||||
MeshTools::concatenateInto(a, {a}); |
||||
CORRADE_COMPARE(out.str(), |
||||
"MeshTools::concatenate(): MeshPrimitive::TriangleStrip is not supported, turn it into a plain indexed mesh first\n" |
||||
"MeshTools::concatenateInto(): MeshPrimitive::TriangleStrip is not supported, turn it into a plain indexed mesh first\n"); |
||||
} |
||||
|
||||
void ConcatenateTest::concatenateInconsistentPrimitive() { |
||||
/* Things are a bit duplicated to test correct numbering */ |
||||
Trade::MeshData a{MeshPrimitive::Triangles, 0}; |
||||
Trade::MeshData b{MeshPrimitive::Lines, 0}; |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
MeshTools::concatenate(a, {a, b}); |
||||
MeshTools::concatenateInto(a, {a, b}); |
||||
CORRADE_COMPARE(out.str(), |
||||
"MeshTools::concatenate(): expected MeshPrimitive::Triangles but got MeshPrimitive::Lines in mesh 1\n" |
||||
"MeshTools::concatenateInto(): expected MeshPrimitive::Triangles but got MeshPrimitive::Lines in mesh 1\n"); |
||||
} |
||||
|
||||
void ConcatenateTest::concatenateInconsistentAttributeType() { |
||||
/* Things are a bit duplicated to test correct numbering */ |
||||
Trade::MeshData a{MeshPrimitive::Lines, nullptr, { |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
VertexFormat::Vector3, nullptr}, |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
VertexFormat::Vector3, nullptr}, |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Color, |
||||
VertexFormat::Vector3ubNormalized, nullptr} |
||||
}}; |
||||
Trade::MeshData b{MeshPrimitive::Lines, nullptr, { |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Position, |
||||
VertexFormat::Vector3, nullptr}, |
||||
Trade::MeshAttributeData{Trade::MeshAttribute::Color, |
||||
VertexFormat::Vector3usNormalized, nullptr} |
||||
}}; |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
MeshTools::concatenate(a, {a, a, a, b}); |
||||
MeshTools::concatenateInto(a, {a, a, a, b}); |
||||
CORRADE_COMPARE(out.str(), |
||||
"MeshTools::concatenate(): expected VertexFormat::Vector3ubNormalized for attribute 2 (Trade::MeshAttribute::Color) but got VertexFormat::Vector3usNormalized in mesh 3 attribute 1\n" |
||||
"MeshTools::concatenateInto(): expected VertexFormat::Vector3ubNormalized for attribute 2 (Trade::MeshAttribute::Color) but got VertexFormat::Vector3usNormalized in mesh 3 attribute 1\n"); |
||||
} |
||||
|
||||
void ConcatenateTest::concatenateIntoNoMeshes() { |
||||
Trade::MeshData destination{MeshPrimitive::Triangles, 0}; |
||||
|
||||
std::ostringstream out; |
||||
Error redirectError{&out}; |
||||
MeshTools::concatenateInto(destination, {}); |
||||
CORRADE_COMPARE(out.str(), "MeshTools::concatenateInto(): no meshes passed\n"); |
||||
} |
||||
|
||||
}}}} |
||||
|
||||
CORRADE_TEST_MAIN(Magnum::MeshTools::Test::ConcatenateTest) |
||||
Loading…
Reference in new issue