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