Browse Source

MeshTools: implemented concatenate() and concatenateInto().

pull/371/head
Vladimír Vondruš 6 years ago
parent
commit
393ba7a088
  1. 2
      doc/changelog.dox
  2. 12
      doc/snippets/MagnumMeshTools.cpp
  3. 2
      src/Magnum/MeshTools/CMakeLists.txt
  4. 247
      src/Magnum/MeshTools/Concatenate.cpp
  5. 153
      src/Magnum/MeshTools/Concatenate.h
  6. 48
      src/Magnum/MeshTools/Interleave.cpp
  7. 3
      src/Magnum/MeshTools/Interleave.h
  8. 3
      src/Magnum/MeshTools/Test/CMakeLists.txt
  9. 595
      src/Magnum/MeshTools/Test/ConcatenateTest.cpp

2
doc/changelog.dox

@ -133,6 +133,8 @@ See also:
differently indexed attributes into a single index buffer, and
@ref MeshTools::combineFaceAttributes() for converting per-face attributes
into per-vertex
- New @ref MeshTools::concatenate() and @ref MeshTools::concatenateInto()
tool for batching multiple generic meshes together
@subsubsection changelog-latest-new-platform Platform libraries

12
doc/snippets/MagnumMeshTools.cpp

@ -26,11 +26,14 @@
#include "Magnum/Math/Color.h"
#include "Magnum/Math/FunctionsBatch.h"
#include "Magnum/MeshTools/CompressIndices.h"
#include "Magnum/MeshTools/Concatenate.h"
#include "Magnum/MeshTools/Duplicate.h"
#include "Magnum/MeshTools/FlipNormals.h"
#include "Magnum/MeshTools/GenerateNormals.h"
#include "Magnum/MeshTools/Interleave.h"
#include "Magnum/MeshTools/RemoveDuplicates.h"
#include "Magnum/MeshTools/Transform.h"
#include "Magnum/Primitives/Cube.h"
#include "Magnum/Trade/MeshData.h"
#ifdef MAGNUM_BUILD_DEPRECATED
@ -74,6 +77,15 @@ std::pair<Containers::Array<char>, MeshIndexType> result =
/* [compressIndices-offset] */
}
{
/* [concatenate-make-mutable] */
/* Flip triangles on a cube primitive so it's counterclockwise from the inside
in order to render a cube map */
Trade::MeshData mesh = MeshTools::concatenate(Primitives::cubeSolid());
MeshTools::flipFaceWindingInPlace(mesh.mutableIndices());
/* [concatenate-make-mutable] */
}
#ifdef MAGNUM_BUILD_DEPRECATED
{
CORRADE_IGNORE_DEPRECATED_PUSH

2
src/Magnum/MeshTools/CMakeLists.txt

@ -31,6 +31,7 @@ set(MagnumMeshTools_SRCS
set(MagnumMeshTools_GracefulAssert_SRCS
Combine.cpp
CompressIndices.cpp
Concatenate.cpp
Duplicate.cpp
FlipNormals.cpp
GenerateNormals.cpp
@ -40,6 +41,7 @@ set(MagnumMeshTools_GracefulAssert_SRCS
set(MagnumMeshTools_HEADERS
Combine.h
CompressIndices.h
Concatenate.h
Duplicate.h
FlipNormals.h
GenerateNormals.h

247
src/Magnum/MeshTools/Concatenate.cpp

@ -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));
}
}}

153
src/Magnum/MeshTools/Concatenate.h

@ -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

48
src/Magnum/MeshTools/Interleave.cpp

