From 12dd74a347aa49eb34065b2e351ae23acdbff47b Mon Sep 17 00:00:00 2001 From: Squareys Date: Sun, 3 May 2020 10:24:18 +0200 Subject: [PATCH] Trade: Implement support for Weights & JointIndices in MeshData. Signed-off-by: Squareys --- src/Magnum/Trade/MeshData.cpp | 54 +++++++++++++ src/Magnum/Trade/MeshData.h | 83 ++++++++++++++++++- src/Magnum/Trade/Test/MeshDataTest.cpp | 106 +++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 4 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index a4a949bc2..354949d4e 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -777,6 +777,58 @@ Containers::Array MeshData::objectIdsAsArray(const UnsignedInt id) return out; } +void MeshData::weightsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Weights, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::weightsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Weights) << "weights attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::weightsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::weightsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); + const Containers::StridedArrayView2D destination4f = Containers::arrayCast<2, Float>(destination); + + if(attribute._format == VertexFormat::Vector4) + Utility::copy(Containers::arrayCast(attributeData), destination); + else if(attribute._format == VertexFormat::Vector4h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4f); + else if(attribute._format == VertexFormat::Vector4ubNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 4), destination4f); + else if(attribute._format == VertexFormat::Vector4usNormalized) + Math::unpackInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4f); + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::weightsAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + weightsInto(out, id); + return out; +} + +void MeshData::jointIdsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::JointIds, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::jointIdsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::JointIds) << "joint IDs attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::jointIdsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::jointIdsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); + const Containers::StridedArrayView2D destination4ui = Containers::arrayCast<2, UnsignedInt>(destination); + + if(attribute._format == VertexFormat::Vector4ui) + Utility::copy(Containers::arrayCast(attributeData), destination); + else if(attribute._format == VertexFormat::Vector4ub) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 4), destination4ui); + else if(attribute._format == VertexFormat::Vector4us) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4), destination4ui); + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array MeshData::jointIdsAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + jointIdsInto(out, id); + return out; +} + Containers::Array MeshData::releaseIndexData() { _indexCount = 0; Containers::Array out = std::move(_indexData); @@ -811,6 +863,8 @@ Debug& operator<<(Debug& debug, const MeshAttribute value) { _c(TextureCoordinates) _c(Color) _c(ObjectId) + _c(Weights) + _c(JointIds) #undef _c /* LCOV_EXCL_STOP */ diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 06390f8ce..4ce7aec3d 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -147,6 +147,23 @@ enum class MeshAttribute: UnsignedShort { */ ObjectId, + /** + * Weights. Type is usually @ref VertexFormat::Vector4, but can be also + * @ref VertexFormat::Vector4h, @ref VertexFormat::Vector4ubNormalized + * or @ref VertexFormat::Vector4usNormalized. + * Corresponds to @ref Shaders::Generic::Weights. + * @see @ref MeshData::weightsAsArray() + */ + Weights, + + /** + * Joint IDs. Type is usually @ref VertexFormat::Vector4ui, but can be also + * @ref VertexFormat::Vector4us or @ref VertexFormat::Vector4ub. + * Corresponds to @ref Shaders::Generic::JointIds. + * @see @ref MeshData::jointIdsAsArray() + */ + JointIds, + /** * This and all higher values are for importer-specific attributes. Can be * of any type. See documentation of a particular importer for details. @@ -613,10 +630,11 @@ the @ref Primitives library. The simplest usage is through the convenience functions @ref positions2DAsArray(), @ref positions3DAsArray(), @ref tangentsAsArray(), @ref bitangentsAsArray(), @ref normalsAsArray(), @ref tangentsAsArray(), @ref textureCoordinates2DAsArray(), -@ref colorsAsArray() and @ref objectIdsAsArray(). Each of these takes an index -(as there can be multiple sets of texture coordinates, for example) and you're -expected to check for attribute presence first with either @ref hasAttribute() -or @ref attributeCount(MeshAttribute) const: +@ref colorsAsArray(), @ref objectIdsAsArray(), @ref weightsAsArray() and +@ref jointIdsAsArray(). Each of these takes an index (as there can be multiple +sets of texture coordinates, for example) and you're expected to check for +attribute presence first with either @ref hasAttribute() or +@ref attributeCount(MeshAttribute) const: @snippet MagnumTrade.cpp MeshData-usage @@ -1717,6 +1735,54 @@ class MAGNUM_TRADE_EXPORT MeshData { */ void objectIdsInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + /** + * @brief Weights as 4D float vectors + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * with @ref MeshAttribute::Weights as the first argument. Converts + * the weights 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. + * @see @ref weightsInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() + */ + Containers::Array weightsAsArray(UnsignedInt id = 0) const; + + /** + * @brief Weights as 4D float vectors into a pre-allocated view + * + * Like @ref weightsAsArray(), but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref vertexCount() + */ + void weightsInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Joint IDs as 4D unsigned int vectors + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) const + * with @ref MeshAttribute::JointIds as the first argument. Converts + * the joint indices 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. + * @see @ref jointIdsInto(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() + */ + Containers::Array jointIdsAsArray(UnsignedInt id = 0) const; + + /** + * @brief Joint IDs as 4D unsigned int vectors into a pre-allocated view + * + * Like @ref jointIdsAsArray(), but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref vertexCount() + */ + void jointIdsInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + /** * @brief Release index data storage * @@ -2064,6 +2130,15 @@ namespace Implementation { (format == VertexFormat::UnsignedInt || format == VertexFormat::UnsignedShort || format == VertexFormat::UnsignedByte)) || + (name == MeshAttribute::Weights && + (format == VertexFormat::Vector4 || + format == VertexFormat::Vector4h || + format == VertexFormat::Vector4ubNormalized || + format == VertexFormat::Vector4usNormalized)) || + (name == MeshAttribute::JointIds && + (format == VertexFormat::Vector4ui || + format == VertexFormat::Vector4ub || + format == VertexFormat::Vector4us)) || /* Custom attributes can be anything */ isMeshAttributeCustom(name); } diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 301d305f4..20ef216fb 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -146,6 +146,14 @@ struct MeshDataTest: TestSuite::Tester { template void objectIdsAsArray(); void objectIdsIntoArrayInvalidSize(); + template void weightsAsArray(); + template void weightsAsArrayPackedUnsignedNormalized(); + template void weightsAsArrayPackedSignedNormalized(); + void weightsIntoArrayInvalidSize(); + + template void jointIdsAsArray(); + void jointIdsIntoArrayInvalidSize(); + void implementationSpecificVertexFormat(); void implementationSpecificVertexFormatWrongAccess(); void implementationSpecificVertexFormatNotContained(); @@ -364,6 +372,17 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::objectIdsAsArray, &MeshDataTest::objectIdsIntoArrayInvalidSize, + &MeshDataTest::weightsAsArray, + &MeshDataTest::weightsAsArray, + &MeshDataTest::weightsAsArrayPackedUnsignedNormalized, + &MeshDataTest::weightsAsArrayPackedUnsignedNormalized, + &MeshDataTest::weightsIntoArrayInvalidSize, + + &MeshDataTest::jointIdsAsArray, + &MeshDataTest::jointIdsAsArray, + &MeshDataTest::jointIdsAsArray, + &MeshDataTest::jointIdsIntoArrayInvalidSize, + &MeshDataTest::implementationSpecificVertexFormat, &MeshDataTest::implementationSpecificVertexFormatWrongAccess, &MeshDataTest::implementationSpecificVertexFormatNotContained, @@ -1856,8 +1875,11 @@ _c(Vector3b) _c(Vector3us) _c(Vector3s) _c(Vector4) +_c(Vector4ui) _c(Vector4h) +_c(Vector4ub) _c(Vector4b) +_c(Vector4us) _c(Vector4s) _c(Color3) _c(Color3h) @@ -2500,6 +2522,90 @@ void MeshDataTest::objectIdsIntoArrayInvalidSize() { "Trade::MeshData::objectIdsInto(): expected a view with 3 elements but got 2\n"); } +template void MeshDataTest::weightsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; + + Containers::Array vertexData{3*sizeof(T)}; + auto weightsView = Containers::arrayCast(vertexData); + /* Needs to be sufficiently representable to have the test work also for + half floats */ + weightsView[0] = T::pad(Math::Vector4{U(2.0f), U(1.0f), U(0.75f), U(3.0f)}); + weightsView[1] = T::pad(Math::Vector4{U(0.0f), U(-1.0f), U(1.25f), U(1.0f)}); + weightsView[2] = T::pad(Math::Vector4{U(-2.0f), U(3.0f), U(2.5f), U(2.5f)}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Weights, weightsView}}}; + CORRADE_COMPARE_AS(data.weightsAsArray(), Containers::arrayView({ + {2.0f, 1.0f, 0.75f, 3.0f}, {0.0f, -1.0f, 1.25f, 1.0f}, {-2.0f, 3.0f, 2.5f, 2.5f}, + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::weightsAsArrayPackedUnsignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto weightsView = Containers::arrayCast(vertexData); + weightsView[0] = T::pad(Math::Vector4{Math::pack(1.0f), 0, Math::pack(1.0f), Math::pack(0.8)}); + weightsView[1] = T::pad(Math::Vector4{0, Math::pack(1.0f), 0, Math::pack(0.4f)}); + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Weights, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + weightsView}}}; + CORRADE_COMPARE_AS(data.weightsAsArray(), Containers::arrayView({ + Vector4::pad(Math::Vector::pad(Vector4{1.0f, 0.0f, 1.0f, 0.8})), + Vector4::pad(Math::Vector::pad(Vector4{0.0f, 1.0f, 0.0f, 0.4f})) + }), TestSuite::Compare::Container); +} + +void MeshDataTest::weightsIntoArrayInvalidSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Containers::Array vertexData{3*sizeof(Vector4)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Weights, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Vector4 destination[2]; + data.weightsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::weightsInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::jointIdsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; + + Containers::Array vertexData{3*sizeof(T)}; + auto joinIdsView = Containers::arrayCast(vertexData); + joinIdsView[0] = {U(0), U(1), U(2), U(3)}; + joinIdsView[1] = {U(4), U(5), U(6), U(7)}; + joinIdsView[2] = {U(8), U(9), U(10), U(11)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::JointIds, joinIdsView}}}; + CORRADE_COMPARE_AS(data.jointIdsAsArray(), Containers::arrayView({ + {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}, + }), TestSuite::Compare::Container); +} + +void MeshDataTest::jointIdsIntoArrayInvalidSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Containers::Array vertexData{3*sizeof(Vector4ui)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::JointIds, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Vector4ui destination[2]; + data.jointIdsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::jointIdsInto(): expected a view with 3 elements but got 2\n"); +} + /* MSVC 2015 doesn't like anonymous bitfields in inline structs, so putting the declaration outside */ struct VertexWithImplementationSpecificData {