/* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 Vladimír Vondruš 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 "Interleave.h" #include #include "Magnum/Math/Functions.h" #include "Magnum/Trade/MeshData.h" namespace Magnum { namespace MeshTools { bool isInterleaved(const Trade::MeshData& data) { /* There is nothing, so yes it is (because there is nothing we could do to make it interleaved anyway) */ if(!data.attributeCount()) return true; const UnsignedInt stride = data.attributeStride(0); std::size_t minOffset = data.attributeOffset(0); std::size_t maxOffset = minOffset; for(UnsignedInt i = 1; i != data.attributeCount(); ++i) { if(data.attributeStride(i) != stride) return false; const std::size_t offset = data.attributeOffset(i); minOffset = Math::min(minOffset, offset); maxOffset = Math::max(maxOffset, offset + vertexFormatSize(data.attributeFormat(i))); } return maxOffset - minOffset <= stride; } Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt vertexCount, const Containers::ArrayView 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}; const bool interleaved = isInterleaved(data); /* If the mesh is already interleaved, use the original stride to preserve all padding, but remove the initial offset. Otherwise calculate a tightly-packed stride. */ std::size_t stride; std::size_t minOffset; if(interleaved && data.attributeCount()) { stride = data.attributeStride(0); minOffset = ~std::size_t{}; for(UnsignedInt i = 0, max = data.attributeCount(); i != max; ++i) minOffset = Math::min(minOffset, data.attributeOffset(i)); } else { stride = 0; minOffset = 0; for(UnsignedInt i = 0, max = data.attributeCount(); i != max; ++i) stride += vertexFormatSize(data.attributeFormat(i)); } /* Add the extra attributes and explicit padding */ std::size_t extraAttributeCount = 0; for(std::size_t i = 0; i != extra.size(); ++i) { if(extra[i].format() == VertexFormat{}) { CORRADE_ASSERT(extra[i].data().stride() > 0 || stride >= std::size_t(-extra[i].data().stride()), "MeshTools::interleavedLayout(): negative padding" << extra[i].data().stride() << "in extra attribute" << i << "too large for stride" << stride, (Trade::MeshData{MeshPrimitive::Points, 0})); stride += extra[i].data().stride(); } else { stride += vertexFormatSize(extra[i].format()); ++extraAttributeCount; } } /* Allocate new data and attribute array */ Containers::Array vertexData{Containers::NoInit, stride*vertexCount}; Containers::Array attributeData{data.attributeCount() + extraAttributeCount}; /* Copy existing attribute layout. If the original is already interleaved, preserve relative attribute offsets, otherwise pack tightly. */ std::size_t offset = 0; for(UnsignedInt i = 0; i != data.attributeCount(); ++i) { if(interleaved) offset = data.attributeOffset(i) - minOffset; attributeData[i] = Trade::MeshAttributeData{ data.attributeName(i), data.attributeFormat(i), Containers::StridedArrayView1D{vertexData, vertexData + offset, vertexCount, std::ptrdiff_t(stride)}}; if(!interleaved) offset += vertexFormatSize(data.attributeFormat(i)); } /* In case the original is already interleaved, set the offset for extra attribs to the original stride to preserve also potential padding at the end. */ if(interleaved && data.attributeCount()) offset = data.attributeStride(0); /* Mix in the extra attributes */ UnsignedInt attributeIndex = data.attributeCount(); for(UnsignedInt i = 0; i != extra.size(); ++i) { /* Padding, only adjust the offset for next attribute */ if(extra[i].format() == VertexFormat{}) { offset += extra[i].data().stride(); continue; } attributeData[attributeIndex++] = Trade::MeshAttributeData{ extra[i].name(), extra[i].format(), Containers::StridedArrayView1D{vertexData, vertexData + offset, vertexCount, std::ptrdiff_t(stride)}}; offset += vertexFormatSize(extra[i].format()); } return Trade::MeshData{data.primitive(), std::move(vertexData), std::move(attributeData)}; } Trade::MeshData interleavedLayout(const Trade::MeshData& data, const UnsignedInt vertexCount, const std::initializer_list extra) { return interleavedLayout(data, vertexCount, Containers::arrayView(extra)); } Trade::MeshData interleave(Trade::MeshData&& data, const Containers::ArrayView extra) { /* If there are no attributes and no index buffer, bail -- the vertex count is the only property we can transfer. If this wouldn't be done, the return at the end would assert as vertex count is only passed implicitly via attributes (which there are none). */ if(!data.attributeCount() && extra.empty() && !data.isIndexed()) return Trade::MeshData{data.primitive(), data.vertexCount()}; /* Transfer the indices unchanged, in case the mesh is indexed */ Containers::Array indexData; Trade::MeshIndexData indices; if(data.isIndexed()) { /* If we can steal the data, do it */ if(data.indexDataFlags() & Trade::DataFlag::Owned) { indices = Trade::MeshIndexData{data.indices()}; indexData = data.releaseIndexData(); } else { indexData = Containers::Array{data.indexData().size()}; Utility::copy(data.indexData(), indexData); indices = Trade::MeshIndexData{data.indexType(), Containers::ArrayView{indexData + data.indexOffset(), data.indices().size()[0]*data.indices().size()[1]}}; } } const bool interleaved = isInterleaved(data); /* If the mesh is already interleaved and we don't have anything extra, steal that data as well */ Containers::Array vertexData; Containers::Array attributeData; if(interleaved && extra.empty() && (data.vertexDataFlags() & Trade::DataFlag::Owned)) { attributeData = data.releaseAttributeData(); vertexData = data.releaseVertexData(); /* Otherwise do it the hard way */ } else { /* Calculate the layout */ Trade::MeshData layout = interleavedLayout(data, data.vertexCount(), extra); /* Copy existing attributes to new locations */ for(UnsignedInt i = 0; i != data.attributeCount(); ++i) Utility::copy(data.attribute(i), layout.mutableAttribute(i)); /* Mix in the extra attributes */ UnsignedInt attributeIndex = data.attributeCount(); for(UnsignedInt i = 0; i != extra.size(); ++i) { /* Padding, ignore */ if(extra[i].format() == VertexFormat{}) continue; /* Asserting here even though data() has another assert since that one would be too confusing in this context */ CORRADE_ASSERT(!extra[i].isOffsetOnly(), "MeshTools::interleave(): extra attribute" << i << "is offset-only, which is not supported", (Trade::MeshData{MeshPrimitive::Triangles, 0})); /* Copy the attribute in, if it is non-empty, otherwise keep the memory uninitialized */ if(extra[i].data()) { CORRADE_ASSERT(extra[i].data().size() == data.vertexCount(), "MeshTools::interleave(): extra attribute" << i << "expected to have" << data.vertexCount() << "items but got" << extra[i].data().size(), (Trade::MeshData{MeshPrimitive::Triangles, 0})); const Containers::StridedArrayView2D attribute = Containers::arrayCast<2, const char>(extra[i].data(), vertexFormatSize(extra[i].format())); Utility::copy(attribute, layout.mutableAttribute(attributeIndex)); } ++attributeIndex; } /* Release the data from the layout to pack them into the output */ vertexData = layout.releaseVertexData(); attributeData = layout.releaseAttributeData(); } return Trade::MeshData{data.primitive(), std::move(indexData), indices, std::move(vertexData), std::move(attributeData)}; } Trade::MeshData interleave(Trade::MeshData&& data, const std::initializer_list extra) { return interleave(std::move(data), Containers::arrayView(extra)); } Trade::MeshData interleave(const Trade::MeshData& data, const Containers::ArrayView extra) { Containers::ArrayView indexData; Trade::MeshIndexData indices; if(data.isIndexed()) { indexData = data.indexData(); indices = Trade::MeshIndexData{data.indices()}; /* If there's neither an index array nor any attributes in the original mesh, we need to pass vertex count explicitly (MeshData asserts on that to avoid it getting lost.) */ } else if(!data.attributeCount()) { return interleave(Trade::MeshData{data.primitive(), data.vertexCount()}, extra); } return interleave(Trade::MeshData{data.primitive(), {}, indexData, indices, {}, data.vertexData(), Trade::meshAttributeDataNonOwningArray(data.attributeData()) }, extra); } Trade::MeshData interleave(const Trade::MeshData& data, const std::initializer_list extra) { return interleave(std::move(data), Containers::arrayView(extra)); } }}