@ -75,11 +75,11 @@ Containers::StridedArrayView2D<const char> interleavedData(const Trade::MeshData
return out;
}
Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vertexCount, const Containers::ArrayView<const Trade::MeshAttributeData> extra) {
/* If there are no attributes, bail -- return an empty mesh with desired
vertex count but nothing else */
if(!data.attributeCount() && extra.empty())
return Trade::MeshData{data.primitive(), vertexCount};
namespace Implementation {
Containers::Array<Trade::MeshAttributeData> interleavedLayout(Trade::MeshData&& data, const Containers::ArrayView<const Trade::MeshAttributeData> extra) {
/* Nothing to do here, bye! */
if(!data.attributeCount() && extra.empty()) return {};
const bool interleaved = isInterleaved(data);
@ -105,7 +105,7 @@ Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vert
for(std::size_t i = 0; i != extra.size(); ++i) {
if(extra[i].format() == VertexFormat{}) {
CORRADE_ASSERT(extra[i].stride() > 0 || stride >= std::size_t(-extra[i].stride()),
"MeshTools::interleavedLayout(): negative padding" << extra[i].stride() << "in extra attribute" << i << "too large for stride" << stride, (Trade::MeshData{MeshPrimitive::Points, 0}));
"MeshTools::interleavedLayout(): negative padding" << extra[i].stride() << "in extra attribute" << i << "too large for stride" << stride, {});
stride += extra[i].stride();
} else {
stride += vertexFormatSize(extra[i].format());
@ -131,9 +131,6 @@ Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vert
Utility::copy(originalAttributeData, attributeData.prefix(originalAttributeCount));
}
/* Allocate new data array */
Containers::Array<char> vertexData{Containers::NoInit, stride*vertexCount};
/* Copy existing attribute layout. If the original is already interleaved,
preserve relative attribute offsets, otherwise pack tightly. */
std::size_t offset = 0;
@ -142,8 +139,7 @@ Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vert
attributeData[i] = Trade::MeshAttributeData{
attributeData[i].name(), attributeData[i].format(),
Containers::StridedArrayView1D<void>{vertexData, vertexData + offset,
vertexCount, std::ptrdiff_t(stride)}};
offset, 0, std::ptrdiff_t(stride)};
if(!interleaved) offset += vertexFormatSize(attributeData[i].format());
}
@ -164,12 +160,38 @@ Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vert
}
attributeData[attributeIndex++] = Trade::MeshAttributeData{
extra[i].name(), extra[i].format(), Containers::StridedArrayView1D<void>{vertexData, vertexData + offset,
vertexCount, std::ptrdiff_t(stride)}};
extra[i].name(), extra[i].format(),
offset, 0, std::ptrdiff_t(stride)};
offset += vertexFormatSize(extra[i].format());
}
return attributeData;
}
}
Trade::MeshData interleavedLayout(Trade::MeshData&& data, const UnsignedInt vertexCount, const Containers::ArrayView<const Trade::MeshAttributeData> extra) {
Containers::Array<Trade::MeshAttributeData> attributeData = Implementation::interleavedLayout(std::move(data), extra);
/* If there are no attributes, bail -- return an empty mesh with desired
vertex count but nothing else */
if(!attributeData)
return Trade::MeshData{data.primitive(), vertexCount};
/* Allocate new data array */
Containers::Array<char> vertexData{Containers::NoInit, attributeData[0].stride()*vertexCount};
/* Convert the attributes from offset-only and zero vertex count to
absolute, referencing the above-allocated 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()}};
}
return Trade::MeshData{data.primitive(), std::move(vertexData), std::move(attributeData)};
}

3
src/Magnum/MeshTools/Interleave.h

@ -114,6 +114,9 @@ template<class T, class ...U> void writeInterleaved(std::size_t stride, char* st
writeInterleaved(stride, startingOffset + writeOneInterleaved(stride, startingOffset, first), next...);
}
/* Used internally by interleavedLayout() and concatenate() */
MAGNUM_MESHTOOLS_EXPORT Containers::Array<Trade::MeshAttributeData> interleavedLayout(Trade::MeshData&& data, Containers::ArrayView<const Trade::MeshAttributeData> extra);
}
/**

3
src/Magnum/MeshTools/Test/CMakeLists.txt

@ -25,6 +25,7 @@
corrade_add_test(MeshToolsCombineTest CombineTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsCompressIndicesTest CompressIndicesTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsConcatenateTest ConcatenateTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsDuplicateTest DuplicateTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsFlipNormalsTest FlipNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib)
corrade_add_test(MeshToolsGenerateNormalsTest GenerateNormalsTest.cpp LIBRARIES MagnumMeshToolsTestLib MagnumPrimitives)
@ -37,6 +38,7 @@ corrade_add_test(MeshToolsSubdivideRemov___Benchmark SubdivideRemoveDuplicatesBe
# Graceful assert for testing
set_property(TARGET
MeshToolsConcatenateTest
MeshToolsDuplicateTest
MeshToolsInterleaveTest
MeshToolsRemoveDuplicatesTest
@ -46,6 +48,7 @@ set_property(TARGET
set_target_properties(
MeshToolsCombineTest
MeshToolsCompressIndicesTest
MeshToolsConcatenateTest
MeshToolsDuplicateTest
MeshToolsFlipNormalsTest
MeshToolsGenerateNormalsTest

595
src/Magnum/MeshTools/Test/ConcatenateTest.cpp

@ -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…
Cancel
Save