From f84c3679c3e753da316f0d42f5e130836ef21dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 20 Jun 2023 20:31:41 +0200 Subject: [PATCH] Trade: support morph targets in MeshData. Attributes that are morph targets are marked with a morph target ID, those then get treated separately in name-based lookups. --- doc/changelog.dox | 3 + doc/developers.dox | 2 +- doc/snippets/MagnumTrade.cpp | 17 + src/Magnum/MeshTools/Transform.h | 22 +- src/Magnum/Trade/MeshData.cpp | 260 ++++++--- src/Magnum/Trade/MeshData.h | 467 ++++++++++------ src/Magnum/Trade/Test/MeshDataTest.cpp | 715 ++++++++++++++++++++++--- 7 files changed, 1150 insertions(+), 336 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index 0682f1217..6f9f6456d 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -769,6 +769,9 @@ See also: - @ref Trade::MeshData now allows array attributes to have implementation-specific vertex formats as well. The restriction didn't make sense, as there was nothing in the design preventing them from being used. +- @ref Trade::MeshData can now store morph target attributes next to the + base attributes, they're then accessed using a concrete morph target ID in + name-based lookup APIs - Added @ref Trade::MeshData::findAttributeId() for an ability to check that an attribute exists and retrieve its ID in a single step, avoiding a double lookup compared to @relativeref{Trade::MeshData,hasAttribute()} + diff --git a/doc/developers.dox b/doc/developers.dox index d1052aaa8..28a33d28a 100644 --- a/doc/developers.dox +++ b/doc/developers.dox @@ -604,7 +604,7 @@ in inverse --- but usually @ref developers-deprecation "deprecate first". `isVertexFormatCompatibleWithAttribute()` if there's more than one entry corresponding to a particular C++ type. If the mapping is unconventional, be sure to mention it in the - @ref Trade::MeshAttributeData::MeshAttributeData(MeshAttribute, const Containers::StridedArrayView1D&) + @ref Trade::MeshAttributeData::MeshAttributeData(MeshAttribute, const Containers::StridedArrayView1D&, Int) constructor docs. 4. Update corresponding `Trade::MeshData::*Into()` convenience getters to ensure they can handle this type diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index 4f03e15de..e2ee10b6b 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -867,6 +867,23 @@ MeshTools::transformPointsInPlace(Matrix4::scaling(Vector3{2.0f}), /* [MeshData-usage-mutable] */ } +{ +Trade::MeshData data{MeshPrimitive::Points, 0}; +/* [MeshData-usage-morph-targets] */ +Float weights[]{0.25f, 0.5f}; + +/* Calculate morphed positions with the above weights, assuming the mesh has + a Vector3 Position attribute in morph targets 0 and 1 */ +Containers::Array positions = data.positions3DAsArray(0, -1); +for(Int morphTargetId: {0, 1}) { + Containers::StridedArrayView1D morphed = + data.attribute(Trade::MeshAttribute::Position, 0, morphTargetId); + for(std::size_t i = 0; i != data.vertexCount(); ++i) + positions[i] += morphed[i]*weights[morphTargetId]; +} +/* [MeshData-usage-morph-targets] */ +} + { Trade::MeshData data{MeshPrimitive::Points, 0}; /* [MeshData-usage-special-layouts] */ diff --git a/src/Magnum/MeshTools/Transform.h b/src/Magnum/MeshTools/Transform.h index 6d920cdbc..43c9fd460 100644 --- a/src/Magnum/MeshTools/Transform.h +++ b/src/Magnum/MeshTools/Transform.h @@ -161,8 +161,8 @@ See also @ref transform2D(Trade::MeshData&&, const Matrix3&, UnsignedInt, Interl for a potentially more efficient operation instead of always performing a full copy, you can also do an in-place transformation using @ref transform2DInPlace(). @see @ref transform3D(), @ref transformTextureCoordinates2D(), - @ref Trade::MeshData::attributeCount(MeshAttribute) const, - @ref Trade::MeshData::attributeFormat(MeshAttribute, UnsignedInt) const, + @ref Trade::MeshData::attributeCount(MeshAttribute, Int) const, + @ref Trade::MeshData::attributeFormat(MeshAttribute, UnsignedInt, Int) const, @ref isVertexFormatImplementationSpecific() */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData transform2D(const Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id = 0, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes); @@ -191,8 +191,8 @@ packed types, the in-place operation requires the position type to be @p id, and indices (if any) are left untouched. @see @ref transform3DInPlace(), @ref transformTextureCoordinates2DInPlace(), @ref Trade::MeshData::vertexDataFlags(), - @ref Trade::MeshData::attributeCount(MeshAttribute) const, - @ref Trade::MeshData::attributeFormat(MeshAttribute, UnsignedInt) const + @ref Trade::MeshData::attributeCount(MeshAttribute, Int) const, + @ref Trade::MeshData::attributeFormat(MeshAttribute, UnsignedInt, Int) const */ MAGNUM_MESHTOOLS_EXPORT void transform2DInPlace(Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id = 0); @@ -219,8 +219,8 @@ See also @ref transform3D(Trade::MeshData&&, const Matrix4&, UnsignedInt, Interl for a potentially more efficient operation instead of always performing a full copy, you can also do an in-place transformation using @ref transform3DInPlace(). @see @ref transform2D(), @ref transformTextureCoordinates2D(), - @ref Trade::MeshData::attributeCount(MeshAttribute) const, - @ref Trade::MeshData::attributeFormat(MeshAttribute, UnsignedInt) const, + @ref Trade::MeshData::attributeCount(MeshAttribute, Int) const, + @ref Trade::MeshData::attributeFormat(MeshAttribute, UnsignedInt, Int) const, @ref isVertexFormatImplementationSpecific() */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData transform3D(const Trade::MeshData& mesh, const Matrix4& transformation, UnsignedInt id = 0, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes); @@ -255,8 +255,8 @@ operation requires the position, normal and bitangent types to be than @p id, and indices (if any) are left untouched. @see @ref transform2DInPlace(), @ref transformTextureCoordinates2DInPlace(), @ref Trade::MeshData::vertexDataFlags(), - @ref Trade::MeshData::attributeCount(MeshAttribute) const, - @ref Trade::MeshData::attributeFormat(MeshAttribute, UnsignedInt) const + @ref Trade::MeshData::attributeCount(MeshAttribute, Int) const, + @ref Trade::MeshData::attributeFormat(MeshAttribute, UnsignedInt, Int) const */ MAGNUM_MESHTOOLS_EXPORT void transform3DInPlace(Trade::MeshData& mesh, const Matrix4& transformation, UnsignedInt id = 0); @@ -277,7 +277,7 @@ for a potentially more efficient operation instead of always performing a full copy, you can also do an in-place transformation using @ref transformTextureCoordinates2DInPlace(). @see @ref transform2D(), @ref transform3D(), - @ref Trade::MeshData::attributeCount(MeshAttribute) const, + @ref Trade::MeshData::attributeCount(MeshAttribute, Int) const, @ref isVertexFormatImplementationSpecific() */ MAGNUM_MESHTOOLS_EXPORT Trade::MeshData transformTextureCoordinates2D(const Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id = 0, InterleaveFlags flags = InterleaveFlag::PreserveInterleavedAttributes); @@ -308,8 +308,8 @@ coordinate attributes other than @p id, and indices (if any) are passed through untouched. @see @ref transform2DInPlace(), @ref transform3DInPlace(), @ref Trade::MeshData::vertexDataFlags(), - @ref Trade::MeshData::attributeCount(MeshAttribute) const, - @ref Trade::MeshData::attributeFormat(MeshAttribute, UnsignedInt) const + @ref Trade::MeshData::attributeCount(MeshAttribute, Int) const, + @ref Trade::MeshData::attributeFormat(MeshAttribute, UnsignedInt, Int) const */ MAGNUM_MESHTOOLS_EXPORT void transformTextureCoordinates2DInPlace(Trade::MeshData& mesh, const Matrix3& transformation, UnsignedInt id = 0); diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index a5260bdba..d0a88987c 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -59,13 +59,13 @@ MeshIndexData::MeshIndexData(const Containers::StridedArrayView2D& d "Trade::MeshIndexData: second view dimension is not contiguous", ); } -MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, UnsignedShort arraySize) noexcept: MeshAttributeData{nullptr, name, format, data, arraySize} { +MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, const UnsignedShort arraySize, const Int morphTargetId) noexcept: MeshAttributeData{nullptr, name, format, data, arraySize, morphTargetId} { /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ } -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} { +MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView2D& data, UnsignedShort arraySize, const Int morphTargetId) noexcept: MeshAttributeData{nullptr, name, format, Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, arraySize, morphTargetId} { /* Yes, this calls into a constexpr function defined in the header -- because I feel that makes more sense than duplicating the full assert logic */ @@ -218,6 +218,12 @@ MeshData::MeshData(const MeshPrimitive primitive, Containers::Array&& inde "Trade::MeshData: attribute" << i << "[" << Debug::nospace << reinterpret_cast(begin) << Debug::nospace << ":" << Debug::nospace << reinterpret_cast(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 << "]", ); } } + + /** @todo verify that (custom) integer attributes aren't morph targets? + (can't check that in MeshAttributeData constructors as + isVertexFormatInteger() -- once implemented -- wouldn't be + constexpr); or just leave that unchecked, as those are often used + for custom unheard-of behavior anyway? */ } /* Verify that count and array sizes of skin joint IDs and weights match */ @@ -371,11 +377,19 @@ Containers::StridedArrayView1D MeshData::attributeDataViewInternal(c _vertexCount, attribute._stride}; } +UnsignedInt MeshData::attributeCount(const Int morphTargetId) const { + UnsignedInt count = 0; + for(const MeshAttributeData& attribute: _attributes) + if(attribute._morphTargetId == morphTargetId) + ++count; + return count; +} + 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}; + return MeshAttributeData{attribute._name, attribute._format, attributeDataViewInternal(attribute), attribute._arraySize, attribute._morphTargetId}; } MeshAttribute MeshData::attributeName(const UnsignedInt id) const { @@ -388,9 +402,12 @@ UnsignedInt MeshData::attributeId(const UnsignedInt id) const { CORRADE_ASSERT(id < _attributes.size(), "Trade::MeshData::attributeId(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); const MeshAttribute name = _attributes[id]._name; + const Int morphTargetId = _attributes[id]._morphTargetId; UnsignedInt count = 0; for(UnsignedInt i = 0; i != id; ++i) - if(_attributes[i]._name == name) ++count; + if(_attributes[i]._name == name && + _attributes[i]._morphTargetId == morphTargetId) + ++count; return count; } @@ -419,54 +436,89 @@ UnsignedShort MeshData::attributeArraySize(const UnsignedInt id) const { return _attributes[id]._arraySize; } -UnsignedInt MeshData::attributeCount(const MeshAttribute name) const { +Int MeshData::attributeMorphTargetId(const UnsignedInt id) const { + CORRADE_ASSERT(id < _attributes.size(), + "Trade::MeshData::attributeMorphTargetId(): index" << id << "out of range for" << _attributes.size() << "attributes", {}); + return _attributes[id]._morphTargetId; +} + +UnsignedInt MeshData::attributeCount(const MeshAttribute name, const Int morphTargetId) const { UnsignedInt count = 0; for(const MeshAttributeData& attribute: _attributes) - if(attribute._name == name) ++count; + if(attribute._name == name && + attribute._morphTargetId == morphTargetId) + ++count; return count; } -UnsignedInt MeshData::findAttributeIdInternal(const MeshAttribute name, UnsignedInt id) const { +UnsignedInt MeshData::findAttributeIdInternal(const MeshAttribute name, UnsignedInt id, const Int morphTargetId) const { for(std::size_t i = 0; i != _attributes.size(); ++i) { - if(_attributes[i]._name != name) continue; + if(_attributes[i]._name != name || + _attributes[i]._morphTargetId != morphTargetId) + continue; if(id-- == 0) return i; } return ~UnsignedInt{}; } -Containers::Optional MeshData::findAttributeId(const MeshAttribute name, UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(name, id); +Containers::Optional MeshData::findAttributeId(const MeshAttribute name, UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(name, id, morphTargetId); return attributeId == ~UnsignedInt{} ? Containers::Optional{} : attributeId; } -UnsignedInt MeshData::attributeId(const MeshAttribute name, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeId(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); +UnsignedInt MeshData::attributeId(const MeshAttribute name, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(name, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attributeId(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes", {}); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attributeId(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes in morph target" << morphTargetId, {}); + #endif return attributeId; } -VertexFormat MeshData::attributeFormat(const MeshAttribute name, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeFormat(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); +VertexFormat MeshData::attributeFormat(const MeshAttribute name, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(name, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attributeFormat(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes", {}); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attributeFormat(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes in morph target" << morphTargetId, {}); + #endif return _attributes[attributeId]._format; } -std::size_t MeshData::attributeOffset(const MeshAttribute name, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); +std::size_t MeshData::attributeOffset(const MeshAttribute name, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(name, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes", {}); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attributeOffset(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes in morph target" << morphTargetId, {}); + #endif /* Calculation is non-trivial, delegating */ return attributeOffset(attributeId); } -Short MeshData::attributeStride(const MeshAttribute name, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeStride(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); +Short MeshData::attributeStride(const MeshAttribute name, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(name, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attributeStride(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes", {}); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attributeStride(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes in morph target" << morphTargetId, {}); + #endif return _attributes[attributeId]._stride; } -UnsignedShort MeshData::attributeArraySize(const MeshAttribute name, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attributeArraySize(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); +UnsignedShort MeshData::attributeArraySize(const MeshAttribute name, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(name, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attributeArraySize(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes", {}); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attributeArraySize(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes in morph target" << morphTargetId, {}); + #endif return _attributes[attributeId]._arraySize; } @@ -502,17 +554,27 @@ Containers::StridedArrayView2D MeshData::mutableAttribute(const UnsignedIn out.size(), out.stride()}; } -Containers::StridedArrayView2D MeshData::attribute(const MeshAttribute name, UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); +Containers::StridedArrayView2D MeshData::attribute(const MeshAttribute name, UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(name, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes", {}); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes in morph target" << morphTargetId, {}); + #endif return attribute(attributeId); } -Containers::StridedArrayView2D MeshData::mutableAttribute(const MeshAttribute name, UnsignedInt id) { +Containers::StridedArrayView2D MeshData::mutableAttribute(const MeshAttribute name, UnsignedInt id, const Int morphTargetId) { CORRADE_ASSERT(_vertexDataFlags & DataFlag::Mutable, "Trade::MeshData::mutableAttribute(): vertex data not mutable", {}); - const UnsignedInt attributeId = findAttributeIdInternal(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); + const UnsignedInt attributeId = findAttributeIdInternal(name, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes", {}); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes in morph target" << morphTargetId, {}); + #endif return mutableAttribute(attributeId); } @@ -544,9 +606,14 @@ Containers::Array MeshData::indicesAsArray() const { return output; } -void MeshData::positions2DInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Position, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); +void MeshData::positions2DInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Position, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::positions2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position, morphTargetId) << "position attributes", ); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::positions2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position, morphTargetId) << "position attributes in morph target" << morphTargetId, ); + #endif 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), @@ -588,15 +655,20 @@ void MeshData::positions2DInto(const Containers::StridedArrayView1D& de else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } -Containers::Array MeshData::positions2DAsArray(const UnsignedInt id) const { +Containers::Array MeshData::positions2DAsArray(const UnsignedInt id, const Int morphTargetId) const { Containers::Array out{NoInit, _vertexCount}; - positions2DInto(out, id); + positions2DInto(out, id, morphTargetId); return out; } -void MeshData::positions3DInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Position, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::positions3DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position) << "position attributes", ); +void MeshData::positions3DInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Position, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::positions3DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position, morphTargetId) << "position attributes", ); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::positions3DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Position, morphTargetId) << "position attributes in morph target" << morphTargetId, ); + #endif 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), @@ -669,9 +741,9 @@ void MeshData::positions3DInto(const Containers::StridedArrayView1D& de } } -Containers::Array MeshData::positions3DAsArray(const UnsignedInt id) const { +Containers::Array MeshData::positions3DAsArray(const UnsignedInt id, const Int morphTargetId) const { Containers::Array out{NoInit, _vertexCount}; - positions3DInto(out, id); + positions3DInto(out, id, morphTargetId); return out; } @@ -693,9 +765,14 @@ void tangentsOrNormalsInto(const Containers::StridedArrayView1D& att } -void MeshData::tangentsInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Tangent, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::tangentsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Tangent) << "tangent attributes", ); +void MeshData::tangentsInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Tangent, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::tangentsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Tangent, morphTargetId) << "tangent attributes", ); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::tangentsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Tangent, morphTargetId) << "tangent attributes in morph target" << morphTargetId, ); + #endif 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), @@ -716,15 +793,20 @@ void MeshData::tangentsInto(const Containers::StridedArrayView1D& desti tangentsOrNormalsInto(attributeDataViewInternal(attribute), destination, format); } -Containers::Array MeshData::tangentsAsArray(const UnsignedInt id) const { +Containers::Array MeshData::tangentsAsArray(const UnsignedInt id, const Int morphTargetId) const { Containers::Array out{NoInit, _vertexCount}; - tangentsInto(out, id); + tangentsInto(out, id, morphTargetId); return out; } -void MeshData::bitangentSignsInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Tangent, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::bitangentSignsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Tangent) << "tangent attributes", ); +void MeshData::bitangentSignsInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Tangent, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::bitangentSignsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Tangent, morphTargetId) << "tangent attributes", ); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::bitangentSignsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Tangent, morphTargetId) << "tangent attributes in morph target" << morphTargetId, ); + #endif 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), @@ -743,15 +825,20 @@ void MeshData::bitangentSignsInto(const Containers::StridedArrayView1D& d 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 MeshData::bitangentSignsAsArray(const UnsignedInt id, const Int morphTargetId) const { Containers::Array out{NoInit, _vertexCount}; - bitangentSignsInto(out, id); + bitangentSignsInto(out, id, morphTargetId); return out; } -void MeshData::bitangentsInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Bitangent, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::bitangentsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Bitangent) << "bitangent attributes", ); +void MeshData::bitangentsInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Bitangent, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::bitangentsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Bitangent, morphTargetId) << "bitangent attributes", ); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::bitangentsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Bitangent, morphTargetId) << "bitangent attributes in morph target" << morphTargetId, ); + #endif 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), @@ -759,15 +846,20 @@ void MeshData::bitangentsInto(const Containers::StridedArrayView1D& des tangentsOrNormalsInto(attributeDataViewInternal(attribute), destination, attribute._format); } -Containers::Array MeshData::bitangentsAsArray(const UnsignedInt id) const { +Containers::Array MeshData::bitangentsAsArray(const UnsignedInt id, const Int morphTargetId) const { Containers::Array out{NoInit, _vertexCount}; - bitangentsInto(out, id); + bitangentsInto(out, id, morphTargetId); return out; } -void MeshData::normalsInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Normal, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal) << "normal attributes", ); +void MeshData::normalsInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Normal, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal, morphTargetId) << "normal attributes", ); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal, morphTargetId) << "normal attributes in morph target" << morphTargetId, ); + #endif 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), @@ -775,15 +867,20 @@ void MeshData::normalsInto(const Containers::StridedArrayView1D& destin tangentsOrNormalsInto(attributeDataViewInternal(attribute), destination, attribute._format); } -Containers::Array MeshData::normalsAsArray(const UnsignedInt id) const { +Containers::Array MeshData::normalsAsArray(const UnsignedInt id, const Int morphTargetId) const { Containers::Array out{NoInit, _vertexCount}; - normalsInto(out, id); + normalsInto(out, id, morphTargetId); return out; } -void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::TextureCoordinates, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::textureCoordinates2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::TextureCoordinates) << "texture coordinate attributes", ); +void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::TextureCoordinates, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::textureCoordinates2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::TextureCoordinates, morphTargetId) << "texture coordinate attributes", ); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::textureCoordinates2DInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::TextureCoordinates, morphTargetId) << "texture coordinate attributes in morph target" << morphTargetId, ); + #endif 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), @@ -814,15 +911,20 @@ void MeshData::textureCoordinates2DInto(const Containers::StridedArrayView1D MeshData::textureCoordinates2DAsArray(const UnsignedInt id) const { +Containers::Array MeshData::textureCoordinates2DAsArray(const UnsignedInt id, const Int morphTargetId) const { Containers::Array out{NoInit, _vertexCount}; - textureCoordinates2DInto(out, id); + textureCoordinates2DInto(out, id, morphTargetId); return out; } -void MeshData::colorsInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Color, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::colorsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Color) << "color attributes", ); +void MeshData::colorsInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Color, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::colorsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Color, morphTargetId) << "color attributes", ); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::colorsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Color, morphTargetId) << "color attributes in morph target" << morphTargetId, ); + #endif 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), @@ -867,14 +969,15 @@ void MeshData::colorsInto(const Containers::StridedArrayView1D& destinat } } -Containers::Array MeshData::colorsAsArray(const UnsignedInt id) const { +Containers::Array MeshData::colorsAsArray(const UnsignedInt id, const Int morphTargetId) const { Containers::Array out{NoInit, _vertexCount}; - colorsInto(out, id); + colorsInto(out, id, morphTargetId); return out; } void MeshData::jointIdsInto(const Containers::StridedArrayView2D& destination, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::JointIds, id); + /* Joint IDs can't have morph targets */ + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::JointIds, id, -1); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::jointIdsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::JointIds) << "joint ID attributes", ); const MeshAttributeData& attribute = _attributes[attributeId]; @@ -899,7 +1002,8 @@ void MeshData::jointIdsInto(const Containers::StridedArrayView2D& d } Containers::Array MeshData::jointIdsAsArray(const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::JointIds, id); + /* Joint IDs can't have morph targets */ + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::JointIds, id, -1); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::jointIdsAsArray(): index" << id << "out of range for" << attributeCount(MeshAttribute::JointIds) << "joint ID attributes", {}); const MeshAttributeData& attribute = _attributes[attributeId]; @@ -909,7 +1013,8 @@ Containers::Array MeshData::jointIdsAsArray(const UnsignedInt id) c } void MeshData::weightsInto(const Containers::StridedArrayView2D& destination, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Weights, id); + /* Weights can't have morph targets */ + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Weights, id, -1); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::weightsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Weights) << "weight attributes", ); const MeshAttributeData& attribute = _attributes[attributeId]; @@ -936,7 +1041,8 @@ void MeshData::weightsInto(const Containers::StridedArrayView2D& destinat } Containers::Array MeshData::weightsAsArray(const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Weights, id); + /* Weights can't have morph targets */ + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::Weights, id, -1); CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::weightsAsArray(): index" << id << "out of range for" << attributeCount(MeshAttribute::JointIds) << "weight attributes", {}); const MeshAttributeData& attribute = _attributes[attributeId]; Containers::Array out{_vertexCount*attribute._arraySize}; @@ -945,7 +1051,8 @@ Containers::Array MeshData::weightsAsArray(const UnsignedInt id) const { } void MeshData::objectIdsInto(const Containers::StridedArrayView1D& destination, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::ObjectId, id); + /* Object IDs can't have morph targets */ + const UnsignedInt attributeId = findAttributeIdInternal(MeshAttribute::ObjectId, id, -1); 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]; @@ -965,6 +1072,7 @@ void MeshData::objectIdsInto(const Containers::StridedArrayView1D& Containers::Array MeshData::objectIdsAsArray(const UnsignedInt id) const { Containers::Array out{NoInit, _vertexCount}; + /* Object IDs can't have morph targets */ objectIdsInto(out, id); return out; } diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 54ea5d38c..1621770d8 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -102,6 +102,10 @@ enum class MeshAttribute: UnsignedShort { * * @snippet MagnumTrade.cpp MeshAttribute-bitangent-from-tangent * + * When used as a morph target attribute, the handedness shouldn't change + * compared to the base attribute. It's not checked or enforced in any way + * though. + * * Corresponds to @ref Shaders::GenericGL::Tangent or * @ref Shaders::GenericGL::Tangent4. * @see @ref MeshData::tangentsAsArray(), @@ -114,8 +118,13 @@ enum class MeshAttribute: UnsignedShort { * @ref VertexFormat::Vector3h, @ref VertexFormat::Vector3bNormalized or * @ref VertexFormat::Vector3sNormalized. For better storage efficiency, * the bitangent can be also reconstructed from the normal and tangent, see - * @ref MeshAttribute::Tangent for more information. Corresponds to - * @ref Shaders::GenericGL::Bitangent. + * @ref MeshAttribute::Tangent for more information. + * + * When used as a morph target attribute, the handedness shouldn't change + * compared to the base attribute. It's not checked or enforced in any way + * though. + * + * Corresponds to @ref Shaders::GenericGL::Bitangent. * @see @ref MeshData::bitangentsAsArray() */ Bitangent, @@ -164,7 +173,8 @@ enum class MeshAttribute: UnsignedShort { * * Count of instances of this attribute and array size of each instance is * expected to match instance count and array sizes of - * @ref MeshAttribute::Weights. + * @ref MeshAttribute::Weights. This attribute isn't allowed to be a morph + * target. * * Corresponds to @ref Shaders::GenericGL::JointIds and * @ref Shaders::GenericGL::SecondaryJointIds, divided between them based @@ -185,7 +195,8 @@ enum class MeshAttribute: UnsignedShort { * * Count of instances of this attribute and array size of each instance is * expected to match instance count and array sizes of - * @ref MeshAttribute::JointIds. + * @ref MeshAttribute::JointIds. This attribute isn't allowed to be a morph + * target. * * Corresponds to @ref Shaders::GenericGL::Weights and * @ref Shaders::GenericGL::SecondaryWeights, divided between them based @@ -199,6 +210,9 @@ enum class MeshAttribute: UnsignedShort { * (Instanced) object ID for editor selection or scene annotation. Type is * usually @ref VertexFormat::UnsignedInt, but can be also * @ref VertexFormat::UnsignedShort or @ref VertexFormat::UnsignedByte. + * + * This attribute isn't allowed to be a morph target. + * * Corresponds to @ref Shaders::GenericGL::ObjectId. * @see @ref MeshData::objectIdsAsArray() */ @@ -407,7 +421,7 @@ supply @ref VertexFormat explicitly. @subsection Trade-MeshAttributeData-usage-offset-only Offset-only attribute data If the actual attribute data location is not known yet, the instance can be -created as "offset-only" using @ref MeshAttributeData(MeshAttribute, VertexFormat, std::size_t, UnsignedInt, std::ptrdiff_t, UnsignedShort), +created as "offset-only" using @ref MeshAttributeData(MeshAttribute, VertexFormat, std::size_t, UnsignedInt, std::ptrdiff_t, UnsignedShort, Int), meaning the actual view gets created only later when passed to a @ref MeshData instance with a concrete vertex data array. This is useful mainly to avoid pointer patching during data serialization, but also for example when vertex @@ -445,15 +459,17 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * initialization of the attribute array for @ref MeshData, expected to * be replaced with concrete values later. */ - constexpr explicit MeshAttributeData() noexcept: _format{}, _name{}, _isOffsetOnly{false}, _vertexCount{}, _stride{}, _arraySize{}, _data{} {} + constexpr explicit MeshAttributeData() noexcept: _format{}, _name{}, _isOffsetOnly{false}, _morphTargetId{-1}, _vertexCount{}, _stride{}, _arraySize{}, _data{} {} /** * @brief Type-erased constructor - * @param name Attribute name - * @param format Vertex format - * @param data Attribute data - * @param arraySize Array size. Use @cpp 0 @ce for non-array + * @param name Attribute name + * @param format Vertex format + * @param data Attribute data + * @param arraySize Array size. Use @cpp 0 @ce for non-array * attributes. + * @param morphTargetId Morph target ID. Use @cpp -1 @ce for + * attributes that are not morph targets. * * Expects that @p data stride fits into a signed 16-bit value, that * vertex count fits into 32 bits, and for builtin attributes that @@ -462,15 +478,17 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * can be zero or negative, but note that such data layouts are not * commonly supported by GPU APIs. */ - explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, UnsignedShort arraySize = 0) noexcept; + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, UnsignedShort arraySize = 0, Int morphTargetId = -1) noexcept; /** * @brief Constructor - * @param name Attribute name - * @param format Vertex format - * @param data Attribute data - * @param arraySize Array size. Use @cpp 0 @ce for non-array + * @param name Attribute name + * @param format Vertex format + * @param data Attribute data + * @param arraySize Array size. Use @cpp 0 @ce for non-array * attributes. + * @param morphTargetId Morph target ID. Use @cpp -1 @ce for + * attributes that are not morph targets. * * Expects that the second dimension of @p data is contiguous and its * size matches @p format and @p arraySize, and for builtin attributes @@ -480,18 +498,28 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * or negative, but note that such data layouts are not commonly * supported by GPU APIs. */ - explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView2D& data, UnsignedShort arraySize = 0) noexcept; + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, const Containers::StridedArrayView2D& data, UnsignedShort arraySize = 0, Int morphTargetId = -1) noexcept; /** @overload */ - explicit MeshAttributeData(MeshAttribute name, VertexFormat format, std::nullptr_t, UnsignedShort arraySize = 0) noexcept: MeshAttributeData{nullptr, name, format, nullptr, arraySize} {} + #ifdef DOXYGEN_GENERATING_OUTPUT + explicit MeshAttributeData(MeshAttribute name, VertexFormat format, std::nullptr_t, UnsignedShort arraySize = 0, Int morphTargetId = -1) noexcept; + #else + /* Extra template crap is needed to avoid ambiguity with the + offset-only constructor (where 0 passed to offset would match with + std::nullptr_t). 0 as null pointer constant was deprecated in C++11 + already, WHY IS THIS STILL A PROBLEM?! */ + template::value && !std::is_convertible::value>::type> explicit MeshAttributeData(MeshAttribute name, VertexFormat format, U, UnsignedShort arraySize = 0, Int morphTargetId = -1) noexcept: MeshAttributeData{nullptr, name, format, nullptr, arraySize, morphTargetId} {} + #endif /** * @brief Constructor - * @param name Attribute name - * @param data Attribute data + * @param name Attribute name + * @param data Attribute data + * @param morphTargetId Morph target ID. Use @cpp -1 @ce for + * attributes that are not morph targets. * * Detects @ref VertexFormat based on @p T and calls - * @ref MeshAttributeData(MeshAttribute, VertexFormat, const Containers::StridedArrayView1D&, UnsignedShort). + * @ref MeshAttributeData(MeshAttribute, VertexFormat, const Containers::StridedArrayView1D&, UnsignedShort, Int). * For most types known by Magnum, the detected @ref VertexFormat is of * the same name as the type (so e.g. @ref Magnum::Vector3ui "Vector3ui" * gets recognized as @ref VertexFormat::Vector3ui), with the @@ -522,34 +550,38 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * @todo Pick a type based on the combination of T and name? E.g., for * a Tangent it would pick Vector3sNormalized instead of Vector3s */ - template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept; + template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data, Int morphTargetId = -1) noexcept; /** @overload */ - template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView& data) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data)} {} + template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::ArrayView& data, Int morphTargetId = -1) noexcept: MeshAttributeData{name, Containers::stridedArrayView(data), morphTargetId} {} /** * @brief Construct an array attribute - * @param name Attribute name - * @param data Attribute data + * @param name Attribute name + * @param data Attribute data + * @param morphTargetId Morph target ID. Use @cpp -1 @ce for + * attributes that are not morph targets. * * Detects @ref VertexFormat based on @p T and calls - * @ref MeshAttributeData(MeshAttribute, VertexFormat, const Containers::StridedArrayView1D&, UnsignedShort) + * @ref MeshAttributeData(MeshAttribute, VertexFormat, const Containers::StridedArrayView1D&, UnsignedShort, Int) * with the second dimension size passed to @p arraySize. Expects that * the second dimension is contiguous, and if @p name is a builtin - * attribute, it's an array attribute. See @ref MeshAttributeData(MeshAttribute, const Containers::StridedArrayView1D&) + * attribute, it's an array attribute. See @ref MeshAttributeData(MeshAttribute, const Containers::StridedArrayView1D&, Int) * for details about @ref VertexFormat detection. */ - template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView2D& data) noexcept; + template constexpr explicit MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView2D& data, Int morphTargetId = -1) noexcept; /** * @brief Construct an offset-only attribute - * @param name Attribute name - * @param format Attribute format - * @param offset Attribute data offset - * @param vertexCount Attribute vertex count - * @param stride Attribute stride - * @param arraySize Array size. Use @cpp 0 @ce for non-array + * @param name Attribute name + * @param format Attribute format + * @param offset Attribute data offset + * @param vertexCount Attribute vertex count + * @param stride Attribute stride + * @param arraySize Array size. Use @cpp 0 @ce for non-array * attributes. + * @param morphTargetId Morph target ID. Use @cpp -1 @ce for + * attributes that are not morph targets. * * Instances created this way refer to an offset in unspecified * external vertex data instead of containing the data view directly. @@ -570,7 +602,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * @see @ref isOffsetOnly(), @ref arraySize(), * @ref data(Containers::ArrayView) const */ - explicit constexpr MeshAttributeData(MeshAttribute name, VertexFormat format, std::size_t offset, UnsignedInt vertexCount, std::ptrdiff_t stride, UnsignedShort arraySize = 0) noexcept; + explicit constexpr MeshAttributeData(MeshAttribute name, VertexFormat format, std::size_t offset, UnsignedInt vertexCount, std::ptrdiff_t stride, UnsignedShort arraySize = 0, Int morphTargetId = -1) noexcept; /** * @brief Construct a pad value @@ -581,7 +613,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * passed to @ref MeshData. * @see @ref stride() */ - constexpr explicit MeshAttributeData(Int padding): _format{}, _name{}, _isOffsetOnly{false}, _vertexCount{0}, _stride{ + constexpr explicit MeshAttributeData(Int padding): _format{}, _name{}, _isOffsetOnly{false}, _morphTargetId{-1}, _vertexCount{0}, _stride{ (CORRADE_CONSTEXPR_ASSERT(padding >= -32768 && padding <= 32767, "Trade::MeshAttributeData: expected padding to fit into 16 bits but got" << padding), Short(padding)) }, _arraySize{}, _data{nullptr} {} @@ -592,7 +624,7 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { * Returns @cpp true @ce if the attribute doesn't contain the data view * directly, but instead refers to unspecified external vertex data. * @see @ref data(Containers::ArrayView) const, - * @ref MeshAttributeData(MeshAttribute, VertexFormat, std::size_t, UnsignedInt, std::ptrdiff_t, UnsignedShort) + * @ref MeshAttributeData(MeshAttribute, VertexFormat, std::size_t, UnsignedInt, std::ptrdiff_t, UnsignedShort, Int) */ constexpr bool isOffsetOnly() const { return _isOffsetOnly; } @@ -622,9 +654,21 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { */ constexpr Short stride() const { return _stride; } - /** @brief Attribute array size */ + /** + * @brief Attribute array size + * + * Returns @cpp 0 @ce if the attribute isn't an array. + */ constexpr UnsignedShort arraySize() const { return _arraySize; } + /** + * @brief Morph target ID + * @m_since_latest + * + * Returns @cpp -1 @ce if the attribute isn't a morph target. + */ + constexpr Int morphTargetId() const { return _morphTargetId; } + /** * @brief Type-erased attribute data * @@ -665,13 +709,16 @@ class MAGNUM_TRADE_EXPORT MeshAttributeData { /* Delegated to by all ArrayView constructors, which additionally check either stride or second dimension size. Nullptr first, to avoid accidental matches as much as possible. */ - constexpr explicit MeshAttributeData(std::nullptr_t, MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, UnsignedShort arraySize) noexcept; + constexpr explicit MeshAttributeData(std::nullptr_t, MeshAttribute name, VertexFormat format, const Containers::StridedArrayView1D& data, UnsignedShort arraySize, Int morphTargetId) noexcept; VertexFormat _format; MeshAttribute _name; bool _isOffsetOnly; - /* 1 byte free for more stuff on 64b (23, aligned to 24) and on 32b - (19, aligned to 20) */ + /* glTF spec says the expected number of morph targets is ~8, so 128 + should be enough. Signed in order to use -1 as "not a morph target", + if 128 wouldn't be enough then this could get changed to unsigned + and interpreting only 255 as -1. */ + Byte _morphTargetId; /* Vertex count in MeshData is currently 32-bit, so this doesn't need to be 64-bit either */ @@ -742,9 +789,8 @@ functions @ref positions2DAsArray(), @ref positions3DAsArray(), @ref tangentsAsArray(), @ref textureCoordinates2DAsArray(), @ref colorsAsArray(), @ref jointIdsAsArray(), @ref weightsAsArray() and @ref objectIdsAsArray(). You're expected to check for attribute presence first -with either @ref hasAttribute() (or @ref attributeCount(MeshAttribute) const, -as there can be multiple sets of texture coordinates, for example). If you are -creating a @ref GL::Mesh, the usual path forward is then to +with either @ref hasAttribute() (or @ref attributeCount(MeshAttribute, Int) const, as there can be multiple sets of texture coordinates, for example). If +you are creating a @ref GL::Mesh, the usual path forward is then to @ref MeshTools::interleave() attributes of interest, upload them to a @ref GL::Buffer and configure attribute binding for the mesh. @@ -794,6 +840,30 @@ mesh positions: If the transformation includes a rotation or non-uniform scaling, you may want to do a similar operation with normals and tangents as well. +@section Trade-MeshData-usage-morph-targets Morph targets + +By default, named attribute access (either through the @ref positions3DAsArray() +etc. convenience accesors or via @ref attribute(MeshAttribute, UnsignedInt, Int) const "attribute()" +and similar) searches only through the base attributes. Meshes that have morph +targets can have the additional attributes accessed by passing a +`morphTargetId` argument to these functions: + +@snippet MagnumTrade.cpp MeshData-usage-morph-targets + +If a base attribute doesn't have a corresponding morph target attribute (which +can be checked using @ref hasAttribute(MeshAttribute, Int) const with +appropriate `morphTargetId` passed), the base attribute is meant to be used +unchanged. Base attributes with multiple sets can have multiple sets of morph +target attributes as well (which can be again checked using +@ref attributeCount(MeshAttribute, Int) const with appropriate `morphTargetId` +passed). If only some instances from the set have a morph target, the remaining +attributes are expected to alias the base ones (i.e., have the same +@ref attributeOffset(), @ref attributeStride() and @ref attributeArraySize()) +in order to match their numbering. Finally, there can attributes that are only +defined among morph targets but have no corresponding base attribute. This +isn't restricted in any way and their treatment is left to be +application-specific. + @section Trade-MeshData-special-layouts Special data layouts The class is able to represent data layouts beyond what's supported by common @@ -1363,22 +1433,38 @@ class MAGNUM_TRADE_EXPORT MeshData { * desired vertex count). See also @ref indexCount() which returns * count of elements in the @ref indices() array, and * @ref attributeCount() which returns count of different per-vertex - * attribute arrays. + * attributes. * @see @ref MeshTools::primitiveCount(MeshPrimitive, UnsignedInt) */ UnsignedInt vertexCount() const { return _vertexCount; } /** - * @brief Attribute array count + * @brief Total attribute count * - * Count of different per-vertex attribute arrays, or @cpp 0 @ce for an - * attribute-less mesh. See also @ref indexCount() which returns count - * of elements in the @ref indices() array and @ref vertexCount() which - * returns count of elements in every @ref attribute() array. - * @see @ref attributeCount(MeshAttribute) const + * Count of all per-vertex attributes including extra attributes and + * morph targets, or @cpp 0 @ce for an attribute-less mesh. See also + * @ref indexCount() which returns count of elements in the + * @ref indices() array and @ref vertexCount() which returns count of + * elements in every @ref attribute(). + * @see @ref attributeCount(Int) const, + * @ref attributeCount(MeshAttribute, Int) const */ UnsignedInt attributeCount() const { return UnsignedInt(_attributes.size()); } + /** + * @brief Attribute count for given morph target + * @m_since_latest + * + * Count of attributes for which @ref attributeMorphTargetId() is equal + * to @p morphTargetId, or @cpp 0 @ce if there's no such morph target. + * Use @cpp -1 @ce to get the count of base attributes that aren't + * morph targets. Total number of attributes in all morph targets can + * be calculated by subtracting the value of this function with + * @cpp -1 @ce from @ref attributeCount() const. + * @see @ref attributeCount(MeshAttribute, Int) const + */ + UnsignedInt attributeCount(Int morphTargetId) const; + /** * @brief Raw attribute data * @@ -1412,14 +1498,14 @@ class MAGNUM_TRADE_EXPORT MeshData { MeshAttribute attributeName(UnsignedInt id) const; /** - * @brief Attribute ID in a set of attributes of the same name + * @brief Attribute ID in a set of attributes of the same name and morph target ID * @m_since_latest * * The @p id is expected to be smaller than @ref attributeCount() const. * Returns the number of attributes of the same @ref attributeName() - * preceeding @p id, or @cpp 0 @ce if it's the first attribute of - * given name. - * @see @ref attributeId(MeshAttribute, UnsignedInt) const + * and @ref attributeMorphTargetId() preceeding @p id, or @cpp 0 @ce if + * it's the first attribute of given name and given morph target ID. + * @see @ref attributeId(MeshAttribute, UnsignedInt, Int) const */ UnsignedInt attributeId(UnsignedInt id) const; @@ -1427,7 +1513,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * @brief Attribute format * * The @p id is expected to be smaller than @ref attributeCount() const. - * You can also use @ref attributeFormat(MeshAttribute, UnsignedInt) const + * You can also use @ref attributeFormat(MeshAttribute, UnsignedInt, Int) const * to directly get a type of given named attribute. * @see @ref attributeName(), @ref indexType() */ @@ -1441,7 +1527,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * between pointers returned from @ref vertexData() and a particular * @ref attribute(). The @p id is expected to be smaller than * @ref attributeCount() const. You can also use - * @ref attributeOffset(MeshAttribute, UnsignedInt) const to + * @ref attributeOffset(MeshAttribute, UnsignedInt, Int) const to * directly get an offset of given named attribute. * @see @ref indexOffset(), @ref MeshTools::isInterleaved() */ @@ -1455,8 +1541,8 @@ class MAGNUM_TRADE_EXPORT MeshData { * negative, such data layouts are however not commonly supported by * GPU APIs. The @p id is expected to be smaller than * @ref attributeCount() const. You can also use - * @ref attributeStride(MeshAttribute, UnsignedInt) const to directly - * get a stride of given named attribute. + * @ref attributeStride(MeshAttribute, UnsignedInt, Int) const to + * directly get a stride of given named attribute. * @see @ref MeshTools::isInterleaved() */ Short attributeStride(UnsignedInt id) const; @@ -1467,27 +1553,39 @@ class MAGNUM_TRADE_EXPORT MeshData { * In case given attribute is an array (the equivalent of e.g. * @cpp int[30] @ce), returns array size, otherwise returns @cpp 0 @ce. * The @p id is expected to be smaller than @ref attributeCount() const. - * You can also use @ref attributeArraySize(MeshAttribute, UnsignedInt) const + * You can also use @ref attributeArraySize(MeshAttribute, UnsignedInt, Int) const * to directly get array size of given named attribute. * * Note that this is different from vertex count, which is exposed * through @ref vertexCount(), and is an orthogonal concept to having * multiple attributes of the same name (for example two sets of * texture coordinates), which is exposed through - * @ref attributeCount(MeshAttribute) const. See + * @ref attributeCount(MeshAttribute, Int) const. See * @ref Trade-MeshData-populating-custom for an example. * @see @ref isMeshAttributeCustom() */ UnsignedShort attributeArraySize(UnsignedInt id) const; + /** + * @brief Attribute morph target ID + * @m_since_latest + * + * In case given attribute is a morph target, returns its ID, otherwise + * returns @cpp -1 @ce. The @p id is expected to be smaller than + * @ref attributeCount() const. + */ + Int attributeMorphTargetId(UnsignedInt id) const; + /** * @brief Whether the mesh has given attribute * - * @see @ref attributeCount(MeshAttribute) const, + * By default it checks only attributes that aren't morph targets, set + * @p morphTargetId to check the attribute for given morph target ID. + * @see @ref attributeCount(MeshAttribute, Int) const, * @ref findAttributeId() */ - bool hasAttribute(MeshAttribute name) const { - return attributeCount(name); + bool hasAttribute(MeshAttribute name, Int morphTargetId = -1) const { + return attributeCount(name, morphTargetId); } /** @@ -1495,70 +1593,75 @@ class MAGNUM_TRADE_EXPORT MeshData { * * Unlike @ref attributeCount() const this returns count for given * attribute name --- for example a mesh can have more than one set of - * texture coordinates. - * @see @ref hasAttribute() + * texture coordinates. By default it counts only attributes that + * aren't morph targets, set @p morphTargetId to count attributes for + * given morph target ID. + * @see @ref hasAttribute(), @ref attributeCount(Int) const */ - UnsignedInt attributeCount(MeshAttribute name) const; + UnsignedInt attributeCount(MeshAttribute name, Int morphTargetId = -1) const; /** * @brief Find an absolute ID of a named attribute * @m_since_latest * * If @p name isn't present or @p id is not smaller than - * @ref attributeCount(MeshAttribute) const, returns + * @ref attributeCount(MeshAttribute, Int) const, returns * @ref Containers::NullOpt. The lookup is done in an * @f$ \mathcal{O}(n) @f$ complexity with @f$ n @f$ being the attribute * count. * @see @ref hasAttribute(), @ref attributeId() */ - Containers::Optional findAttributeId(MeshAttribute name, UnsignedInt id = 0) const; + Containers::Optional findAttributeId(MeshAttribute name, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Absolute ID of a named attribute * * Like @ref findAttributeId(), but the @p id is expected to be smaller - * than @ref attributeCount(MeshAttribute) const. + * than @ref attributeCount(MeshAttribute, Int) const. * @see @ref attributeId(UnsignedInt) const */ - UnsignedInt attributeId(MeshAttribute name, UnsignedInt id = 0) const; + UnsignedInt attributeId(MeshAttribute name, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Format of a named attribute * * The @p id is expected to be smaller than - * @ref attributeCount(MeshAttribute) const. + * @ref attributeCount(MeshAttribute, Int) const. * @see @ref attributeFormat(UnsignedInt) const */ - VertexFormat attributeFormat(MeshAttribute name, UnsignedInt id = 0) const; + VertexFormat attributeFormat(MeshAttribute name, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Offset of a named attribute * * The @p id is expected to be smaller than - * @ref attributeCount(MeshAttribute) const. See + * @ref attributeCount(MeshAttribute, Int) const. See * @ref attributeOffset(UnsignedInt) const for more information. */ - std::size_t attributeOffset(MeshAttribute name, UnsignedInt id = 0) const; + std::size_t attributeOffset(MeshAttribute name, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Stride of a named attribute * * The @p id is expected to be smaller than - * @ref attributeCount(MeshAttribute) const. See + * @ref attributeCount(MeshAttribute, Int) const. See * @ref attributeStride(UnsignedInt) const for more information. */ - Short attributeStride(MeshAttribute name, UnsignedInt id = 0) const; + Short attributeStride(MeshAttribute name, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Array size of a named attribute * * The @p id is expected to be smaller than - * @ref attributeCount(MeshAttribute) const. Note that this is + * @ref attributeCount(MeshAttribute, Int) const. Note that this is * different from vertex count, and is an orthogonal concept to having * multiple attributes of the same name --- see * @ref attributeArraySize(UnsignedInt) const for more information. */ - UnsignedShort attributeArraySize(MeshAttribute name, UnsignedInt id = 0) const; + UnsignedShort attributeArraySize(MeshAttribute name, UnsignedInt id = 0, Int morphTargetId = -1) const; + + /* No attributeMorphTargetId(MeshAttribute, ...) overload because the + lookup is for a concrete morphTargetId already */ /** * @brief Data for given attribute @@ -1573,8 +1676,8 @@ class MAGNUM_TRADE_EXPORT MeshData { * * Use the templated overload below to get the attribute in a concrete * type. You can also use - * @ref attribute(MeshAttribute, UnsignedInt) const to directly get - * data for given named attribute. + * @ref attribute(MeshAttribute, UnsignedInt, Int) const to directly + * get data for given named attribute. * @see @relativeref{Corrade,Containers::StridedArrayView::isContiguous()}, * @ref vertexFormatSize(), * @ref isVertexFormatImplementationSpecific() @@ -1611,7 +1714,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref objectIdsAsArray() accessors to get common attributes converted * to usual types in contiguous arrays, but note that these operations * involve extra allocation and data conversion. - * @see @ref attribute(MeshAttribute, UnsignedInt) const, + * @see @ref attribute(MeshAttribute, UnsignedInt, Int) const, * @ref mutableAttribute(UnsignedInt), * @ref isVertexFormatImplementationSpecific(), * @ref attributeArraySize() @@ -1655,34 +1758,33 @@ class MAGNUM_TRADE_EXPORT MeshData { * @brief Data for given named attribute * * The @p id is expected to be smaller than - * @ref attributeCount(MeshAttribute) const. See + * @ref attributeCount(MeshAttribute, Int) const. See * @ref attribute(UnsignedInt) const for more information. Use the * templated overload below to get the attribute in a concrete type. - * @see @ref attribute(UnsignedInt) const, - * @ref mutableAttribute(MeshAttribute, UnsignedInt), + * @see @ref mutableAttribute(MeshAttribute, UnsignedInt, Int), * @relativeref{Corrade,Containers::StridedArrayView::isContiguous()}, * @ref isVertexFormatImplementationSpecific() */ - Containers::StridedArrayView2D attribute(MeshAttribute name, UnsignedInt id = 0) const; + Containers::StridedArrayView2D attribute(MeshAttribute name, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Mutable data for given named attribute * - * Like @ref attribute(MeshAttribute, UnsignedInt) const, but returns a - * mutable view. Expects that the mesh is mutable. + * Like @ref attribute(MeshAttribute, UnsignedInt, Int) const, but + * returns a mutable view. Expects that the mesh is mutable. * @see @ref vertexDataFlags() */ - Containers::StridedArrayView2D mutableAttribute(MeshAttribute name, UnsignedInt id = 0); + Containers::StridedArrayView2D mutableAttribute(MeshAttribute name, UnsignedInt id = 0, Int morphTargetId = -1); /** * @brief Data for given named attribute in a concrete type * * The @p id is expected to be smaller than - * @ref attributeCount(MeshAttribute) const and @p T is expected to - * correspond to @ref attributeFormat(MeshAttribute, UnsignedInt) const. + * @ref attributeCount(MeshAttribute, Int) const and @p T is expected + * to correspond to @ref attributeFormat(MeshAttribute, UnsignedInt, Int) const. * Expects that the vertex format is *not* implementation-specific, in * that case you can only access the attribute via the typeless - * @ref attribute(MeshAttribute, UnsignedInt) const above. The + * @ref attribute(MeshAttribute, UnsignedInt, Int) const above. The * attribute is also expected to not be an array, in that case you need * to use the overload below by using @cpp T[] @ce instead of * @cpp T @ce. In rare cases the stride of the returned view may be @@ -1697,10 +1799,10 @@ class MAGNUM_TRADE_EXPORT MeshData { * to usual types in contiguous arrays, but note that these operations * involve extra data conversion and an allocation. * @see @ref attribute(UnsignedInt) const, - * @ref mutableAttribute(MeshAttribute, UnsignedInt), + * @ref mutableAttribute(MeshAttribute, UnsignedInt, Int), * @ref isVertexFormatImplementationSpecific() */ - template::value>::type> Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0) const; + template::value>::type> Containers::StridedArrayView1D attribute(MeshAttribute name, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Data for given named array attribute in a concrete type @@ -1712,16 +1814,16 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref attributeArraySize() for given attribute. For non-array * attributes the second dimension has a size of @cpp 1 @ce. */ - template::value>::type> Containers::StridedArrayView2D::type> attribute(MeshAttribute name, UnsignedInt id = 0) const; + template::value>::type> Containers::StridedArrayView2D::type> attribute(MeshAttribute name, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Mutable data for given named attribute in a concrete type * - * Like @ref attribute(MeshAttribute, UnsignedInt) const, but returns a - * mutable view. Expects that the mesh is mutable. + * Like @ref attribute(MeshAttribute, UnsignedInt, Int) const, but + * returns a mutable view. Expects that the mesh is mutable. * @see @ref vertexDataFlags() */ - template::value>::type> Containers::StridedArrayView1D mutableAttribute(MeshAttribute name, UnsignedInt id = 0); + template::value>::type> Containers::StridedArrayView1D mutableAttribute(MeshAttribute name, UnsignedInt id = 0, Int morphTargetId = -1); /** * @brief Mutable data for given named array attribute in a concrete type @@ -1733,7 +1835,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref attributeArraySize() for given attribute. For non-array * attributes the second dimension has a size of @cpp 1 @ce. */ - template::value>::type> Containers::StridedArrayView2D::type> mutableAttribute(MeshAttribute name, UnsignedInt id = 0); + template::value>::type> Containers::StridedArrayView2D::type> mutableAttribute(MeshAttribute name, UnsignedInt id = 0, Int morphTargetId = -1); /** * @brief Indices as 32-bit integers @@ -1758,17 +1860,17 @@ class MAGNUM_TRADE_EXPORT MeshData { /** * @brief Positions as 2D float vectors * - * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt, Int) const * with @ref MeshAttribute::Position as the first argument. Converts * the position array from an arbitrary underlying type and returns it * in a newly-allocated array. If the underlying type is * three-component, the last component is dropped. Expects that the * vertex format is *not* implementation-specific, in that case you can - * only access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * only access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt, Int) const. * @see @ref positions2DInto(), @ref attributeFormat(), * @ref isVertexFormatImplementationSpecific() */ - Containers::Array positions2DAsArray(UnsignedInt id = 0) const; + Containers::Array positions2DAsArray(UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Positions as 2D float vectors into a pre-allocated view @@ -1778,22 +1880,22 @@ class MAGNUM_TRADE_EXPORT MeshData { * @p destination is sized to contain exactly all data. * @see @ref vertexCount() */ - void positions2DInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0) const; + void positions2DInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Positions as 3D float vectors * - * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt, Int) const * with @ref MeshAttribute::Position as the first argument. Converts * the position array from an arbitrary underlying type and returns it * in a newly-allocated array. If the underlying type is two-component, * the Z component is set to @cpp 0.0f @ce. Expects that the vertex * format is *not* implementation-specific, in that case you can only - * access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt, Int) const. * @see @ref positions3DInto(), @ref attributeFormat(), * @ref isVertexFormatImplementationSpecific() */ - Containers::Array positions3DAsArray(UnsignedInt id = 0) const; + Containers::Array positions3DAsArray(UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Positions as 3D float vectors into a pre-allocated view @@ -1803,17 +1905,17 @@ class MAGNUM_TRADE_EXPORT MeshData { * @p destination is sized to contain exactly all data. * @see @ref vertexCount() */ - void positions3DInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0) const; + void positions3DInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Tangents as 3D float vectors * - * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt, Int) const * with @ref MeshAttribute::Tangent as the first argument. Converts the * tangent array from an arbitrary underlying type and returns it in a * newly-allocated array. Expects that the vertex format is *not* * implementation-specific, in that case you can only access the - * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt, Int) const. * * If the tangents contain a fourth component with bitangent direction, * it's ignored here --- use @ref bitangentSignsAsArray() to get those @@ -1823,7 +1925,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref attributeFormat(), * @ref isVertexFormatImplementationSpecific() */ - Containers::Array tangentsAsArray(UnsignedInt id = 0) const; + Containers::Array tangentsAsArray(UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Tangents as 3D float vectors into a pre-allocated view @@ -1834,7 +1936,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * extract the fourth component wit bitangent direction, if present. * @see @ref vertexCount() */ - void tangentsInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0) const; + void tangentsInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Bitangent signs as floats @@ -1847,7 +1949,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref normalsAsArray(), @ref attributeFormat(), * @ref isVertexFormatImplementationSpecific() */ - Containers::Array bitangentSignsAsArray(UnsignedInt id = 0) const; + Containers::Array bitangentSignsAsArray(UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Bitangent signs as floats into a pre-allocated view @@ -1857,17 +1959,17 @@ class MAGNUM_TRADE_EXPORT MeshData { * @p destination is sized to contain exactly all data. * @see @ref vertexCount() */ - void bitangentSignsInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0) const; + void bitangentSignsInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Bitangents as 3D float vectors * - * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt, Int) const * with @ref MeshAttribute::Bitangent as the first argument. Converts * the bitangent array from an arbitrary underlying type and returns it * in a newly-allocated array. Expects that the vertex format is *not* * implementation-specific, in that case you can only access the - * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt, Int) const. * * Note that in some cases the bitangents aren't provided directly but * calculated from normals and four-component tangents. In that case @@ -1878,7 +1980,7 @@ class MAGNUM_TRADE_EXPORT MeshData { * @ref normalsAsArray(), @ref attributeFormat(), * @ref isVertexFormatImplementationSpecific() */ - Containers::Array bitangentsAsArray(UnsignedInt id = 0) const; + Containers::Array bitangentsAsArray(UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Bitangents as 3D float vectors into a pre-allocated view @@ -1888,22 +1990,22 @@ class MAGNUM_TRADE_EXPORT MeshData { * @p destination is sized to contain exactly all data. * @see @ref vertexCount() */ - void bitangentsInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0) const; + void bitangentsInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Normals as 3D float vectors * - * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt, Int) const * with @ref MeshAttribute::Normal as the first argument. Converts the * normal array from an arbitrary underlying type and returns it in a * newly-allocated array. Expects that the vertex format is *not* * implementation-specific, in that case you can only access the - * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt, Int) const. * @see @ref normalsInto(), @ref tangentsAsArray(), * @ref bitangentsAsArray(), @ref attributeFormat(), * @ref isVertexFormatImplementationSpecific() */ - Containers::Array normalsAsArray(UnsignedInt id = 0) const; + Containers::Array normalsAsArray(UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Normals as 3D float vectors into a pre-allocated view @@ -1913,22 +2015,22 @@ class MAGNUM_TRADE_EXPORT MeshData { * sized to contain exactly all data. * @see @ref vertexCount() */ - void normalsInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0) const; + void normalsInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Texture coordinates as 2D float vectors * - * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt, Int) const * with @ref MeshAttribute::TextureCoordinates as the first argument. * Converts the texture coordinate array from an arbitrary underlying * type and returns it in a newly-allocated array. Expects that the * vertex format is *not* implementation-specific, in that case you can * only access the attribute via the typeless - * @ref attribute(MeshAttribute, UnsignedInt) const. + * @ref attribute(MeshAttribute, UnsignedInt, Int) const. * @see @ref textureCoordinates2DInto(), @ref attributeFormat(), * @ref isVertexFormatImplementationSpecific() */ - Containers::Array textureCoordinates2DAsArray(UnsignedInt id = 0) const; + Containers::Array textureCoordinates2DAsArray(UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Texture coordinates as 2D float vectors into a pre-allocated view @@ -1938,22 +2040,22 @@ class MAGNUM_TRADE_EXPORT MeshData { * @p destination is sized to contain exactly all data. * @see @ref vertexCount() */ - void textureCoordinates2DInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0) const; + void textureCoordinates2DInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Colors as RGBA floats * - * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt, Int) const * with @ref MeshAttribute::Color as the first argument. Converts the * color array from an arbitrary underlying type and returns it in a * newly-allocated array. If the underlying type is three-component, * the alpha component is set to @cpp 1.0f @ce. Expects that the vertex * format is *not* implementation-specific, in that case you can only - * access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * access the attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt, Int) const. * @see @ref colorsInto(), @ref attributeFormat(), * @ref isVertexFormatImplementationSpecific() */ - Containers::Array colorsAsArray(UnsignedInt id = 0) const; + Containers::Array colorsAsArray(UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Colors as RGBA floats into a pre-allocated view @@ -1963,21 +2065,23 @@ class MAGNUM_TRADE_EXPORT MeshData { * sized to contain exactly all data. * @see @ref vertexCount() */ - void colorsInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0) const; + void colorsInto(const Containers::StridedArrayView1D& destination, UnsignedInt id = 0, Int morphTargetId = -1) const; /** * @brief Skin joint IDs as unsigned int arrays * @m_since_latest * - * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt, Int) const * with @ref MeshAttribute::JointIds as the first argument. Converts * the joint IDs array from an arbitrary underlying type and returns it * in a newly-allocated array. Expects that the vertex format is *not* * implementation-specific, in that case you can only access the - * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt, Int) const. + * Unlike other attributes, @ref MeshAttribute::JointIds can't have + * morph targets so this function provides no morph target ID argument. * * As it's an array attribute, the returned array has @ref vertexCount() - * times @ref attributeArraySize(MeshAttribute, UnsignedInt) const + * times @ref attributeArraySize(MeshAttribute, UnsignedInt, Int) const * elements. You can make a 2D view onto the result to conveniently * index the data: * @@ -2014,15 +2118,17 @@ class MAGNUM_TRADE_EXPORT MeshData { * @brief Skin weights as float arrays * @m_since_latest * - * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt, Int) const * with @ref MeshAttribute::Weights as the first argument. Converts the * weights array from an arbitrary underlying types and returns them in * a newly-allocated array. Expects that the vertex format is *not* * implementation-specific, in that case you can only access the - * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt, Int) const. + * Unlike other attributes, @ref MeshAttribute::Weights can't have + * morph targets so this function provides no morph target ID argument. * * As it's an array attribute, the returned array has @ref vertexCount() - * times @ref attributeArraySize(MeshAttribute, UnsignedInt) const + * times @ref attributeArraySize(MeshAttribute, UnsignedInt, Int) const * elements. You can make a 2D view onto the result to conveniently * index the data, see @ref jointIdsAsArray() for an example snippet. * @see @ref weightsInto(), @ref attributeFormat(), @@ -2046,12 +2152,14 @@ class MAGNUM_TRADE_EXPORT MeshData { /** * @brief Object IDs as 32-bit integers * - * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt, Int) const * with @ref MeshAttribute::ObjectId as the first argument. Converts * the object ID array from an arbitrary underlying type and returns it * in a newly-allocated array. Expects that the vertex format is *not* * implementation-specific, in that case you can only access the - * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt) const. + * attribute via the typeless @ref attribute(MeshAttribute, UnsignedInt, Int) const. + * Unlike other attributes, @ref MeshAttribute::ObjectId can't have + * morph targets so this function provides no morph target ID argument. * @see @ref objectIdsInto(), @ref attributeFormat(), * @ref isVertexFormatImplementationSpecific() */ @@ -2134,7 +2242,7 @@ class MAGNUM_TRADE_EXPORT MeshData { /* Internal helper without the extra overhead from Optional, returns ~UnsignedInt{} on failure */ - UnsignedInt findAttributeIdInternal(MeshAttribute name, UnsignedInt id) const; + UnsignedInt findAttributeIdInternal(MeshAttribute name, UnsignedInt id, Int morphTargetId) const; /* Like attribute(), but returning just a 1D view */ MAGNUM_TRADE_LOCAL Containers::StridedArrayView1D attributeDataViewInternal(const MeshAttributeData& attribute) const; @@ -2464,14 +2572,31 @@ namespace Implementation { return name == MeshAttribute::JointIds || name == MeshAttribute::Weights; } + + constexpr bool isMorphTargetAllowed(MeshAttribute name) { + /* It also makes no sense for custom attributes with non-normalized + integer formats to be morph targets, but that's impossible to check + in a constexpr context so we blacklist only the builtin integer + attributes. */ + return + name != MeshAttribute::JointIds && + name != MeshAttribute::Weights && + name != MeshAttribute::ObjectId; + } #endif } -constexpr MeshAttributeData::MeshAttributeData(std::nullptr_t, const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, const UnsignedShort arraySize) noexcept: +constexpr MeshAttributeData::MeshAttributeData(std::nullptr_t, const MeshAttribute name, const VertexFormat format, const Containers::StridedArrayView1D& data, const UnsignedShort arraySize, const Int morphTargetId) noexcept: _format{format}, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format), "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name)}, _isOffsetOnly{false}, + _morphTargetId{( + CORRADE_CONSTEXPR_ASSERT(morphTargetId == -1 || UnsignedInt(morphTargetId) < 128, + "Trade::MeshAttributeData: expected morph target ID to be either -1 or less than 128 but got" << morphTargetId), + CORRADE_CONSTEXPR_ASSERT(morphTargetId == -1 || Implementation::isMorphTargetAllowed(name), + "Trade::MeshAttributeData: morph target not allowed for" << name), + Byte(morphTargetId))}, _vertexCount{( #ifndef CORRADE_TARGET_32BIT CORRADE_CONSTEXPR_ASSERT(data.size() <= 0xffffffffu, "Trade::MeshAttributeData: expected vertex count to fit into 32 bits but got" << data.size()), @@ -2486,11 +2611,18 @@ constexpr MeshAttributeData::MeshAttributeData(std::nullptr_t, const MeshAttribu arraySize)}, _data{data.data()} {} -constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const std::size_t offset, const UnsignedInt vertexCount, const std::ptrdiff_t stride, UnsignedShort arraySize) noexcept: +constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const VertexFormat format, const std::size_t offset, const UnsignedInt vertexCount, const std::ptrdiff_t stride, UnsignedShort arraySize, const Int morphTargetId) noexcept: _format{format}, _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isVertexFormatCompatibleWithAttribute(name, format), "Trade::MeshAttributeData:" << format << "is not a valid format for" << name), name)}, - _isOffsetOnly{true}, _vertexCount{vertexCount}, + _isOffsetOnly{true}, + _morphTargetId{( + CORRADE_CONSTEXPR_ASSERT(morphTargetId == -1 || UnsignedInt(morphTargetId) < 128, + "Trade::MeshAttributeData: expected morph target ID to be either -1 or less than 128 but got" << morphTargetId), + CORRADE_CONSTEXPR_ASSERT(morphTargetId == -1 || Implementation::isMorphTargetAllowed(name), + "Trade::MeshAttributeData: morph target not allowed for" << name), + Byte(morphTargetId))}, + _vertexCount{vertexCount}, _stride{(CORRADE_CONSTEXPR_ASSERT(stride >= -32768 && stride <= 32767, "Trade::MeshAttributeData: expected stride to fit into 16 bits but got" << stride), Short(stride))}, @@ -2498,15 +2630,16 @@ constexpr MeshAttributeData::MeshAttributeData(const MeshAttribute name, const V "Trade::MeshAttributeData:" << name << "can't be an array attribute"), arraySize)}, _data{offset} {} -template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data) noexcept: MeshAttributeData{nullptr, name, Implementation::vertexFormatFor::type>(), data, 0} {} +template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView1D& data, const Int morphTargetId) noexcept: MeshAttributeData{nullptr, name, Implementation::vertexFormatFor::type>(), data, 0, morphTargetId} {} -template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView2D& data) noexcept: MeshAttributeData{ +template constexpr MeshAttributeData::MeshAttributeData(MeshAttribute name, const Containers::StridedArrayView2D& data, const Int morphTargetId) noexcept: MeshAttributeData{ /* Not using isContiguous<1>() as that's not constexpr */ (CORRADE_CONSTEXPR_ASSERT(data.stride()[1] == sizeof(T), "Trade::MeshAttributeData: second view dimension is not contiguous"), nullptr), name, Implementation::vertexFormatFor::type>(), Containers::StridedArrayView1D{{data.data(), ~std::size_t{}}, data.size()[0], data.stride()[0]}, - UnsignedShort(data.size()[1]) + UnsignedShort(data.size()[1]), + morphTargetId } {} template Containers::StridedArrayView1D MeshData::indices() const { @@ -2597,10 +2730,14 @@ template Containers::StridedArrayView2D::type>(data); } -template Containers::StridedArrayView1D MeshData::attribute(const MeshAttribute name, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, - "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); +template Containers::StridedArrayView1D MeshData::attribute(const MeshAttribute name, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(name, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes", {}); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes in morph target" << morphTargetId, {}); + #endif const Containers::StridedArrayView2D data = attribute(attributeId); /* Unlike mutableAttribute(), the above can't fail, so no early return with CORRADE_GRACEFUL_ASSERT */ @@ -2610,10 +2747,14 @@ template Containers::StridedArrayView1D MeshData::attri return Containers::arrayCast<1, const T>(data); } -template Containers::StridedArrayView2D::type> MeshData::attribute(const MeshAttribute name, const UnsignedInt id) const { - const UnsignedInt attributeId = findAttributeIdInternal(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, - "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); +template Containers::StridedArrayView2D::type> MeshData::attribute(const MeshAttribute name, const UnsignedInt id, const Int morphTargetId) const { + const UnsignedInt attributeId = findAttributeIdInternal(name, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes", {}); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::attribute(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes in morph target" << morphTargetId, {}); + #endif const Containers::StridedArrayView2D data = attribute(attributeId); /* Unlike mutableAttribute(), the above can't fail, so no early return with CORRADE_GRACEFUL_ASSERT */ @@ -2623,10 +2764,14 @@ template Containers::StridedArrayView2D::type>(data); } -template Containers::StridedArrayView1D MeshData::mutableAttribute(const MeshAttribute name, const UnsignedInt id) { - const UnsignedInt attributeId = findAttributeIdInternal(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, - "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); +template Containers::StridedArrayView1D MeshData::mutableAttribute(const MeshAttribute name, const UnsignedInt id, const Int morphTargetId) { + const UnsignedInt attributeId = findAttributeIdInternal(name, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes", {}); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes in morph target" << morphTargetId, {}); + #endif Containers::StridedArrayView2D data = mutableAttribute(attributeId); #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; @@ -2637,10 +2782,14 @@ template Containers::StridedArrayView1D MeshData::mutableAttr return Containers::arrayCast<1, T>(data); } -template Containers::StridedArrayView2D::type> MeshData::mutableAttribute(const MeshAttribute name, const UnsignedInt id) { - const UnsignedInt attributeId = findAttributeIdInternal(name, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, - "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name) << name << "attributes", {}); +template Containers::StridedArrayView2D::type> MeshData::mutableAttribute(const MeshAttribute name, const UnsignedInt id, const Int morphTargetId) { + const UnsignedInt attributeId = findAttributeIdInternal(name, id, morphTargetId); + #ifndef CORRADE_NO_ASSERT + if(morphTargetId == -1) CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes", {}); + else CORRADE_ASSERT(attributeId != ~UnsignedInt{}, + "Trade::MeshData::mutableAttribute(): index" << id << "out of range for" << attributeCount(name, morphTargetId) << name << "attributes in morph target" << morphTargetId, {}); + #endif Containers::StridedArrayView2D data = mutableAttribute(attributeId); #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ if(!data.stride()[1]) return {}; diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 2c59d258b..302775eb1 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -65,33 +65,45 @@ struct MeshDataTest: TestSuite::Tester { void constructIndexNullptr(); void constructAttribute(); + void constructAttributeMorphTarget(); void constructAttributeDefault(); void constructAttributeCustom(); void constructAttribute2D(); + void constructAttribute2DMorphTarget(); void constructAttribute2DWrongSize(); void constructAttribute2DNonContiguous(); void constructAttributeTypeErased(); + void constructAttributeTypeErasedMorphTarget(); void constructAttributeNullptr(); + void constructAttributeNullptrMorphTarget(); void constructAttributePadding(); void constructAttributeNonOwningArray(); void constructAttributeOffsetOnly(); + void constructAttributeOffsetOnlyMorphTarget(); void constructAttributeImplementationSpecificFormat(); void constructAttributeWrongFormat(); #ifndef CORRADE_TARGET_32BIT void constructAttributeWrongSize(); #endif void constructAttributeWrongStride(); + void constructAttributeWrongMorphTargetId(); + void constructAttributeMorphTargetNotAllowed(); void constructAttributeOnlyArrayAllowed(); void constructAttributeWrongDataAccess(); void constructArrayAttribute(); + void constructArrayAttributeMorphTarget(); void constructArrayAttributeNonContiguous(); void constructArrayAttribute2D(); + void constructArrayAttribute2DMorphTarget(); void constructArrayAttribute2DWrongSize(); void constructArrayAttribute2DNonContiguous(); void constructArrayAttributeTypeErased(); + void constructArrayAttributeTypeErasedMorphTarget(); void constructArrayAttributeNullptr(); + void constructArrayAttributeNullptrMorphTarget(); void constructArrayAttributeOffsetOnly(); + void constructArrayAttributeOffsetOnlyMorphTarget(); void constructArrayAttributeImplementationSpecificFormat(); void constructArrayAttributeNotAllowed(); @@ -231,6 +243,15 @@ const struct { {"mutable", DataFlag::Mutable} }; +const struct { + const char* name; + UnsignedInt id; + Int morphTargetId; +} AsArrayData[]{ + {"", 1, -1}, + {"morph target", 0, 37} +}; + MeshDataTest::MeshDataTest() { addTests({&MeshDataTest::customAttributeName, &MeshDataTest::customAttributeNameTooLarge, @@ -255,33 +276,45 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::constructIndexNullptr, &MeshDataTest::constructAttribute, + &MeshDataTest::constructAttributeMorphTarget, &MeshDataTest::constructAttributeDefault, &MeshDataTest::constructAttributeCustom, &MeshDataTest::constructAttribute2D, + &MeshDataTest::constructAttribute2DMorphTarget, &MeshDataTest::constructAttribute2DWrongSize, &MeshDataTest::constructAttribute2DNonContiguous, &MeshDataTest::constructAttributeTypeErased, + &MeshDataTest::constructAttributeTypeErasedMorphTarget, &MeshDataTest::constructAttributeNullptr, + &MeshDataTest::constructAttributeNullptrMorphTarget, &MeshDataTest::constructAttributePadding, &MeshDataTest::constructAttributeNonOwningArray, &MeshDataTest::constructAttributeOffsetOnly, + &MeshDataTest::constructAttributeOffsetOnlyMorphTarget, &MeshDataTest::constructAttributeImplementationSpecificFormat, &MeshDataTest::constructAttributeWrongFormat, #ifndef CORRADE_TARGET_32BIT &MeshDataTest::constructAttributeWrongSize, #endif &MeshDataTest::constructAttributeWrongStride, + &MeshDataTest::constructAttributeWrongMorphTargetId, + &MeshDataTest::constructAttributeMorphTargetNotAllowed, &MeshDataTest::constructAttributeOnlyArrayAllowed, &MeshDataTest::constructAttributeWrongDataAccess, &MeshDataTest::constructArrayAttribute, + &MeshDataTest::constructArrayAttributeMorphTarget, &MeshDataTest::constructArrayAttributeNonContiguous, &MeshDataTest::constructArrayAttribute2D, + &MeshDataTest::constructArrayAttribute2DMorphTarget, &MeshDataTest::constructArrayAttribute2DWrongSize, &MeshDataTest::constructArrayAttribute2DNonContiguous, &MeshDataTest::constructArrayAttributeTypeErased, + &MeshDataTest::constructArrayAttributeTypeErasedMorphTarget, &MeshDataTest::constructArrayAttributeNullptr, + &MeshDataTest::constructArrayAttributeNullptrMorphTarget, &MeshDataTest::constructArrayAttributeOffsetOnly, + &MeshDataTest::constructArrayAttributeOffsetOnlyMorphTarget, &MeshDataTest::constructArrayAttributeImplementationSpecificFormat, &MeshDataTest::constructArrayAttributeNotAllowed}); @@ -338,12 +371,16 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::indicesAsArray, &MeshDataTest::indicesAsArray, &MeshDataTest::indicesAsArray, - &MeshDataTest::indicesIntoArrayInvalidSize, - &MeshDataTest::positions2DAsArray, - &MeshDataTest::positions2DAsArray, - &MeshDataTest::positions2DAsArray, - &MeshDataTest::positions2DAsArray, - &MeshDataTest::positions2DAsArrayPackedUnsigned, + &MeshDataTest::indicesIntoArrayInvalidSize}); + + addInstancedTests({ + &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DAsArray, + &MeshDataTest::positions2DAsArray}, + Containers::arraySize(AsArrayData)); + + addTests({&MeshDataTest::positions2DAsArrayPackedUnsigned, &MeshDataTest::positions2DAsArrayPackedUnsigned, &MeshDataTest::positions2DAsArrayPackedUnsigned, &MeshDataTest::positions2DAsArrayPackedUnsigned, @@ -359,12 +396,16 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::positions2DAsArrayPackedSignedNormalized, &MeshDataTest::positions2DAsArrayPackedSignedNormalized, &MeshDataTest::positions2DAsArrayPackedSignedNormalized, - &MeshDataTest::positions2DIntoArrayInvalidSize, - &MeshDataTest::positions3DAsArray, - &MeshDataTest::positions3DAsArray, - &MeshDataTest::positions3DAsArray, - &MeshDataTest::positions3DAsArray, - &MeshDataTest::positions3DAsArrayPackedUnsigned, + &MeshDataTest::positions2DIntoArrayInvalidSize}); + + addInstancedTests({ + &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DAsArray, + &MeshDataTest::positions3DAsArray}, + Containers::arraySize(AsArrayData)); + + addTests({&MeshDataTest::positions3DAsArrayPackedUnsigned, &MeshDataTest::positions3DAsArrayPackedUnsigned, &MeshDataTest::positions3DAsArrayPackedUnsigned, &MeshDataTest::positions3DAsArrayPackedUnsigned, @@ -380,35 +421,55 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::positions3DAsArrayPackedSignedNormalized, &MeshDataTest::positions3DAsArrayPackedSignedNormalized, &MeshDataTest::positions3DAsArrayPackedSignedNormalized, - &MeshDataTest::positions3DIntoArrayInvalidSize, - &MeshDataTest::tangentsAsArray, - &MeshDataTest::tangentsAsArray, - &MeshDataTest::tangentsAsArray, - &MeshDataTest::tangentsAsArray, - &MeshDataTest::tangentsAsArrayPackedSignedNormalized, + &MeshDataTest::positions3DIntoArrayInvalidSize}); + + addInstancedTests({ + &MeshDataTest::tangentsAsArray, + &MeshDataTest::tangentsAsArray, + &MeshDataTest::tangentsAsArray, + &MeshDataTest::tangentsAsArray}, + Containers::arraySize(AsArrayData)); + + addTests({&MeshDataTest::tangentsAsArrayPackedSignedNormalized, &MeshDataTest::tangentsAsArrayPackedSignedNormalized, &MeshDataTest::tangentsAsArrayPackedSignedNormalized, &MeshDataTest::tangentsAsArrayPackedSignedNormalized, - &MeshDataTest::tangentsIntoArrayInvalidSize, - &MeshDataTest::bitangentSignsAsArray, - &MeshDataTest::bitangentSignsAsArray, - &MeshDataTest::bitangentSignsAsArrayPackedSignedNormalized, + &MeshDataTest::tangentsIntoArrayInvalidSize}); + + addInstancedTests({ + &MeshDataTest::bitangentSignsAsArray, + &MeshDataTest::bitangentSignsAsArray}, + Containers::arraySize(AsArrayData)); + + addTests({&MeshDataTest::bitangentSignsAsArrayPackedSignedNormalized, &MeshDataTest::bitangentSignsAsArrayPackedSignedNormalized, &MeshDataTest::bitangentSignsAsArrayNotFourComponent, - &MeshDataTest::bitangentSignsIntoArrayInvalidSize, - &MeshDataTest::bitangentsAsArray, - &MeshDataTest::bitangentsAsArray, - &MeshDataTest::bitangentsAsArrayPackedSignedNormalized, + &MeshDataTest::bitangentSignsIntoArrayInvalidSize}); + + addInstancedTests({ + &MeshDataTest::bitangentsAsArray, + &MeshDataTest::bitangentsAsArray}, + Containers::arraySize(AsArrayData)); + + addTests({&MeshDataTest::bitangentsAsArrayPackedSignedNormalized, &MeshDataTest::bitangentsAsArrayPackedSignedNormalized, - &MeshDataTest::bitangentsIntoArrayInvalidSize, - &MeshDataTest::normalsAsArray, - &MeshDataTest::normalsAsArray, - &MeshDataTest::normalsAsArrayPackedSignedNormalized, + &MeshDataTest::bitangentsIntoArrayInvalidSize}); + + addInstancedTests({ + &MeshDataTest::normalsAsArray, + &MeshDataTest::normalsAsArray}, + Containers::arraySize(AsArrayData)); + + addTests({&MeshDataTest::normalsAsArrayPackedSignedNormalized, &MeshDataTest::normalsAsArrayPackedSignedNormalized, - &MeshDataTest::normalsIntoArrayInvalidSize, - &MeshDataTest::textureCoordinates2DAsArray, - &MeshDataTest::textureCoordinates2DAsArray, - &MeshDataTest::textureCoordinates2DAsArrayPackedUnsigned, + &MeshDataTest::normalsIntoArrayInvalidSize}); + + addInstancedTests({ + &MeshDataTest::textureCoordinates2DAsArray, + &MeshDataTest::textureCoordinates2DAsArray}, + Containers::arraySize(AsArrayData)); + + addTests({&MeshDataTest::textureCoordinates2DAsArrayPackedUnsigned, &MeshDataTest::textureCoordinates2DAsArrayPackedUnsigned, &MeshDataTest::textureCoordinates2DAsArrayPackedSigned, &MeshDataTest::textureCoordinates2DAsArrayPackedSigned, @@ -416,12 +477,16 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::textureCoordinates2DAsArrayPackedUnsignedNormalized, &MeshDataTest::textureCoordinates2DAsArrayPackedSignedNormalized, &MeshDataTest::textureCoordinates2DAsArrayPackedSignedNormalized, - &MeshDataTest::textureCoordinates2DIntoArrayInvalidSize, - &MeshDataTest::colorsAsArray, - &MeshDataTest::colorsAsArray, - &MeshDataTest::colorsAsArray, - &MeshDataTest::colorsAsArray, - &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, + &MeshDataTest::textureCoordinates2DIntoArrayInvalidSize}); + + addInstancedTests({ + &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsAsArray, + &MeshDataTest::colorsAsArray}, + Containers::arraySize(AsArrayData)); + + addTests({&MeshDataTest::colorsAsArrayPackedUnsignedNormalized, &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, @@ -819,6 +884,7 @@ void MeshDataTest::constructAttribute() { MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(positionData)}; CORRADE_VERIFY(!positions.isOffsetOnly()); CORRADE_COMPARE(positions.arraySize(), 0); + CORRADE_COMPARE(positions.morphTargetId(), -1); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_COMPARE(positions.offset(positionData), 0); @@ -833,12 +899,48 @@ void MeshDataTest::constructAttribute() { constexpr MeshAttributeData cpositions{MeshAttribute::Position, Containers::arrayView(Positions)}; constexpr bool isOffsetOnly = cpositions.isOffsetOnly(); constexpr UnsignedShort arraySize = cpositions.arraySize(); + constexpr Int morphTargetId = cpositions.morphTargetId(); constexpr MeshAttribute name = cpositions.name(); constexpr VertexFormat format = cpositions.format(); constexpr Short stride = cpositions.stride(); constexpr Containers::StridedArrayView1D data = cpositions.data(); CORRADE_VERIFY(!isOffsetOnly); CORRADE_COMPARE(arraySize, 0); + CORRADE_COMPARE(morphTargetId, -1); + CORRADE_COMPARE(name, MeshAttribute::Position); + CORRADE_COMPARE(format, VertexFormat::Vector2); + CORRADE_COMPARE(stride, sizeof(Vector2)); + CORRADE_COMPARE(data.data(), Positions); +} + +void MeshDataTest::constructAttributeMorphTarget() { + const Vector2 positionData[3]; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(positionData), 15}; + CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); + CORRADE_COMPARE(positions.morphTargetId(), 15); + CORRADE_COMPARE(positions.name(), MeshAttribute::Position); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); + CORRADE_COMPARE(positions.offset(positionData), 0); + CORRADE_COMPARE(positions.stride(), sizeof(Vector2)); + CORRADE_VERIFY(positions.data().data() == positionData); + + /* This is allowed too for simplicity, the parameter has to be large enough + tho */ + char someArray[3*sizeof(Vector2)]; + CORRADE_VERIFY(positions.data(someArray).data() == positionData); + + constexpr MeshAttributeData cpositions{MeshAttribute::Position, Containers::arrayView(Positions), 15}; + constexpr bool isOffsetOnly = cpositions.isOffsetOnly(); + constexpr UnsignedShort arraySize = cpositions.arraySize(); + constexpr Int morphTargetId = cpositions.morphTargetId(); + constexpr MeshAttribute name = cpositions.name(); + constexpr VertexFormat format = cpositions.format(); + constexpr Short stride = cpositions.stride(); + constexpr Containers::StridedArrayView1D data = cpositions.data(); + CORRADE_VERIFY(!isOffsetOnly); + CORRADE_COMPARE(arraySize, 0); + CORRADE_COMPARE(morphTargetId, 15); CORRADE_COMPARE(name, MeshAttribute::Position); CORRADE_COMPARE(format, VertexFormat::Vector2); CORRADE_COMPARE(stride, sizeof(Vector2)); @@ -874,6 +976,21 @@ void MeshDataTest::constructAttribute2D() { MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, positionView}; CORRADE_VERIFY(!positions.isOffsetOnly()); CORRADE_COMPARE(positions.arraySize(), 0); + CORRADE_COMPARE(positions.morphTargetId(), -1); + CORRADE_COMPARE(positions.name(), MeshAttribute::Position); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); + CORRADE_COMPARE(positions.data().data(), positionView.data()); +} + +void MeshDataTest::constructAttribute2DMorphTarget() { + char positionData[4*sizeof(Vector2)]{}; + auto positionView = Containers::StridedArrayView2D{positionData, + {4, sizeof(Vector2)}}.every(2); + + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, positionView, 0, 33}; + CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); + CORRADE_COMPARE(positions.morphTargetId(), 33); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_COMPARE(positions.data().data(), positionView.data()); @@ -910,6 +1027,18 @@ void MeshDataTest::constructAttributeTypeErased() { MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, positionData}; CORRADE_VERIFY(!positions.isOffsetOnly()); CORRADE_COMPARE(positions.arraySize(), 0); + CORRADE_COMPARE(positions.morphTargetId(), -1); + CORRADE_COMPARE(positions.name(), MeshAttribute::Position); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector3); + CORRADE_VERIFY(positions.data().data() == positionData); +} + +void MeshDataTest::constructAttributeTypeErasedMorphTarget() { + const Vector3 positionData[3]{}; + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector3, positionData, 0, 101}; + CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); + CORRADE_COMPARE(positions.morphTargetId(), 101); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector3); CORRADE_VERIFY(positions.data().data() == positionData); @@ -919,6 +1048,17 @@ void MeshDataTest::constructAttributeNullptr() { MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr}; CORRADE_VERIFY(!positions.isOffsetOnly()); CORRADE_COMPARE(positions.arraySize(), 0); + CORRADE_COMPARE(positions.morphTargetId(), -1); + CORRADE_COMPARE(positions.name(), MeshAttribute::Position); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); + CORRADE_VERIFY(!positions.data().data()); +} + +void MeshDataTest::constructAttributeNullptrMorphTarget() { + MeshAttributeData positions{MeshAttribute::Position, VertexFormat::Vector2, nullptr, 0, 67}; + CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 0); + CORRADE_COMPARE(positions.morphTargetId(), 67); CORRADE_COMPARE(positions.name(), MeshAttribute::Position); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_VERIFY(!positions.data().data()); @@ -928,6 +1068,7 @@ void MeshDataTest::constructAttributePadding() { MeshAttributeData padding{-35}; CORRADE_VERIFY(!padding.isOffsetOnly()); CORRADE_COMPARE(padding.arraySize(), 0); + CORRADE_COMPARE(padding.morphTargetId(), -1); CORRADE_COMPARE(padding.name(), MeshAttribute{}); CORRADE_COMPARE(padding.format(), VertexFormat{}); CORRADE_COMPARE(padding.data().size(), 0); @@ -954,6 +1095,7 @@ void MeshDataTest::constructAttributeOffsetOnly() { MeshAttributeData a{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)}; CORRADE_VERIFY(a.isOffsetOnly()); CORRADE_COMPARE(a.arraySize(), 0); + CORRADE_COMPARE(a.morphTargetId(), -1); CORRADE_COMPARE(a.name(), MeshAttribute::TextureCoordinates); CORRADE_COMPARE(a.format(), VertexFormat::Vector2); CORRADE_COMPARE(a.offset(vertexData), sizeof(Vector2)); @@ -965,6 +1107,41 @@ void MeshDataTest::constructAttributeOffsetOnly() { constexpr MeshAttributeData ca{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2)}; CORRADE_VERIFY(ca.isOffsetOnly()); CORRADE_COMPARE(ca.arraySize(), 0); + CORRADE_COMPARE(ca.morphTargetId(), -1); + CORRADE_COMPARE(ca.name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(ca.format(), VertexFormat::Vector2); + CORRADE_COMPARE(ca.offset(vertexData), sizeof(Vector2)); + CORRADE_COMPARE(ca.stride(), 2*sizeof(Vector2)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), + Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), + TestSuite::Compare::Container); +} + +void MeshDataTest::constructAttributeOffsetOnlyMorphTarget() { + struct { + Vector2 position; + Vector2 textureCoordinates; + } vertexData[] { + {{}, {1.0f, 0.3f}}, + {{}, {0.5f, 0.7f}}, + }; + + MeshAttributeData a{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2), 0, 92}; + CORRADE_VERIFY(a.isOffsetOnly()); + CORRADE_COMPARE(a.arraySize(), 0); + CORRADE_COMPARE(a.morphTargetId(), 92); + CORRADE_COMPARE(a.name(), MeshAttribute::TextureCoordinates); + CORRADE_COMPARE(a.format(), VertexFormat::Vector2); + CORRADE_COMPARE(a.offset(vertexData), sizeof(Vector2)); + CORRADE_COMPARE(a.stride(), 2*sizeof(Vector2)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.data(vertexData)), + Containers::arrayView({{1.0f, 0.3f}, {0.5f, 0.7f}}), + TestSuite::Compare::Container); + + constexpr MeshAttributeData ca{MeshAttribute::TextureCoordinates, VertexFormat::Vector2, sizeof(Vector2), 2, 2*sizeof(Vector2), 0, 92}; + CORRADE_VERIFY(ca.isOffsetOnly()); + CORRADE_COMPARE(ca.arraySize(), 0); + CORRADE_COMPARE(ca.morphTargetId(), 92); CORRADE_COMPARE(ca.name(), MeshAttribute::TextureCoordinates); CORRADE_COMPARE(ca.format(), VertexFormat::Vector2); CORRADE_COMPARE(ca.offset(vertexData), sizeof(Vector2)); @@ -1047,6 +1224,54 @@ void MeshDataTest::constructAttributeWrongStride() { "Trade::MeshAttributeData: expected padding to fit into 16 bits but got -32769\n"); } +void MeshDataTest::constructAttributeWrongMorphTargetId() { + CORRADE_SKIP_IF_NO_ASSERT(); + + Vector2 positions[1]; + + /* These should be fine */ + MeshAttributeData{MeshAttribute::Position, Containers::arrayView(positions), -1}; + MeshAttributeData{MeshAttribute::Position, Containers::arrayView(positions), 127}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, 1, sizeof(Vector2), 0, -1}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, 1, sizeof(Vector2), 0, 127}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::Position, Containers::arrayView(positions), -56}; + MeshAttributeData{MeshAttribute::Position, Containers::arrayView(positions), 128}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, 1, sizeof(Vector2), 0, -56}; + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector2, 0, 1, sizeof(Vector2), 0, 128}; + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData: expected morph target ID to be either -1 or less than 128 but got -56\n" + "Trade::MeshAttributeData: expected morph target ID to be either -1 or less than 128 but got 128\n" + "Trade::MeshAttributeData: expected morph target ID to be either -1 or less than 128 but got -56\n" + "Trade::MeshAttributeData: expected morph target ID to be either -1 or less than 128 but got 128\n"); +} + +void MeshDataTest::constructAttributeMorphTargetNotAllowed() { + CORRADE_SKIP_IF_NO_ASSERT(); + + UnsignedInt ids[4]; + + /* -1 is allowed */ + MeshAttributeData{MeshAttribute::ObjectId, Containers::arrayView(ids), -1}; + MeshAttributeData{MeshAttribute::JointIds, Containers::stridedArrayView(ids).expanded<0>(Containers::Size2D{1, 4}), -1}; + MeshAttributeData{MeshAttribute::ObjectId, VertexFormat::UnsignedInt, 0, 4, sizeof(UnsignedInt), 0, -1}; + MeshAttributeData{MeshAttribute::JointIds, VertexFormat::UnsignedInt, 0, 1, sizeof(UnsignedInt), 4, -1}; + + std::ostringstream out; + Error redirectError{&out}; + MeshAttributeData{MeshAttribute::ObjectId, Containers::arrayView(ids), 37}; + MeshAttributeData{MeshAttribute::JointIds, Containers::stridedArrayView(ids).expanded<0>(Containers::Size2D{1, 4}), 37}; + MeshAttributeData{MeshAttribute::ObjectId, VertexFormat::UnsignedInt, 0, 4, sizeof(UnsignedInt), 0, 37}; + MeshAttributeData{MeshAttribute::JointIds, VertexFormat::UnsignedInt, 0, 1, sizeof(UnsignedInt), 4, 37}; + CORRADE_COMPARE(out.str(), + "Trade::MeshAttributeData: morph target not allowed for Trade::MeshAttribute::ObjectId\n" + "Trade::MeshAttributeData: morph target not allowed for Trade::MeshAttribute::JointIds\n" + "Trade::MeshAttributeData: morph target not allowed for Trade::MeshAttribute::ObjectId\n" + "Trade::MeshAttributeData: morph target not allowed for Trade::MeshAttribute::JointIds\n"); +} + void MeshDataTest::constructAttributeOnlyArrayAllowed() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -1097,6 +1322,7 @@ void MeshDataTest::constructArrayAttribute() { CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); CORRADE_COMPARE(data.format(), VertexFormat::Vector2); CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_COMPARE(data.morphTargetId(), -1); CORRADE_VERIFY(data.data().data() == vertexData); CORRADE_COMPARE(data.data().size(), 3); CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); @@ -1107,6 +1333,32 @@ void MeshDataTest::constructArrayAttribute() { CORRADE_COMPARE(cdata.name(), meshAttributeCustom(35)); CORRADE_COMPARE(cdata.format(), VertexFormat::Vector2); CORRADE_COMPARE(cdata.arraySize(), 4); + CORRADE_COMPARE(cdata.morphTargetId(), -1); + CORRADE_VERIFY(cdata.data().data() == ArrayVertexData); + CORRADE_COMPARE(cdata.data().size(), 3); + CORRADE_COMPARE(cdata.data().stride(), sizeof(Vector2)*4); +} + +void MeshDataTest::constructArrayAttributeMorphTarget() { + Vector2 vertexData[3*4]; + Containers::StridedArrayView2D attribute{vertexData, {3, 4}}; + MeshAttributeData data{meshAttributeCustom(35), attribute, 23}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_COMPARE(data.morphTargetId(), 23); + CORRADE_VERIFY(data.data().data() == vertexData); + CORRADE_COMPARE(data.data().size(), 3); + CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); + + constexpr Containers::StridedArrayView2D cattribute{ArrayVertexData, {3, 4}}; + constexpr MeshAttributeData cdata{meshAttributeCustom(35), cattribute, 23}; + CORRADE_VERIFY(!cdata.isOffsetOnly()); + CORRADE_COMPARE(cdata.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(cdata.format(), VertexFormat::Vector2); + CORRADE_COMPARE(cdata.arraySize(), 4); + CORRADE_COMPARE(cdata.morphTargetId(), 23); CORRADE_VERIFY(cdata.data().data() == ArrayVertexData); CORRADE_COMPARE(cdata.data().size(), 3); CORRADE_COMPARE(cdata.data().stride(), sizeof(Vector2)*4); @@ -1132,6 +1384,20 @@ void MeshDataTest::constructArrayAttribute2D() { CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); CORRADE_COMPARE(data.format(), VertexFormat::Vector2); CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_COMPARE(data.morphTargetId(), -1); + CORRADE_VERIFY(data.data().data() == vertexData); + CORRADE_COMPARE(data.data().size(), 3); + CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); +} + +void MeshDataTest::constructArrayAttribute2DMorphTarget() { + char vertexData[3*4*sizeof(Vector2)]; + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, Containers::StridedArrayView2D{vertexData, {3, 4*sizeof(Vector2)}}, 4, 77}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_COMPARE(data.morphTargetId(), 77); CORRADE_VERIFY(data.data().data() == vertexData); CORRADE_COMPARE(data.data().size(), 3); CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); @@ -1171,6 +1437,21 @@ void MeshDataTest::constructArrayAttributeTypeErased() { CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); CORRADE_COMPARE(data.format(), VertexFormat::Vector2); CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_COMPARE(data.morphTargetId(), -1); + CORRADE_VERIFY(data.data().data() == vertexData); + CORRADE_COMPARE(data.data().size(), 3); + CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); +} + +void MeshDataTest::constructArrayAttributeTypeErasedMorphTarget() { + Vector2 vertexData[3*4]; + Containers::StridedArrayView1D attribute{vertexData, 3, 4*sizeof(Vector2)}; + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, attribute, 4, 99}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_COMPARE(data.morphTargetId(), 99); CORRADE_VERIFY(data.data().data() == vertexData); CORRADE_COMPARE(data.data().size(), 3); CORRADE_COMPARE(data.data().stride(), sizeof(Vector2)*4); @@ -1180,6 +1461,17 @@ void MeshDataTest::constructArrayAttributeNullptr() { MeshAttributeData positions{meshAttributeCustom(35), VertexFormat::Vector2, nullptr, 4}; CORRADE_VERIFY(!positions.isOffsetOnly()); CORRADE_COMPARE(positions.arraySize(), 4); + CORRADE_COMPARE(positions.morphTargetId(), -1); + CORRADE_COMPARE(positions.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); + CORRADE_VERIFY(!positions.data().data()); +} + +void MeshDataTest::constructArrayAttributeNullptrMorphTarget() { + MeshAttributeData positions{meshAttributeCustom(35), VertexFormat::Vector2, nullptr, 4, 37}; + CORRADE_VERIFY(!positions.isOffsetOnly()); + CORRADE_COMPARE(positions.arraySize(), 4); + CORRADE_COMPARE(positions.morphTargetId(), 37); CORRADE_COMPARE(positions.name(), meshAttributeCustom(35)); CORRADE_COMPARE(positions.format(), VertexFormat::Vector2); CORRADE_VERIFY(!positions.data().data()); @@ -1191,6 +1483,7 @@ void MeshDataTest::constructArrayAttributeOffsetOnly() { CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); CORRADE_COMPARE(data.format(), VertexFormat::Vector2); CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_COMPARE(data.morphTargetId(), -1); Vector2 vertexData[1 + 3*4]; CORRADE_VERIFY(data.data(vertexData).data() == vertexData + 1); @@ -1202,6 +1495,28 @@ void MeshDataTest::constructArrayAttributeOffsetOnly() { CORRADE_COMPARE(cdata.name(), meshAttributeCustom(35)); CORRADE_COMPARE(cdata.format(), VertexFormat::Vector2); CORRADE_COMPARE(cdata.arraySize(), 4); + CORRADE_COMPARE(cdata.morphTargetId(), -1); +} + +void MeshDataTest::constructArrayAttributeOffsetOnlyMorphTarget() { + MeshAttributeData data{meshAttributeCustom(35), VertexFormat::Vector2, sizeof(Vector2), 3, sizeof(Vector2), 4, 44}; + CORRADE_VERIFY(data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(data.format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.arraySize(), 4); + CORRADE_COMPARE(data.morphTargetId(), 44); + + Vector2 vertexData[1 + 3*4]; + CORRADE_VERIFY(data.data(vertexData).data() == vertexData + 1); + CORRADE_COMPARE(data.data(vertexData).size(), 3); + CORRADE_COMPARE(data.data(vertexData).stride(), sizeof(Vector2)); + + constexpr MeshAttributeData cdata{meshAttributeCustom(35), VertexFormat::Vector2, sizeof(Vector2), 3, sizeof(Vector2), 4, 44}; + CORRADE_VERIFY(cdata.isOffsetOnly()); + CORRADE_COMPARE(cdata.name(), meshAttributeCustom(35)); + CORRADE_COMPARE(cdata.format(), VertexFormat::Vector2); + CORRADE_COMPARE(cdata.arraySize(), 4); + CORRADE_COMPARE(cdata.morphTargetId(), 44); } void MeshDataTest::constructArrayAttributeImplementationSpecificFormat() { @@ -1258,6 +1573,8 @@ void MeshDataTest::construct() { Vector3 normal; Vector2 textureCoordinate; Short id[2]; + Vector2 positionMorphTarget; + Byte idMorphTarget[3]; }; Containers::Array indexData{8*sizeof(UnsignedShort)}; @@ -1272,15 +1589,21 @@ void MeshDataTest::construct() { {{0.1f, 0.2f, 0.3f}, Vector3::xAxis(), {0.000f, 0.125f}, - {15, 74}}, + {15, 74}, + {0.3f, 0.1f}, + {74, 0, 15}}, {{0.4f, 0.5f, 0.6f}, Vector3::yAxis(), {0.250f, 0.375f}, - {-374, 2}}, + {-374, 2}, + {0.6f, 0.4f}, + {2, 0, -37}}, {{0.7f, 0.8f, 0.9f}, Vector3::zAxis(), {0.500f, 0.625f}, - {22, -1}} + {22, -1}, + {0.9f, 0.7f}, + {-1, 0, 22}} }, vertices); if(instanceData.vertexCount < 3) @@ -1304,7 +1627,16 @@ void MeshDataTest::construct() { vertices.slice(&Vertex::textureCoordinate)}, /* Custom & array */ MeshAttributeData{meshAttributeCustom(13), - Containers::arrayCast<2, Short>(vertices.slice(&Vertex::id))} + Containers::arrayCast<2, Short>(vertices.slice(&Vertex::id))}, + /* Positions as a morph target */ + MeshAttributeData{MeshAttribute::Position, + vertices.slice(&Vertex::positionMorphTarget), 37}, + /* Array as a morph target */ + MeshAttributeData{meshAttributeCustom(13), + Containers::arrayCast<2, Byte>(vertices.slice(&Vertex::idMorphTarget)), 37}, + /* Positions as a morph target again */ + MeshAttributeData{MeshAttribute::Position, + vertices.slice(&Vertex::positionMorphTarget), 37}, }, instanceData.vertexCount, &importerState}; @@ -1337,42 +1669,73 @@ void MeshDataTest::construct() { /* Attribute access by ID */ CORRADE_COMPARE(data.vertexCount(), instanceData.expectedVertexCount); - CORRADE_COMPARE(data.attributeCount(), 5); + CORRADE_COMPARE(data.attributeCount(), 8); + CORRADE_COMPARE(data.attributeCount(-1), 5); + CORRADE_COMPARE(data.attributeCount(1), 0); + CORRADE_COMPARE(data.attributeCount(37), 3); CORRADE_COMPARE(data.attributeName(0), MeshAttribute::Position); CORRADE_COMPARE(data.attributeName(1), MeshAttribute::TextureCoordinates); CORRADE_COMPARE(data.attributeName(2), MeshAttribute::Normal); CORRADE_COMPARE(data.attributeName(3), MeshAttribute::TextureCoordinates); CORRADE_COMPARE(data.attributeName(4), meshAttributeCustom(13)); + CORRADE_COMPARE(data.attributeName(5), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeName(6), meshAttributeCustom(13)); + CORRADE_COMPARE(data.attributeName(7), MeshAttribute::Position); CORRADE_COMPARE(data.attributeId(0), 0); CORRADE_COMPARE(data.attributeId(1), 0); CORRADE_COMPARE(data.attributeId(2), 0); CORRADE_COMPARE(data.attributeId(3), 1); CORRADE_COMPARE(data.attributeId(4), 0); + CORRADE_COMPARE(data.attributeId(5), 0); + CORRADE_COMPARE(data.attributeId(6), 0); + CORRADE_COMPARE(data.attributeId(7), 1); CORRADE_COMPARE(data.attributeFormat(0), VertexFormat::Vector3); CORRADE_COMPARE(data.attributeFormat(1), VertexFormat::Vector2); CORRADE_COMPARE(data.attributeFormat(2), VertexFormat::Vector3); CORRADE_COMPARE(data.attributeFormat(3), VertexFormat::Vector2); CORRADE_COMPARE(data.attributeFormat(4), VertexFormat::Short); + CORRADE_COMPARE(data.attributeFormat(5), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeFormat(6), VertexFormat::Byte); + CORRADE_COMPARE(data.attributeFormat(7), VertexFormat::Vector2); CORRADE_COMPARE(data.attributeOffset(0), 0); CORRADE_COMPARE(data.attributeOffset(1), 2*sizeof(Vector3)); CORRADE_COMPARE(data.attributeOffset(2), sizeof(Vector3)); CORRADE_COMPARE(data.attributeOffset(3), 2*sizeof(Vector3)); CORRADE_COMPARE(data.attributeOffset(4), 2*sizeof(Vector3) + sizeof(Vector2)); + CORRADE_COMPARE(data.attributeOffset(5), 2*sizeof(Vector3) + sizeof(Vector2) + 2*sizeof(Short)); + CORRADE_COMPARE(data.attributeOffset(6), 2*sizeof(Vector3) + sizeof(Vector2) + 2*sizeof(Short) + sizeof(Vector2)); + CORRADE_COMPARE(data.attributeOffset(7), 2*sizeof(Vector3) + sizeof(Vector2) + 2*sizeof(Short)); CORRADE_COMPARE(data.attributeStride(0), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(1), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(2), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(3), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(4), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(5), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(6), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(7), sizeof(Vertex)); CORRADE_COMPARE(data.attributeArraySize(0), 0); CORRADE_COMPARE(data.attributeArraySize(1), 0); CORRADE_COMPARE(data.attributeArraySize(2), 0); CORRADE_COMPARE(data.attributeArraySize(3), 0); CORRADE_COMPARE(data.attributeArraySize(4), 2); + CORRADE_COMPARE(data.attributeArraySize(5), 0); + CORRADE_COMPARE(data.attributeArraySize(6), 3); + CORRADE_COMPARE(data.attributeArraySize(7), 0); + CORRADE_COMPARE(data.attributeMorphTargetId(0), -1); + CORRADE_COMPARE(data.attributeMorphTargetId(1), -1); + CORRADE_COMPARE(data.attributeMorphTargetId(2), -1); + CORRADE_COMPARE(data.attributeMorphTargetId(3), -1); + CORRADE_COMPARE(data.attributeMorphTargetId(4), -1); + CORRADE_COMPARE(data.attributeMorphTargetId(5), 37); + CORRADE_COMPARE(data.attributeMorphTargetId(6), 37); + CORRADE_COMPARE(data.attributeMorphTargetId(7), 37); /* Raw attribute data access by ID */ CORRADE_COMPARE(data.attributeData(1).name(), MeshAttribute::TextureCoordinates); CORRADE_COMPARE(data.attributeData(1).format(), VertexFormat::Vector2); CORRADE_COMPARE(data.attributeData(1).data().size(), instanceData.expectedVertexCount); CORRADE_COMPARE(data.attributeData(1).arraySize(), 0); + CORRADE_COMPARE(data.attributeData(1).morphTargetId(), -1); if(instanceData.vertexCount) CORRADE_COMPARE(Containers::arrayCast(data.attributeData(1).data())[1], (Vector2{0.250f, 0.375f})); /* Offset-only */ @@ -1380,6 +1743,7 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attributeData(2).format(), VertexFormat::Vector3); CORRADE_COMPARE(data.attributeData(2).data().size(), instanceData.expectedVertexCount); CORRADE_COMPARE(data.attributeData(2).arraySize(), 0); + CORRADE_COMPARE(data.attributeData(2).morphTargetId(), -1); if(instanceData.vertexCount) CORRADE_COMPARE(Containers::arrayCast(data.attributeData(2).data())[1], Vector3::yAxis()); /* Array */ @@ -1387,10 +1751,29 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attributeData(4).format(), VertexFormat::Short); CORRADE_COMPARE(data.attributeData(4).data().size(), instanceData.expectedVertexCount); CORRADE_COMPARE(data.attributeData(4).arraySize(), 2); + CORRADE_COMPARE(data.attributeData(4).morphTargetId(), -1); if(instanceData.vertexCount) { CORRADE_COMPARE((Containers::arrayCast<2, const Short>(data.attributeData(4).data(), 2))[1][0], -374); CORRADE_COMPARE((Containers::arrayCast<2, const Short>(data.attributeData(4).data(), 2))[1][1], 2); } + /* Morph target. No special treatment in this case. */ + CORRADE_COMPARE(data.attributeData(5).name(), MeshAttribute::Position); + CORRADE_COMPARE(data.attributeData(5).format(), VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeData(5).data().size(), instanceData.expectedVertexCount); + CORRADE_COMPARE(data.attributeData(5).arraySize(), 0); + CORRADE_COMPARE(data.attributeData(5).morphTargetId(), 37); + if(instanceData.vertexCount) + CORRADE_COMPARE(Containers::arrayCast(data.attributeData(5).data())[1], (Vector2{0.6f, 0.4f})); + /* Morph target array. No special treatment in this case. */ + CORRADE_COMPARE(data.attributeData(6).name(), meshAttributeCustom(13)); + CORRADE_COMPARE(data.attributeData(6).format(), VertexFormat::Byte); + CORRADE_COMPARE(data.attributeData(6).data().size(), instanceData.expectedVertexCount); + CORRADE_COMPARE(data.attributeData(6).arraySize(), 3); + CORRADE_COMPARE(data.attributeData(6).morphTargetId(), 37); + if(instanceData.vertexCount) { + CORRADE_COMPARE((Containers::arrayCast<2, const Byte>(data.attributeData(6).data(), 3))[1][0], 2); + CORRADE_COMPARE((Containers::arrayCast<2, const Byte>(data.attributeData(6).data(), 3))[1][2], -37); + } /* Typeless access by ID with a cast later */ CORRADE_COMPARE(data.attribute(0).size()[0], instanceData.expectedVertexCount); @@ -1409,6 +1792,15 @@ void MeshDataTest::construct() { data.attribute(4))[0])[0], 15); CORRADE_COMPARE((Containers::arrayCast<2, const Short>( data.attribute(4))[0])[1], 74); + /* Morph target. No special treatment in case of by ID access. */ + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(5))[2]), (Vector2{0.9f, 0.7f})); + /* Morph target array. No special treatment in case of by ID access. */ + CORRADE_COMPARE((Containers::arrayCast<2, const Byte>( + data.attribute(6))[0])[0], 74); + CORRADE_COMPARE((Containers::arrayCast<2, const Byte>( + data.attribute(6))[0])[2], 15); + CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( data.mutableAttribute(0))[1]), (Vector3{0.4f, 0.5f, 0.6f})); CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( @@ -1422,6 +1814,14 @@ void MeshDataTest::construct() { data.mutableAttribute(4))[0])[0], 15); CORRADE_COMPARE((Containers::arrayCast<2, Short>( data.mutableAttribute(4))[0])[1], 74); + /* Morph target. No special treatment in case of by ID access. */ + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>( + data.mutableAttribute(5))[2]), (Vector2{0.9f, 0.7f})); + /* Morph target array. No special treatment in case of by ID access. */ + CORRADE_COMPARE((Containers::arrayCast<2, Byte>( + data.mutableAttribute(6))[0])[0], 74); + CORRADE_COMPARE((Containers::arrayCast<2, Byte>( + data.mutableAttribute(6))[0])[2], 15); } /* Typed access by ID */ @@ -1435,6 +1835,12 @@ void MeshDataTest::construct() { /* Array */ CORRADE_COMPARE(data.attribute(4)[1][0], -374); CORRADE_COMPARE(data.attribute(4)[1][1], 2); + /* Morph target. No special treatment in case of by ID access. */ + CORRADE_COMPARE(data.attribute(5)[2], (Vector2{0.9f, 0.7f})); + /* Morph target array. No special treatment in case of by ID access. */ + CORRADE_COMPARE(data.attribute(6)[1][0], 2); + CORRADE_COMPARE(data.attribute(6)[1][2], -37); + CORRADE_COMPARE(data.mutableAttribute(0)[1], (Vector3{0.4f, 0.5f, 0.6f})); CORRADE_COMPARE(data.mutableAttribute(1)[0], (Vector2{0.000f, 0.125f})); CORRADE_COMPARE(data.mutableAttribute(2)[2], Vector3::zAxis()); @@ -1442,6 +1848,11 @@ void MeshDataTest::construct() { /* Array */ CORRADE_COMPARE(data.mutableAttribute(4)[1][0], -374); CORRADE_COMPARE(data.mutableAttribute(4)[1][1], 2); + /* Morph target. No special treatment in this case. */ + CORRADE_COMPARE(data.mutableAttribute(5)[2], (Vector2{0.9f, 0.7f})); + /* Morph target array. No special treatment in case of by ID access. */ + CORRADE_COMPARE(data.mutableAttribute(6)[1][0], 2); + CORRADE_COMPARE(data.mutableAttribute(6)[1][2], -37); } /* Accessing a non-array attribute as an array should be possible as well @@ -1458,14 +1869,20 @@ void MeshDataTest::construct() { CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Normal)); CORRADE_VERIFY(data.hasAttribute(MeshAttribute::TextureCoordinates)); CORRADE_VERIFY(data.hasAttribute(meshAttributeCustom(13))); + CORRADE_VERIFY(data.hasAttribute(MeshAttribute::Position, 37)); + CORRADE_VERIFY(data.hasAttribute(meshAttributeCustom(13), 37)); CORRADE_VERIFY(!data.hasAttribute(MeshAttribute::Color)); CORRADE_VERIFY(!data.hasAttribute(meshAttributeCustom(23))); + CORRADE_VERIFY(!data.hasAttribute(MeshAttribute::TextureCoordinates, 37)); CORRADE_COMPARE(data.attributeCount(MeshAttribute::Position), 1); CORRADE_COMPARE(data.attributeCount(MeshAttribute::Normal), 1); CORRADE_COMPARE(data.attributeCount(MeshAttribute::TextureCoordinates), 2); CORRADE_COMPARE(data.attributeCount(meshAttributeCustom(13)), 1); + CORRADE_COMPARE(data.attributeCount(MeshAttribute::Position, 37), 2); + CORRADE_COMPARE(data.attributeCount(meshAttributeCustom(13), 37), 1); CORRADE_COMPARE(data.attributeCount(MeshAttribute::Color), 0); CORRADE_COMPARE(data.attributeCount(meshAttributeCustom(23)), 0); + CORRADE_COMPARE(data.attributeCount(MeshAttribute::TextureCoordinates, 37), 0); CORRADE_COMPARE(data.findAttributeId(MeshAttribute::Position), 0); CORRADE_COMPARE(data.attributeId(MeshAttribute::Position), 0); CORRADE_COMPARE(data.findAttributeId(MeshAttribute::Normal), 2); @@ -1476,8 +1893,16 @@ void MeshDataTest::construct() { CORRADE_COMPARE(data.attributeId(MeshAttribute::TextureCoordinates, 1), 3); CORRADE_COMPARE(data.findAttributeId(meshAttributeCustom(13)), 4); CORRADE_COMPARE(data.attributeId(meshAttributeCustom(13)), 4); + CORRADE_COMPARE(data.findAttributeId(MeshAttribute::Position, 0, 37), 5); + CORRADE_COMPARE(data.attributeId(MeshAttribute::Position, 0, 37), 5); + CORRADE_COMPARE(data.findAttributeId(meshAttributeCustom(13), 0, 37), 6); + CORRADE_COMPARE(data.attributeId(meshAttributeCustom(13), 0, 37), 6); + CORRADE_COMPARE(data.findAttributeId(MeshAttribute::Position, 1, 37), 7); + CORRADE_COMPARE(data.attributeId(MeshAttribute::Position, 1, 37), 7); CORRADE_COMPARE(data.findAttributeId(MeshAttribute::Color), Containers::NullOpt); CORRADE_COMPARE(data.findAttributeId(MeshAttribute::TextureCoordinates, 2), Containers::NullOpt); + CORRADE_COMPARE(data.findAttributeId(MeshAttribute::Position, 2, 37), Containers::NullOpt); + CORRADE_COMPARE(data.findAttributeId(MeshAttribute::TextureCoordinates, 0, 37), Containers::NullOpt); CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position), VertexFormat::Vector3); CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Normal), @@ -1488,20 +1913,34 @@ void MeshDataTest::construct() { VertexFormat::Vector2); CORRADE_COMPARE(data.attributeFormat(meshAttributeCustom(13)), VertexFormat::Short); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position, 0, 37), + VertexFormat::Vector2); + CORRADE_COMPARE(data.attributeFormat(meshAttributeCustom(13), 0, 37), + VertexFormat::Byte); + CORRADE_COMPARE(data.attributeFormat(MeshAttribute::Position, 1, 37), + VertexFormat::Vector2); CORRADE_COMPARE(data.attributeOffset(MeshAttribute::Position), 0); CORRADE_COMPARE(data.attributeOffset(MeshAttribute::Normal), sizeof(Vector3)); CORRADE_COMPARE(data.attributeOffset(MeshAttribute::TextureCoordinates, 0), 2*sizeof(Vector3)); CORRADE_COMPARE(data.attributeOffset(MeshAttribute::TextureCoordinates, 1), 2*sizeof(Vector3)); CORRADE_COMPARE(data.attributeOffset(meshAttributeCustom(13)), 2*sizeof(Vector3) + sizeof(Vector2)); + CORRADE_COMPARE(data.attributeOffset(MeshAttribute::Position, 0, 37), 2*sizeof(Vector3) + sizeof(Vector2) + 2*sizeof(Short)); CORRADE_COMPARE(data.attributeOffset(meshAttributeCustom(13), 0, 37), 2*sizeof(Vector3) + sizeof(Vector2) + 2*sizeof(Short) + sizeof(Vector2)); + CORRADE_COMPARE(data.attributeOffset(MeshAttribute::Position, 1, 37), 2*sizeof(Vector3) + sizeof(Vector2) + 2*sizeof(Short)); CORRADE_COMPARE(data.attributeStride(MeshAttribute::Position), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(MeshAttribute::Normal), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 0), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(MeshAttribute::TextureCoordinates, 1), sizeof(Vertex)); CORRADE_COMPARE(data.attributeStride(meshAttributeCustom(13)), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(MeshAttribute::Position, 0, 37), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(meshAttributeCustom(13), 0, 37), sizeof(Vertex)); + CORRADE_COMPARE(data.attributeStride(MeshAttribute::Position, 1, 37), sizeof(Vertex)); CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::Position), 0); CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::Normal), 0); CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::TextureCoordinates, 0), 0); CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::TextureCoordinates, 1), 0); CORRADE_COMPARE(data.attributeArraySize(meshAttributeCustom(13)), 2); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::Position, 0, 37), 0); + CORRADE_COMPARE(data.attributeArraySize(meshAttributeCustom(13), 0, 37), 3); + CORRADE_COMPARE(data.attributeArraySize(MeshAttribute::Position, 1, 37), 0); /* Typeless access by name with a cast later */ CORRADE_COMPARE(data.attribute(MeshAttribute::Position).size()[0], instanceData.expectedVertexCount); @@ -1520,6 +1959,17 @@ void MeshDataTest::construct() { data.attribute(meshAttributeCustom(13)))[1])[0], -374); CORRADE_COMPARE((Containers::arrayCast<2, const Short>( data.attribute(meshAttributeCustom(13)))[1])[1], 2); + /* Morph target */ + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(MeshAttribute::Position, 0, 37))[1]), (Vector2{0.6f, 0.4f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.attribute(MeshAttribute::Position, 1, 37))[2]), (Vector2{0.9f, 0.7f})); + /* Array morph target */ + CORRADE_COMPARE((Containers::arrayCast<2, const Byte>( + data.attribute(meshAttributeCustom(13), 0, 37))[1])[0], 2); + CORRADE_COMPARE((Containers::arrayCast<2, const Byte>( + data.attribute(meshAttributeCustom(13), 0, 37))[1])[2], -37); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector3>( data.mutableAttribute(MeshAttribute::Position))[1]), (Vector3{0.4f, 0.5f, 0.6f})); CORRADE_COMPARE((Containers::arrayCast<1, Vector3>( @@ -1533,6 +1983,16 @@ void MeshDataTest::construct() { data.mutableAttribute(meshAttributeCustom(13)))[1])[0], -374); CORRADE_COMPARE((Containers::arrayCast<2, Short>( data.mutableAttribute(meshAttributeCustom(13)))[1])[1], 2); + /* Morph target */ + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.mutableAttribute(MeshAttribute::Position, 0, 37))[1]), (Vector2{0.6f, 0.4f})); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>( + data.mutableAttribute(MeshAttribute::Position, 1, 37))[2]), (Vector2{0.9f, 0.7f})); + /* Array morph target */ + CORRADE_COMPARE((Containers::arrayCast<2, const Byte>( + data.mutableAttribute(meshAttributeCustom(13), 0, 37))[1])[0], 2); + CORRADE_COMPARE((Containers::arrayCast<2, const Byte>( + data.mutableAttribute(meshAttributeCustom(13), 0, 37))[1])[2], -37); } /* Typed access by name */ @@ -1546,6 +2006,13 @@ void MeshDataTest::construct() { /* Array */ CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2][0], 22); CORRADE_COMPARE(data.attribute(meshAttributeCustom(13))[2][1], -1); + /* Morph target */ + CORRADE_COMPARE(data.attribute(MeshAttribute::Position, 0, 37)[0], (Vector2{0.3f, 0.1f})); + CORRADE_COMPARE(data.attribute(MeshAttribute::Position, 1, 37)[2], (Vector2{0.9f, 0.7f})); + /* Morph target array */ + CORRADE_COMPARE(data.attribute(meshAttributeCustom(13), 0, 37)[2][0], -1); + CORRADE_COMPARE(data.attribute(meshAttributeCustom(13), 0, 37)[2][2], 22); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position)[1], (Vector3{0.4f, 0.5f, 0.6f})); CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Normal)[2], Vector3::zAxis()); CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::TextureCoordinates, 0)[0], (Vector2{0.000f, 0.125f})); @@ -1553,6 +2020,12 @@ void MeshDataTest::construct() { /* Array */ CORRADE_COMPARE(data.mutableAttribute(meshAttributeCustom(13))[2][0], 22); CORRADE_COMPARE(data.mutableAttribute(meshAttributeCustom(13))[2][1], -1); + /* Morph target */ + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position, 0, 37)[0], (Vector2{0.3f, 0.1f})); + CORRADE_COMPARE(data.mutableAttribute(MeshAttribute::Position, 1, 37)[2], (Vector2{0.9f, 0.7f})); + /* Morph target array */ + CORRADE_COMPARE(data.mutableAttribute(meshAttributeCustom(13), 0, 37)[2][0], -1); + CORRADE_COMPARE(data.mutableAttribute(meshAttributeCustom(13), 0, 37)[2][2], 22); } /* Accessing a non-array attribute as an array should be possible as well @@ -2839,6 +3312,8 @@ void MeshDataTest::indicesIntoArrayInvalidSize() { } template void MeshDataTest::positions2DAsArray() { + auto&& instanceData = AsArrayData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); setTestCaseTemplateName(NameTraits::name()); /* Testing also that it picks the correct attribute */ @@ -2860,9 +3335,9 @@ template void MeshDataTest::positions2DAsArray() { MeshAttributeData{MeshAttribute::ObjectId, view.slice(&Vertex::objectId)}, MeshAttributeData{MeshAttribute::Position, - view.slice(&Vertex::position)} + view.slice(&Vertex::position), instanceData.morphTargetId} }}; - CORRADE_COMPARE_AS(data.positions2DAsArray(1), Containers::arrayView({ + CORRADE_COMPARE_AS(data.positions2DAsArray(instanceData.id, instanceData.morphTargetId), Containers::arrayView({ {2.0f, 1.0f}, {0.0f, -1.0f}, {-2.0f, 3.0f} }), TestSuite::Compare::Container); } @@ -2961,6 +3436,8 @@ void MeshDataTest::positions2DIntoArrayInvalidSize() { } template void MeshDataTest::positions3DAsArray() { + auto&& instanceData = AsArrayData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); setTestCaseTemplateName(NameTraits::name()); /* Testing also that it picks the correct attribute. Needs to be @@ -2984,9 +3461,9 @@ template void MeshDataTest::positions3DAsArray() { MeshAttributeData{MeshAttribute::ObjectId, view.slice(&Vertex::objectId)}, MeshAttributeData{MeshAttribute::Position, - view.slice(&Vertex::position)} + view.slice(&Vertex::position), instanceData.morphTargetId} }}; - CORRADE_COMPARE_AS(data.positions3DAsArray(1), Containers::arrayView({ + CORRADE_COMPARE_AS(data.positions3DAsArray(instanceData.id, instanceData.morphTargetId), Containers::arrayView({ Vector3::pad(Math::Vector::pad(Vector3{2.0f, 1.0f, 0.75f})), Vector3::pad(Math::Vector::pad(Vector3{0.0f, -1.0f, 1.25f})), Vector3::pad(Math::Vector::pad(Vector3{-2.0f, 3.0f, 2.5f})) @@ -3093,6 +3570,8 @@ void MeshDataTest::positions3DIntoArrayInvalidSize() { } template void MeshDataTest::tangentsAsArray() { + auto&& instanceData = AsArrayData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); setTestCaseTemplateName(NameTraits::name()); /* Testing also that it picks the correct attribute. Needs to be @@ -3116,9 +3595,9 @@ template void MeshDataTest::tangentsAsArray() { MeshAttributeData{MeshAttribute::ObjectId, view.slice(&Vertex::objectId)}, MeshAttributeData{MeshAttribute::Tangent, - view.slice(&Vertex::tangent)} + view.slice(&Vertex::tangent), instanceData.morphTargetId} }}; - CORRADE_COMPARE_AS(data.tangentsAsArray(1), Containers::arrayView({ + CORRADE_COMPARE_AS(data.tangentsAsArray(instanceData.id, instanceData.morphTargetId), Containers::arrayView({ {2.0f, 1.0f, 0.75f}, {0.0f, -1.0f, 1.25f}, {-2.0f, 3.0f, 2.5f}, }), TestSuite::Compare::Container); } @@ -3160,6 +3639,8 @@ void MeshDataTest::tangentsIntoArrayInvalidSize() { } template void MeshDataTest::bitangentSignsAsArray() { + auto&& instanceData = AsArrayData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); setTestCaseTemplateName(Math::TypeTraits::name()); /* Testing also that it picks the correct attribute. Needs to be @@ -3182,9 +3663,9 @@ template void MeshDataTest::bitangentSignsAsArray() { MeshAttributeData{MeshAttribute::ObjectId, view.slice(&Vertex::objectId)}, MeshAttributeData{MeshAttribute::Tangent, - view.slice(&Vertex::tangent)} + view.slice(&Vertex::tangent), instanceData.morphTargetId} }}; - CORRADE_COMPARE_AS(data.bitangentSignsAsArray(1), Containers::arrayView({ + CORRADE_COMPARE_AS(data.bitangentSignsAsArray(instanceData.id, instanceData.morphTargetId), Containers::arrayView({ -1.0f, 1.0f, -1.0f }), TestSuite::Compare::Container); } @@ -3242,6 +3723,8 @@ void MeshDataTest::bitangentSignsIntoArrayInvalidSize() { } template void MeshDataTest::bitangentsAsArray() { + auto&& instanceData = AsArrayData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); setTestCaseTemplateName(NameTraits::name()); /* Testing also that it picks the correct attribute. Needs to be @@ -3265,9 +3748,9 @@ template void MeshDataTest::bitangentsAsArray() { MeshAttributeData{MeshAttribute::ObjectId, view.slice(&Vertex::objectId)}, MeshAttributeData{MeshAttribute::Bitangent, - view.slice(&Vertex::bitangent)} + view.slice(&Vertex::bitangent), instanceData.morphTargetId} }}; - CORRADE_COMPARE_AS(data.bitangentsAsArray(1), Containers::arrayView({ + CORRADE_COMPARE_AS(data.bitangentsAsArray(instanceData.id, instanceData.morphTargetId), Containers::arrayView({ {2.0f, 1.0f, 0.75f}, {0.0f, -1.0f, 1.25f}, {-2.0f, 3.0f, 2.5f}, }), TestSuite::Compare::Container); } @@ -3309,6 +3792,8 @@ void MeshDataTest::bitangentsIntoArrayInvalidSize() { } template void MeshDataTest::normalsAsArray() { + auto&& instanceData = AsArrayData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); setTestCaseTemplateName(NameTraits::name()); /* Testing also that it picks the correct attribute. Needs to be @@ -3332,9 +3817,9 @@ template void MeshDataTest::normalsAsArray() { MeshAttributeData{MeshAttribute::ObjectId, view.slice(&Vertex::objectId)}, MeshAttributeData{MeshAttribute::Normal, - view.slice(&Vertex::normal)} + view.slice(&Vertex::normal), instanceData.morphTargetId} }}; - CORRADE_COMPARE_AS(data.normalsAsArray(1), Containers::arrayView({ + CORRADE_COMPARE_AS(data.normalsAsArray(instanceData.id, instanceData.morphTargetId), Containers::arrayView({ {2.0f, 1.0f, 0.75f}, {0.0f, -1.0f, 1.25f}, {-2.0f, 3.0f, 2.5f}, }), TestSuite::Compare::Container); } @@ -3375,6 +3860,8 @@ void MeshDataTest::normalsIntoArrayInvalidSize() { } template void MeshDataTest::textureCoordinates2DAsArray() { + auto&& instanceData = AsArrayData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); setTestCaseTemplateName(NameTraits::name()); /* Testing also that it picks the correct attribute. Needs to be @@ -3398,9 +3885,9 @@ template void MeshDataTest::textureCoordinates2DAsArray() { MeshAttributeData{MeshAttribute::ObjectId, view.slice(&Vertex::objectId)}, MeshAttributeData{MeshAttribute::TextureCoordinates, - view.slice(&Vertex::textureCoordinate)} + view.slice(&Vertex::textureCoordinate), instanceData.morphTargetId} }}; - CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(1), Containers::arrayView({ + CORRADE_COMPARE_AS(data.textureCoordinates2DAsArray(instanceData.id, instanceData.morphTargetId), Containers::arrayView({ {2.0f, 1.0f}, {0.0f, -1.0f}, {-2.0f, 3.0f}, }), TestSuite::Compare::Container); } @@ -3496,6 +3983,8 @@ void MeshDataTest::textureCoordinates2DIntoArrayInvalidSize() { } template void MeshDataTest::colorsAsArray() { + auto&& instanceData = AsArrayData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); setTestCaseTemplateName(NameTraits::name()); /* Testing also that it picks the correct attribute. Can't use e.g. @@ -3518,9 +4007,9 @@ template void MeshDataTest::colorsAsArray() { MeshAttributeData{MeshAttribute::ObjectId, view.slice(&Vertex::objectId)}, MeshAttributeData{MeshAttribute::Color, - view.slice(&Vertex::color)} + view.slice(&Vertex::color), instanceData.morphTargetId} }}; - CORRADE_COMPARE_AS(data.colorsAsArray(1), Containers::arrayView({ + CORRADE_COMPARE_AS(data.colorsAsArray(instanceData.id, instanceData.morphTargetId), Containers::arrayView({ {2.0f, 1.0f, 0.75f}, {0.0f, -1.0f, 1.25f}, {-2.0f, 3.0f, 2.5f}, }), TestSuite::Compare::Container); } @@ -3974,114 +4463,162 @@ void MeshDataTest::attributeNotFound() { MeshAttributeData{MeshAttribute::JointIds, VertexFormat::UnsignedByte, nullptr, 3}, MeshAttributeData{MeshAttribute::Color, VertexFormat::Vector4, nullptr}, MeshAttributeData{MeshAttribute::Weights, VertexFormat::Float, nullptr, 6}, - MeshAttributeData{MeshAttribute::JointIds, VertexFormat::UnsignedShort, nullptr, 6} + MeshAttributeData{MeshAttribute::JointIds, VertexFormat::UnsignedShort, nullptr, 6}, + /* Morph targets */ + MeshAttributeData{MeshAttribute::Color, VertexFormat::Vector3ubNormalized, nullptr, 0, 37}, + MeshAttributeData{MeshAttribute::Color, VertexFormat::Vector4usNormalized, nullptr, 0, 37}, + MeshAttributeData{MeshAttribute::Color, VertexFormat::Vector4usNormalized, nullptr, 0, 37}, }}; /* This is fine */ CORRADE_COMPARE(data.attributeCount(MeshAttribute::Position), 0); + CORRADE_COMPARE(data.attributeCount(MeshAttribute::Position, 37), 0); CORRADE_COMPARE(data.findAttributeId(MeshAttribute::Position), Containers::NullOpt); CORRADE_COMPARE(data.findAttributeId(MeshAttribute::Color, 2), Containers::NullOpt); + CORRADE_COMPARE(data.findAttributeId(MeshAttribute::Color, 3, 37), Containers::NullOpt); std::ostringstream out; Error redirectError{&out}; - data.attributeData(6); - data.attributeName(6); - data.attributeId(6); - data.attributeFormat(6); - data.attributeOffset(6); - data.attributeStride(6); - data.attributeArraySize(6); - data.attribute(6); - data.attribute(6); - data.attribute(6); - data.mutableAttribute(6); - data.mutableAttribute(6); - data.mutableAttribute(6); + data.attributeData(9); + data.attributeName(9); + data.attributeId(9); + data.attributeFormat(9); + data.attributeOffset(9); + data.attributeStride(9); + data.attributeArraySize(9); + data.attributeMorphTargetId(9); + data.attribute(9); + data.attribute(9); + data.attribute(9); + data.mutableAttribute(9); + data.mutableAttribute(9); + data.mutableAttribute(9); data.attributeId(MeshAttribute::Position); data.attributeId(MeshAttribute::Color, 2); + data.attributeId(MeshAttribute::Color, 3, 37); data.attributeFormat(MeshAttribute::Position); data.attributeFormat(MeshAttribute::Color, 2); + data.attributeFormat(MeshAttribute::Color, 3, 37); data.attributeOffset(MeshAttribute::Position); data.attributeOffset(MeshAttribute::Color, 2); + data.attributeOffset(MeshAttribute::Color, 3, 37); data.attributeStride(MeshAttribute::Position); data.attributeStride(MeshAttribute::Color, 2); + data.attributeStride(MeshAttribute::Color, 3, 37); data.attributeArraySize(MeshAttribute::Position); data.attributeArraySize(MeshAttribute::Color, 2); + data.attributeArraySize(MeshAttribute::Color, 3, 37); data.attribute(MeshAttribute::Position); data.attribute(MeshAttribute::Color, 2); + data.attribute(MeshAttribute::Color, 3, 37); data.attribute(MeshAttribute::Position); data.attribute(MeshAttribute::Color, 2); + data.attribute(MeshAttribute::Color, 3, 37); data.attribute(MeshAttribute::Position); data.attribute(MeshAttribute::Color, 2); + data.attribute(MeshAttribute::Color, 3, 37); data.mutableAttribute(MeshAttribute::Position); data.mutableAttribute(MeshAttribute::Color, 2); + data.mutableAttribute(MeshAttribute::Color, 3, 37); data.mutableAttribute(MeshAttribute::Position); data.mutableAttribute(MeshAttribute::Color, 2); + data.mutableAttribute(MeshAttribute::Color, 3, 37); data.mutableAttribute(MeshAttribute::Position); data.mutableAttribute(MeshAttribute::Color, 2); + data.mutableAttribute(MeshAttribute::Color, 3, 37); data.positions2DAsArray(); + data.positions2DAsArray(0, 37); data.positions3DAsArray(); + data.positions3DAsArray(0, 37); data.tangentsAsArray(); + data.tangentsAsArray(0, 37); data.bitangentSignsAsArray(); + data.bitangentSignsAsArray(0, 37); data.bitangentsAsArray(); + data.bitangentsAsArray(0, 37); data.normalsAsArray(); + data.normalsAsArray(0, 37); data.textureCoordinates2DAsArray(); + data.textureCoordinates2DAsArray(0, 37); data.colorsAsArray(2); + data.colorsAsArray(3, 37); /* jointIdsAsArray() and weightsAsArray() have their own assert in order to - fetch array size, have to test also Into() for these */ + fetch array size, have to test also Into() for these. They have no morph + targets however, so that's omitted for them. */ data.jointIdsAsArray(2); data.jointIdsInto(nullptr, 2); data.weightsAsArray(2); data.weightsInto(nullptr, 2); + /* Object IDs have no morph targets either */ data.objectIdsAsArray(); CORRADE_COMPARE_AS(out.str(), - "Trade::MeshData::attributeData(): index 6 out of range for 6 attributes\n" - "Trade::MeshData::attributeName(): index 6 out of range for 6 attributes\n" - "Trade::MeshData::attributeId(): index 6 out of range for 6 attributes\n" - "Trade::MeshData::attributeFormat(): index 6 out of range for 6 attributes\n" - "Trade::MeshData::attributeOffset(): index 6 out of range for 6 attributes\n" - "Trade::MeshData::attributeStride(): index 6 out of range for 6 attributes\n" - "Trade::MeshData::attributeArraySize(): index 6 out of range for 6 attributes\n" - "Trade::MeshData::attribute(): index 6 out of range for 6 attributes\n" - "Trade::MeshData::attribute(): index 6 out of range for 6 attributes\n" - "Trade::MeshData::attribute(): index 6 out of range for 6 attributes\n" - "Trade::MeshData::mutableAttribute(): index 6 out of range for 6 attributes\n" - "Trade::MeshData::mutableAttribute(): index 6 out of range for 6 attributes\n" - "Trade::MeshData::mutableAttribute(): index 6 out of range for 6 attributes\n" + "Trade::MeshData::attributeData(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::attributeName(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::attributeId(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::attributeFormat(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::attributeOffset(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::attributeStride(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::attributeArraySize(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::attributeMorphTargetId(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::attribute(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::attribute(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::attribute(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::mutableAttribute(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::mutableAttribute(): index 9 out of range for 9 attributes\n" + "Trade::MeshData::mutableAttribute(): index 9 out of range for 9 attributes\n" "Trade::MeshData::attributeId(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attributeId(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeId(): index 3 out of range for 3 Trade::MeshAttribute::Color attributes in morph target 37\n" "Trade::MeshData::attributeFormat(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attributeFormat(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeFormat(): index 3 out of range for 3 Trade::MeshAttribute::Color attributes in morph target 37\n" "Trade::MeshData::attributeOffset(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attributeOffset(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeOffset(): index 3 out of range for 3 Trade::MeshAttribute::Color attributes in morph target 37\n" "Trade::MeshData::attributeStride(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attributeStride(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeStride(): index 3 out of range for 3 Trade::MeshAttribute::Color attributes in morph target 37\n" "Trade::MeshData::attributeArraySize(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attributeArraySize(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attributeArraySize(): index 3 out of range for 3 Trade::MeshAttribute::Color attributes in morph target 37\n" "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attribute(): index 3 out of range for 3 Trade::MeshAttribute::Color attributes in morph target 37\n" "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attribute(): index 3 out of range for 3 Trade::MeshAttribute::Color attributes in morph target 37\n" "Trade::MeshData::attribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::attribute(): index 3 out of range for 3 Trade::MeshAttribute::Color attributes in morph target 37\n" "Trade::MeshData::mutableAttribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::mutableAttribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::mutableAttribute(): index 3 out of range for 3 Trade::MeshAttribute::Color attributes in morph target 37\n" "Trade::MeshData::mutableAttribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::mutableAttribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::mutableAttribute(): index 3 out of range for 3 Trade::MeshAttribute::Color attributes in morph target 37\n" "Trade::MeshData::mutableAttribute(): index 0 out of range for 0 Trade::MeshAttribute::Position attributes\n" "Trade::MeshData::mutableAttribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" + "Trade::MeshData::mutableAttribute(): index 3 out of range for 3 Trade::MeshAttribute::Color attributes in morph target 37\n" "Trade::MeshData::positions2DInto(): index 0 out of range for 0 position attributes\n" + "Trade::MeshData::positions2DInto(): index 0 out of range for 0 position attributes in morph target 37\n" "Trade::MeshData::positions3DInto(): index 0 out of range for 0 position attributes\n" + "Trade::MeshData::positions3DInto(): index 0 out of range for 0 position attributes in morph target 37\n" "Trade::MeshData::tangentsInto(): index 0 out of range for 0 tangent attributes\n" + "Trade::MeshData::tangentsInto(): index 0 out of range for 0 tangent attributes in morph target 37\n" "Trade::MeshData::bitangentSignsInto(): index 0 out of range for 0 tangent attributes\n" + "Trade::MeshData::bitangentSignsInto(): index 0 out of range for 0 tangent attributes in morph target 37\n" "Trade::MeshData::bitangentsInto(): index 0 out of range for 0 bitangent attributes\n" + "Trade::MeshData::bitangentsInto(): index 0 out of range for 0 bitangent attributes in morph target 37\n" "Trade::MeshData::normalsInto(): index 0 out of range for 0 normal attributes\n" + "Trade::MeshData::normalsInto(): index 0 out of range for 0 normal attributes in morph target 37\n" "Trade::MeshData::textureCoordinates2DInto(): index 0 out of range for 0 texture coordinate attributes\n" + "Trade::MeshData::textureCoordinates2DInto(): index 0 out of range for 0 texture coordinate attributes in morph target 37\n" "Trade::MeshData::colorsInto(): index 2 out of range for 2 color attributes\n" + "Trade::MeshData::colorsInto(): index 3 out of range for 3 color attributes in morph target 37\n" "Trade::MeshData::jointIdsAsArray(): index 2 out of range for 2 joint ID attributes\n" "Trade::MeshData::jointIdsInto(): index 2 out of range for 2 joint ID attributes\n" "Trade::MeshData::weightsAsArray(): index 2 out of range for 2 weight attributes\n"