mirror of https://github.com/mosra/magnum.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
230 lines
12 KiB
230 lines
12 KiB
/* |
|
This file is part of Magnum. |
|
|
|
Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, |
|
2020, 2021, 2022 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 Containers::Iterable<const Trade::MeshData>& meshes) { |
|
UnsignedInt indexCount = 0; |
|
UnsignedInt vertexCount = 0; |
|
for(const Trade::MeshData& mesh: meshes) { |
|
/* 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 Containers::Iterable<const Trade::MeshData>& meshes, const char* const assertPrefix) { |
|
#ifdef CORRADE_NO_ASSERT |
|
static_cast<void>(assertPrefix); |
|
#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()}, |
|
attribute.arraySize()}; |
|
} |
|
|
|
/* Only list primitives are supported currently */ |
|
/** @todo delegate to `indexTriangleStrip()` (`duplicate*()`?) etc when |
|
those are done */ |
|
CORRADE_ASSERT( |
|
meshes.front().primitive() != MeshPrimitive::LineStrip && |
|
meshes.front().primitive() != MeshPrimitive::LineLoop && |
|
meshes.front().primitive() != MeshPrimitive::TriangleStrip && |
|
meshes.front().primitive() != MeshPrimitive::TriangleFan, |
|
assertPrefix << meshes.front().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{meshes.front().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.isEmpty() ? |
|
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. */ |
|
std::size_t indexOffset = 0; |
|
std::size_t vertexOffset = 0; |
|
for(std::size_t i = 0; i != meshes.size(); ++i) { |
|
const Trade::MeshData& mesh = meshes[i]; |
|
|
|
/* 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, |
|
(Trade::MeshData{MeshPrimitive{}, 0})); |
|
|
|
/* If the mesh is indexed, copy the indices over, expanded to 32bit */ |
|
if(mesh.isIndexed()) { |
|
CORRADE_ASSERT(!isMeshIndexTypeImplementationSpecific(mesh.indexType()), |
|
assertPrefix << "mesh" << i << "has an implementation-specific index type" << reinterpret_cast<void*>(meshIndexTypeUnwrap(mesh.indexType())), |
|
(Trade::MeshData{MeshPrimitive{}, 0})); |
|
|
|
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.isEmpty()) { |
|
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 << "attribute" << src, |
|
(Trade::MeshData{MeshPrimitive{}, 0})); |
|
CORRADE_ASSERT(out.attributeArraySize(dst) == mesh.attributeArraySize(src), |
|
assertPrefix << "expected array size" << out.attributeArraySize(dst) << "for attribute" << dst << "(" << Debug::nospace << out.attributeName(dst) << Debug::nospace << ") but got" << mesh.attributeArraySize(src) << "in mesh" << i << "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(const Containers::Iterable<const Trade::MeshData>& meshes, const InterleaveFlags flags) { |
|
CORRADE_ASSERT(!meshes.isEmpty(), |
|
"MeshTools::concatenate(): expected at least one mesh", |
|
(Trade::MeshData{MeshPrimitive::Points, 0})); |
|
#ifndef CORRADE_NO_ASSERT |
|
for(std::size_t i = 0; i != meshes.front().attributeCount(); ++i) { |
|
const VertexFormat format = meshes.front().attributeFormat(i); |
|
CORRADE_ASSERT(!isVertexFormatImplementationSpecific(format), |
|
"MeshTools::concatenate(): attribute" << i << "of the first mesh has an implementation-specific format" << reinterpret_cast<void*>(vertexFormatUnwrap(format)), |
|
(Trade::MeshData{MeshPrimitive::Points, 0})); |
|
} |
|
#endif |
|
|
|
/* 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(meshes.front().attributeCount()) |
|
attributeData = Implementation::interleavedLayout(Trade::MeshData{meshes.front().primitive(), |
|
{}, meshes.front().vertexData(), |
|
Trade::meshAttributeDataNonOwningArray(meshes.front().attributeData())}, {}, flags); |
|
else attributeData = |
|
Implementation::interleavedLayout(Trade::MeshData{meshes.front().primitive(), |
|
meshes.front().vertexCount()}, {}, flags); |
|
|
|
/* 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(meshes); |
|
Containers::Array<char> indexData{NoInit, |
|
indexVertexCount.first*sizeof(UnsignedInt)}; |
|
Containers::Array<char> vertexData{ValueInit, |
|
attributeData.isEmpty() ? 0 : (attributeData[0].stride()*indexVertexCount.second)}; |
|
return Implementation::concatenate(std::move(indexData), indexVertexCount.second, std::move(vertexData), std::move(attributeData), meshes, "MeshTools::concatenate():"); |
|
} |
|
|
|
}}
|
|
|