/* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021 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 "MeshData.h" #include #ifndef CORRADE_NO_ASSERT #include #endif #include "Magnum/Math/Color.h" #include "Magnum/Math/PackingBatch.h" #include "Magnum/Trade/Implementation/arrayUtilities.h" namespace Magnum { namespace Trade { MeshIndexData::MeshIndexData(const MeshIndexType type, const Containers::ArrayView data) noexcept: _type{type}, _data{data} { /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ CORRADE_ASSERT(data.size()%meshIndexTypeSize(type) == 0, "Trade::MeshIndexData: view size" << data.size() << "does not correspond to" << type, ); } MeshIndexData::MeshIndexData(const Containers::StridedArrayView2D& data) noexcept { /* Second dimension being zero indicates a non-indexed mesh */ if(data.size()[1] == 0) { _type = MeshIndexType{}; return; } if(data.size()[1] == 4) _type = MeshIndexType::UnsignedInt; else if(data.size()[1] == 2) _type = MeshIndexType::UnsignedShort; else if(data.size()[1] == 1) _type = MeshIndexType::UnsignedByte; else CORRADE_ASSERT_UNREACHABLE("Trade::MeshIndexData: expected index type size 1, 2 or 4 but got" << data.size()[1], ); CORRADE_ASSERT(data.isContiguous(), "Trade::MeshIndexData: view is not contiguous", ); _data = data.asContiguous(); } MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, UnsignedShort arraySize) noexcept: MeshAttributeData{nullptr, name, format, data, arraySize} { /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ /** @todo support zero / negative stride? would be hard to transfer to GL */ CORRADE_ASSERT(data.empty() || isVertexFormatImplementationSpecific(format) || std::ptrdiff_t(vertexFormatSize(format))*(arraySize ? arraySize : 1) <= data.stride(), "Trade::MeshAttributeData: expected stride to be positive and enough to fit" << format << Debug::nospace << (arraySize ? Utility::format("[{}]", arraySize).data() : "") << Debug::nospace << ", got" << data.stride(), ); } MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView2D& data, UnsignedShort arraySize) noexcept: MeshAttributeData{nullptr, name, format, Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, arraySize} { /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ #ifndef CORRADE_NO_ASSERT if(arraySize) CORRADE_ASSERT(data.empty()[0] || isVertexFormatImplementationSpecific(format) || data.size()[1] == vertexFormatSize(format)*arraySize, "Trade::MeshAttributeData: second view dimension size" << data.size()[1] << "doesn't match" << format << "and array size" << arraySize, ); else CORRADE_ASSERT(data.empty()[0] || isVertexFormatImplementationSpecific(format) || data.size()[1] == vertexFormatSize(format), "Trade::MeshAttributeData: second view dimension size" << data.size()[1] << "doesn't match" << format, ); #endif CORRADE_ASSERT(data.isContiguous<1>(), "Trade::MeshAttributeData: second view dimension is not contiguous", ); } Containers::Array meshAttributeDataNonOwningArray(const Containers::ArrayView view) { /* Ugly, eh? */ return Containers::Array{const_cast(view.data()), view.size(), reinterpret_cast(Implementation::nonOwnedArrayDeleter)}; } MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: _primitive{primitive}, _indexType{indices._type}, _indexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _vertexDataFlags{DataFlag::Owned|DataFlag::Mutable}, _importerState{importerState}, _indices{static_cast(indices._data.data())}, _attributes{std::move(attributes)}, _indexData{std::move(indexData)}, _vertexData{std::move(vertexData)} { /* Save index count, only if the indices are actually specified */ if(_indexType != MeshIndexType{}) _indexCount = UnsignedInt(indices._data.size()/meshIndexTypeSize(indices._type)); else _indexCount = 0; /* Save vertex count. If it's passed explicitly, use that (but still check that all attributes have the same vertex count for safety), otherwise expect at least one attribute */ #ifndef CORRADE_NO_ASSERT UnsignedInt expectedAttributeVertexCount; #endif if(_attributes.empty()) { CORRADE_ASSERT(vertexCount != ImplicitVertexCount, "Trade::MeshData: vertex count can't be implicit if there are no attributes", ); _vertexCount = vertexCount; /* No attributes, so we won't be checking anything */ } else if(vertexCount != ImplicitVertexCount) { _vertexCount = vertexCount; #ifndef CORRADE_NO_ASSERT expectedAttributeVertexCount = _attributes[0]._vertexCount; #endif } else { _vertexCount = _attributes[0]._vertexCount; #ifndef CORRADE_NO_ASSERT expectedAttributeVertexCount = _vertexCount; #endif } CORRADE_ASSERT(_indexCount || _indexData.empty(), "Trade::MeshData: indexData passed for a non-indexed mesh", ); CORRADE_ASSERT(!_indices || (_indices >= _indexData.begin() && _indices + indices._data.size() <= _indexData.end()), "Trade::MeshData: indices [" << Debug::nospace << static_cast(_indices) << Debug::nospace << ":" << Debug::nospace << static_cast(_indices + indices._data.size()) << Debug::nospace << "] are not contained in passed indexData array [" << Debug::nospace << static_cast(_indexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_indexData.end()) << Debug::nospace << "]", ); #ifndef CORRADE_NO_ASSERT /* Not checking what's already checked in MeshIndexData / MeshAttributeData constructors */ for(std::size_t i = 0; i != _attributes.size(); ++i) { const MeshAttributeData& attribute = _attributes[i]; CORRADE_ASSERT(attribute._format != VertexFormat{}, "Trade::MeshData: attribute" << i << "doesn't specify anything", ); CORRADE_ASSERT(attribute._vertexCount == expectedAttributeVertexCount, "Trade::MeshData: attribute" << i << "has" << attribute._vertexCount << "vertices but" << expectedAttributeVertexCount << "expected", ); /* Check that the view fits into the provided vertex data array. For implementation-specific formats we don't know the size so use 0 to check at least partially. */ const UnsignedInt typeSize = isVertexFormatImplementationSpecific(attribute._format) ? 0 : vertexFormatSize(attribute._format); if(attribute._isOffsetOnly) { const std::size_t size = attribute._data.offset + (_vertexCount - 1)*attribute._stride + typeSize; CORRADE_ASSERT(!_vertexCount || size <= _vertexData.size(), "Trade::MeshData: offset-only attribute" << i << "spans" << size << "bytes but passed vertexData array has only" << _vertexData.size(), ); } else { const void* const begin = static_cast(attribute._data.pointer); const void* const end = static_cast(attribute._data.pointer) + (_vertexCount - 1)*attribute._stride + typeSize; CORRADE_ASSERT(!_vertexCount || (begin >= _vertexData.begin() && end <= _vertexData.end()), "Trade::MeshData: attribute" << i << "[" << Debug::nospace << begin << Debug::nospace << ":" << Debug::nospace << end << Debug::nospace << "] is not contained in passed vertexData array [" << Debug::nospace << static_cast(_vertexData.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_vertexData.end()) << Debug::nospace << "]", ); } } #endif } MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, Containers::Array&& vertexData, std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, std::move(indexData), indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), vertexCount, importerState} { CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); _indexDataFlags = indexDataFlags; _vertexDataFlags = vertexDataFlags; } MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, const std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, indexDataFlags, indexData, indices, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, std::move(vertexData), std::move(attributes), vertexCount, importerState} { CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); _indexDataFlags = indexDataFlags; } MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, Containers::Array&& vertexData, const std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, indexDataFlags, indexData, indices, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), vertexCount, importerState} { CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); _vertexDataFlags = vertexDataFlags; } MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const DataFlags vertexDataFlags, const Containers::ArrayView vertexData, const std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, std::move(indexData), indices, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, std::move(vertexData), std::move(attributes), vertexCount, importerState} {} MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& vertexData, const std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, std::move(vertexData), Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} MeshData::MeshData(const MeshPrimitive primitive, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, Containers::Array&& attributes, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(vertexData.data())), vertexData.size(), Implementation::nonOwnedArrayDeleter}, std::move(attributes), vertexCount, importerState} { CORRADE_ASSERT(!(vertexDataFlags & DataFlag::Owned), "Trade::MeshData: can't construct with non-owned vertex data but" << vertexDataFlags, ); _vertexDataFlags = vertexDataFlags; } MeshData::MeshData(const MeshPrimitive primitive, const DataFlags vertexDataFlags, Containers::ArrayView vertexData, std::initializer_list attributes, const UnsignedInt vertexCount, const void* const importerState): MeshData{primitive, vertexDataFlags, vertexData, Implementation::initializerListToArrayWithDefaultDeleter(attributes), vertexCount, importerState} {} MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& indexData, const MeshIndexData& indices, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, std::move(indexData), indices, {}, {}, vertexCount, importerState} {} MeshData::MeshData(const MeshPrimitive primitive, const DataFlags indexDataFlags, const Containers::ArrayView indexData, const MeshIndexData& indices, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, Containers::Array{const_cast(static_cast(indexData.data())), indexData.size(), Implementation::nonOwnedArrayDeleter}, indices, vertexCount, importerState} { CORRADE_ASSERT(!(indexDataFlags & DataFlag::Owned), "Trade::MeshData: can't construct with non-owned index data but" << indexDataFlags, ); _indexDataFlags = indexDataFlags; } MeshData::MeshData(const MeshPrimitive primitive, const UnsignedInt vertexCount, const void* const importerState) noexcept: MeshData{primitive, {}, MeshIndexData{}, {}, {}, vertexCount, importerState} {} MeshData::MeshData(MeshData&&) noexcept = default; MeshData::~MeshData() = default; MeshData& MeshData::operator=(MeshData&&) noexcept = default; Containers::ArrayView MeshData::mutableIndexData() & { CORRADE_ASSERT(_indexDataFlags & DataFlag::Mutable, "Trade::MeshData::mutableIndexData(): index data not mutable", {}); return _indexData; } Containers::ArrayView MeshData::mutableVertexData() & { CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, "Trade::MeshData::mutableVertexData(): vertex data not mutable", {}); return _vertexData; } UnsignedInt MeshData::indexCount() const { CORRADE_ASSERT(isIndexed(), "Trade::MeshData::indexCount(): the mesh is not indexed", {}); return _indexCount; } MeshIndexType MeshData::indexType() const { CORRADE_ASSERT(isIndexed(), "Trade::MeshData::indexType(): the mesh is not indexed", {}); return _indexType; } std::size_t MeshData::indexOffset() const { CORRADE_ASSERT(isIndexed(), "Trade::MeshData::indexOffset(): the mesh is not indexed", {}); return _indices - _indexData.data(); } Containers::StridedArrayView2D MeshData::indices() const { /* For a non-indexed mesh returning zero size in both dimensions, indexed mesh with zero indices still has the second dimension non-zero */ if(!isIndexed()) return {}; const std::size_t indexTypeSize = meshIndexTypeSize(_indexType); /* Build a 2D view using information about attribute type size */ return {{_indices, _indexCount*indexTypeSize}, {_indexCount, indexTypeSize}}; } Containers::StridedArrayView2D MeshData::mutableIndices() { CORRADE_ASSERT(_indexDataFlags & DataFlag::Mutable, "Trade::MeshData::mutableIndices(): index data not mutable", {}); /* For a non-indexed mesh returning zero size in both dimensions, indexed mesh with zero indices still has the second dimension non-zero */ if(!isIndexed()) return {}; const std::size_t indexTypeSize = meshIndexTypeSize(_indexType); /* Build a 2D view using information about index type size */ Containers::StridedArrayView2D out{{_indices, _indexCount*indexTypeSize}, {_indexCount, indexTypeSize}}; /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the view is valid */ {static_cast(const_cast(out.data())), ~std::size_t{}}, out.size(), out.stride()}; } MeshAttributeData MeshData::attributeData(const UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeData(): index" << id << "out of range for" << _attributes.size() << "attributes", MeshAttributeData{}); const MeshAttributeData& attribute = _attributes[id]; return MeshAttributeData{attribute._name, attribute._format, attributeDataViewInternal(attribute), attribute._arraySize}; } MeshAttribute MeshData::attributeName(const UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeName(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); return _attributes[id]._name; } VertexFormat MeshData::attributeFormat(const UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeFormat(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); return _attributes[id]._format; } std::size_t MeshData::attributeOffset(const UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); return _attributes[id]._isOffsetOnly ? _attributes[id]._data.offset : static_cast(_attributes[id]._data.pointer) - _vertexData.data(); } UnsignedInt MeshData::attributeStride(const UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeStride(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); return _attributes[id]._stride; } UnsignedShort MeshData::attributeArraySize(const UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeArraySize(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); return _attributes[id]._arraySize; } UnsignedInt MeshData::attributeCount(const MeshAttribute name) const { UnsignedInt count = 0; for(const MeshAttributeData& attribute: _attributes) if(attribute._name == name) ++count; return count; } UnsignedInt MeshData::attributeFor(const MeshAttribute name, UnsignedInt id) const { for(std::size_t i = 0; i != _attributes.size(); ++i) { if(_attributes[i]._name != name) continue; if(id-- == 0) return i; } #ifdef CORRADE_NO_ASSERT CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ #else return ~UnsignedInt{}; #endif } UnsignedInt MeshData::attributeId(const MeshAttribute name, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(name, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeId(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); return attributeId; } VertexFormat MeshData::attributeFormat(const MeshAttribute name, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(name, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeFormat(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); return _attributes[attributeId]._format; } std::size_t MeshData::attributeOffset(const MeshAttribute name, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(name, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); /* Calculation is non-trivial, delegating */ return attributeOffset(attributeId); } UnsignedInt MeshData::attributeStride(const MeshAttribute name, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(name, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeStride(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); return _attributes[attributeId]._stride; } UnsignedShort MeshData::attributeArraySize(const MeshAttribute name, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(name, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeArraySize(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); return _attributes[attributeId]._arraySize; } Containers::StridedArrayView1D MeshData::attributeDataViewInternal(const MeshAttributeData& attribute) const { return Containers::StridedArrayView1D{ /* We're *sure* the view is correct, so faking the view size */ /** @todo better ideas for the StridedArrayView API? */ {attribute._isOffsetOnly ? _vertexData.data() + attribute._data.offset : attribute._data.pointer, ~std::size_t{}}, /* Not using attribute._vertexCount because that gets stale after releaseVertexData() gets called, and then we would need to slice the result inside attribute() and elsewhere anyway */ _vertexCount, attribute._stride}; } Containers::StridedArrayView2D MeshData::attribute(const UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attribute(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); const MeshAttributeData& attribute = _attributes[id]; /* Build a 2D view using information about attribute type size */ return Containers::arrayCast<2, const char>( attributeDataViewInternal(attribute), isVertexFormatImplementationSpecific(attribute._format) ? attribute._stride : vertexFormatSize(attribute._format)* (attribute._arraySize ? attribute._arraySize : 1)); } Containers::StridedArrayView2D MeshData::mutableAttribute(const UnsignedInt id) { CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); const MeshAttributeData& attribute = _attributes[id]; /* Build a 2D view using information about attribute type size */ const auto out = Containers::arrayCast<2, const char>( attributeDataViewInternal(attribute), isVertexFormatImplementationSpecific(attribute._format) ? attribute._stride : vertexFormatSize(attribute._format)* (attribute._arraySize ? attribute._arraySize : 1)); /** @todo some arrayConstCast? UGH */ return Containers::StridedArrayView2D{ /* The view size is there only for a size assert, we're pretty sure the view is valid */ {static_cast(const_cast(out.data())), ~std::size_t{}}, out.size(), out.stride()}; } Containers::StridedArrayView2D MeshData::attribute(const MeshAttribute name, UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(name, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); return attribute(attributeId); } Containers::StridedArrayView2D MeshData::mutableAttribute(const MeshAttribute name, UnsignedInt id) { CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); const UnsignedInt attributeId = attributeFor(name, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); return mutableAttribute(attributeId); } void MeshData::indicesInto(const Containers::StridedArrayView1D destination) const { CORRADE_ASSERT(isIndexed(), "Trade::MeshData::indicesInto(): the mesh is not indexed", ); CORRADE_ASSERT(destination.size() == indexCount(), "Trade::MeshData::indicesInto(): expected a view with" << indexCount() << "elements but got" << destination.size(), ); const auto destination1ui = Containers::arrayCast<2, UnsignedInt>(destination); if(_indexType == MeshIndexType::UnsignedInt) return Utility::copy(Containers::arrayView(reinterpret_cast(_indices), _indexCount), destination); else if(_indexType == MeshIndexType::UnsignedShort) return Math::castInto(Containers::arrayCast<2, const UnsignedShort>(Containers::arrayView(reinterpret_cast(_indices), _indexCount)), destination1ui); else if(_indexType == MeshIndexType::UnsignedByte) return Math::castInto(Containers::arrayCast<2, const UnsignedByte>(Containers::arrayView(reinterpret_cast(_indices), _indexCount)), destination1ui); else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } Containers::Array MeshData::indicesAsArray() const { /* Repeating the assert here because otherwise it would fire in indexCount() which may be confusing */ CORRADE_ASSERT(isIndexed(), "Trade::MeshData::indicesAsArray(): the mesh is not indexed", {}); Containers::Array output{indexCount()}; indicesInto(output); return output; } void MeshData::positions2DInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(MeshAttribute::Position, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), "Trade::MeshData::positions2DInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination2f = Containers::arrayCast<2, Float>(destination); /* Copy 2D positions as-is, for 3D positions ignore Z */ if(attribute._format == VertexFormat::Vector2 || attribute._format == VertexFormat::Vector3) Utility::copy(Containers::arrayCast(attributeData), destination); else if(attribute._format == VertexFormat::Vector2h || attribute._format == VertexFormat::Vector3h) Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ub || attribute._format == VertexFormat::Vector3ub) Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2b || attribute._format == VertexFormat::Vector3b) Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2us || attribute._format == VertexFormat::Vector3us) Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2s || attribute._format == VertexFormat::Vector3s) Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ubNormalized || attribute._format == VertexFormat::Vector3ubNormalized) Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2bNormalized || attribute._format == VertexFormat::Vector3bNormalized) Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2usNormalized || attribute._format == VertexFormat::Vector3usNormalized) Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2sNormalized || attribute._format == VertexFormat::Vector3sNormalized) Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } Containers::Array MeshData::positions2DAsArray(const UnsignedInt id) const { Containers::Array out{_vertexCount}; positions2DInto(out, id); return out; } void MeshData::positions3DInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(MeshAttribute::Position, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions3DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::positions3DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), "Trade::MeshData::positions3DInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const Containers::StridedArrayView2D destination2f = Containers::arrayCast<2, Float>(Containers::arrayCast(destination)); const Containers::StridedArrayView2D destination3f = Containers::arrayCast<2, Float>(destination); /* For 2D positions copy the XY part to the first two components */ if(attribute._format == VertexFormat::Vector2) Utility::copy(Containers::arrayCast(attributeData), Containers::arrayCast(destination)); else if(attribute._format == VertexFormat::Vector2h) Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ub) Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2b) Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2us) Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2s) Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ubNormalized) Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2bNormalized) Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2usNormalized) Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2sNormalized) Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); /* Copy 3D positions as-is */ else if(attribute._format == VertexFormat::Vector3) Utility::copy(Containers::arrayCast(attributeData), destination); else if(attribute._format == VertexFormat::Vector3h) Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3ub) Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3b) Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3us) Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3s) Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3ubNormalized) Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3bNormalized) Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3usNormalized) Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3sNormalized) Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ /* For 2D positions finally fill the Z with a single value */ if(attribute._format == VertexFormat::Vector2 || attribute._format == VertexFormat::Vector2h || attribute._format == VertexFormat::Vector2ub || attribute._format == VertexFormat::Vector2b || attribute._format == VertexFormat::Vector2us || attribute._format == VertexFormat::Vector2s || attribute._format == VertexFormat::Vector2ubNormalized || attribute._format == VertexFormat::Vector2bNormalized || attribute._format == VertexFormat::Vector2usNormalized || attribute._format == VertexFormat::Vector2sNormalized) { constexpr Float z[1]{0.0f}; Utility::copy( Containers::stridedArrayView(z).broadcasted<0>(_vertexCount), destination3f.transposed<0, 1>()[2]); } } Containers::Array MeshData::positions3DAsArray(const UnsignedInt id) const { Containers::Array out{_vertexCount}; positions3DInto(out, id); return out; } namespace { void tangentsOrNormalsInto(const Containers::StridedArrayView1D attributeData, const Containers::StridedArrayView1D destination, VertexFormat format) { const auto destination3f = Containers::arrayCast<2, Float>(destination); if(format == VertexFormat::Vector3) Utility::copy(Containers::arrayCast(attributeData), destination); else if(format == VertexFormat::Vector3h) Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(format == VertexFormat::Vector3bNormalized) Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); else if(format == VertexFormat::Vector3sNormalized) Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } } void MeshData::tangentsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(MeshAttribute::Tangent, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::tangentsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Tangent) << "tangent attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::tangentsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), "Trade::MeshData::tangentsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); /* If the tangent is four-component, ignore the last component; otherwise copy/unpack given format directly */ VertexFormat format; if(attribute._format == VertexFormat::Vector4) format = VertexFormat::Vector3; else if(attribute._format == VertexFormat::Vector4h) format = VertexFormat::Vector3h; else if(attribute._format == VertexFormat::Vector4bNormalized) format = VertexFormat::Vector3bNormalized; else if(attribute._format == VertexFormat::Vector4sNormalized) format = VertexFormat::Vector3sNormalized; else format = attribute._format; tangentsOrNormalsInto(attributeDataViewInternal(attribute), destination, format); } Containers::Array MeshData::tangentsAsArray(const UnsignedInt id) const { Containers::Array out{_vertexCount}; tangentsInto(out, id); return out; } void MeshData::bitangentSignsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(MeshAttribute::Tangent, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::bitangentSignsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Tangent) << "tangent attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::bitangentSignsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), "Trade::MeshData::bitangentSignsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination1f = Containers::arrayCast<2, Float>(destination); if(attribute._format == VertexFormat::Vector4) Utility::copy(Containers::arrayCast<2, const Float>(attributeData, 4).transposed<0, 1>()[3], destination); else if(attribute._format == VertexFormat::Vector4h) Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4).suffix({0, 3}), destination1f); else if(attribute._format == VertexFormat::Vector4bNormalized) Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 4).suffix({0, 3}), destination1f); else if(attribute._format == VertexFormat::Vector4sNormalized) Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 4).suffix({0, 3}), destination1f); else CORRADE_ASSERT_UNREACHABLE("Trade::MeshData::bitangentSignsInto(): expected four-component tangents, but got" << attribute._format, ); } Containers::Array MeshData::bitangentSignsAsArray(const UnsignedInt id) const { Containers::Array out{_vertexCount}; bitangentSignsInto(out, id); return out; } void MeshData::bitangentsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(MeshAttribute::Bitangent, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::bitangentsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Bitangent) << "bitangent attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::bitangentsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), "Trade::MeshData::bitangentsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); tangentsOrNormalsInto(attributeDataViewInternal(attribute), destination, attribute._format); } Containers::Array MeshData::bitangentsAsArray(const UnsignedInt id) const { Containers::Array out{_vertexCount}; bitangentsInto(out, id); return out; } void MeshData::normalsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(MeshAttribute::Normal, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal) << "normal attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::normalsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), "Trade::MeshData::normalsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); tangentsOrNormalsInto(attributeDataViewInternal(attribute), destination, attribute._format); } Containers::Array MeshData::normalsAsArray(const UnsignedInt id) const { Containers::Array out{_vertexCount}; normalsInto(out, id); return out; } void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(MeshAttribute::TextureCoordinates, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::textureCoordinates2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::TextureCoordinates) << "texture coordinate attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::textureCoordinates2DInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), "Trade::MeshData::textureCoordinatesInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination2f = Containers::arrayCast<2, Float>(destination); if(attribute._format == VertexFormat::Vector2) Utility::copy(Containers::arrayCast(attributeData), destination); else if(attribute._format == VertexFormat::Vector2h) Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ub) Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2b) Math::castInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2us) Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2s) Math::castInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2ubNormalized) Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2bNormalized) Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2usNormalized) Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 2), destination2f); else if(attribute._format == VertexFormat::Vector2sNormalized) Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 2), destination2f); else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } Containers::Array MeshData::textureCoordinates2DAsArray(const UnsignedInt id) const { Containers::Array out{_vertexCount}; textureCoordinates2DInto(out, id); return out; } void MeshData::colorsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(MeshAttribute::Color, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::colorsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Color) << "color attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::colorsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), "Trade::MeshData::colorsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const Containers::StridedArrayView2D destination3f = Containers::arrayCast<2, Float>(Containers::arrayCast(destination)); const Containers::StridedArrayView2D destination4f = Containers::arrayCast<2, Float>(destination); /* For three-component colors copy the RGB part to the first three components */ if(attribute._format == VertexFormat::Vector3) Utility::copy(Containers::arrayCast(attributeData), Containers::arrayCast(destination)); else if(attribute._format == VertexFormat::Vector3h) Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3ubNormalized) Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 3), destination3f); else if(attribute._format == VertexFormat::Vector3usNormalized) Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); /* Copy four-component colors as-is */ else if(attribute._format == VertexFormat::Vector4) Utility::copy(Containers::arrayCast(attributeData), Containers::arrayCast(destination)); else if(attribute._format == VertexFormat::Vector4h) Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4f); else if(attribute._format == VertexFormat::Vector4ubNormalized) Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 4), destination4f); else if(attribute._format == VertexFormat::Vector4usNormalized) Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4f); else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ /* For three-component colors finally fill the alpha with a single value */ if(attribute._format == VertexFormat::Vector3 || attribute._format == VertexFormat::Vector3h || attribute._format == VertexFormat::Vector3ubNormalized || attribute._format == VertexFormat::Vector3usNormalized) { constexpr Float alpha[1]{1.0f}; Utility::copy( Containers::stridedArrayView(alpha).broadcasted<0>(_vertexCount), destination4f.transposed<0, 1>()[3]); } } Containers::Array MeshData::colorsAsArray(const UnsignedInt id) const { Containers::Array out{_vertexCount}; colorsInto(out, id); return out; } void MeshData::objectIdsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { const UnsignedInt attributeId = attributeFor(MeshAttribute::ObjectId, id); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::objectIdsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::ObjectId) << "object ID attributes", ); CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::objectIdsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); const MeshAttributeData& attribute = _attributes[attributeId]; CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), "Trade::MeshData::objectIdsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); const auto destination1ui = Containers::arrayCast<2, UnsignedInt>(destination); if(attribute._format == VertexFormat::UnsignedInt) Utility::copy(Containers::arrayCast(attributeData), destination); else if(attribute._format == VertexFormat::UnsignedShort) Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 1), destination1ui); else if(attribute._format == VertexFormat::UnsignedByte) Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 1), destination1ui); else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } Containers::Array MeshData::objectIdsAsArray(const UnsignedInt id) const { Containers::Array out{_vertexCount}; objectIdsInto(out, id); return out; } Containers::Array MeshData::releaseIndexData() { _indexCount = 0; Containers::Array out = std::move(_indexData); _indexData = Containers::Array{out.data(), 0, Implementation::nonOwnedArrayDeleter}; return out; } Containers::Array MeshData::releaseAttributeData() { return std::move(_attributes); } Containers::Array MeshData::releaseVertexData() { _vertexCount = 0; Containers::Array out = std::move(_vertexData); _vertexData = Containers::Array{out.data(), 0, Implementation::nonOwnedArrayDeleter}; return out; } Debug& operator<<(Debug& debug, const MeshAttribute value) { debug << "Trade::MeshAttribute" << Debug::nospace; if(UnsignedShort(value) >= UnsignedShort(MeshAttribute::Custom)) return debug << "::Custom(" << Debug::nospace << (UnsignedShort(value) - UnsignedShort(MeshAttribute::Custom)) << Debug::nospace << ")"; switch(value) { /* LCOV_EXCL_START */ #define _c(value) case MeshAttribute::value: return debug << "::" #value; _c(Position) _c(Tangent) _c(Bitangent) _c(Normal) _c(TextureCoordinates) _c(Color) _c(ObjectId) #undef _c /* LCOV_EXCL_STOP */ /* To silence compiler warning about unhandled values */ case MeshAttribute::Custom: CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedShort(value)) << Debug::nospace << ")"; } }}