From f4bca9b51ec14a5ab26a770b21df6f9065f7b404 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 16 Mar 2020 17:50:42 +0100 Subject: [PATCH] Trade: implement support for tangents and bitangents in MeshData. Originally wanted to offload this to someone else, but then realized I need those for generic vertex attribute definitions, which I need for instancing, which I need now. So here it is, at the bottom of the dependency chain. --- doc/snippets/MagnumTrade.cpp | 9 ++ src/Magnum/Trade/MeshData.cpp | 105 +++++++++++-- src/Magnum/Trade/MeshData.h | 141 +++++++++++++++++- src/Magnum/Trade/Test/MeshDataTest.cpp | 194 +++++++++++++++++++++++++ 4 files changed, 430 insertions(+), 19 deletions(-) diff --git a/doc/snippets/MagnumTrade.cpp b/doc/snippets/MagnumTrade.cpp index 4383a3fea..77da74394 100644 --- a/doc/snippets/MagnumTrade.cpp +++ b/doc/snippets/MagnumTrade.cpp @@ -254,6 +254,15 @@ Trade::MeshIndexData data{indices}; /* [MeshIndexData-usage] */ } +{ +Vector3 normal; +Vector4 tangent; +/* [MeshAttribute-bitangent-from-tangent] */ +Vector3 bitangent = Math::cross(normal, tangent.xyz())*tangent.w(); +/* [MeshAttribute-bitangent-from-tangent] */ +static_cast(bitangent); +} + { /* [MeshAttributeData-usage] */ Containers::StridedArrayView1D positions; diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 948526f52..1db25bcfe 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -557,27 +557,106 @@ Containers::Array MeshData::positions3DAsArray(const UnsignedInt id) co return out; } -void MeshData::normalsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { - const UnsignedInt attributeId = attributeFor(MeshAttribute::Normal, id); - CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal) << "normal attributes", ); - CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::normalsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); - const MeshAttributeData& attribute = _attributes[attributeId]; - CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), - "Trade::MeshData::normalsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); - const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); +namespace { + +void tangentsOrNormalsInto(const Containers::StridedArrayView1D attributeData, const Containers::StridedArrayView1D destination, VertexFormat format) { const auto destination3f = Containers::arrayCast<2, Float>(destination); - if(attribute._format == VertexFormat::Vector3) + if(format == VertexFormat::Vector3) Utility::copy(Containers::arrayCast(attributeData), destination); - else if(attribute._format == VertexFormat::Vector3h) + else if(format == VertexFormat::Vector3h) Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 3), destination3f); - else if(attribute._format == VertexFormat::Vector3bNormalized) + else if(format == VertexFormat::Vector3bNormalized) Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 3), destination3f); - else if(attribute._format == VertexFormat::Vector3sNormalized) + else if(format == VertexFormat::Vector3sNormalized) Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 3), destination3f); else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } +} + +void MeshData::tangentsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Tangent, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::tangentsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Tangent) << "tangent attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::tangentsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::tangentsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); + + /* If the tangent is four-component, ignore the last component; otherwise + copy/unpack given format directly */ + VertexFormat format; + if(attribute._format == VertexFormat::Vector4) + format = VertexFormat::Vector3; + else if(attribute._format == VertexFormat::Vector4h) + format = VertexFormat::Vector3h; + else if(attribute._format == VertexFormat::Vector4ubNormalized) + format = VertexFormat::Vector3ubNormalized; + else if(attribute._format == VertexFormat::Vector4usNormalized) + format = VertexFormat::Vector3usNormalized; + else format = attribute._format; + tangentsOrNormalsInto(attributeDataViewInternal(attribute), destination, format); +} + +Containers::Array MeshData::tangentsAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + tangentsInto(out, id); + return out; +} + +void MeshData::bitangentSignsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Tangent, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::bitangentSignsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Tangent) << "tangent attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::bitangentSignsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::bitangentSignsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); + const auto destination1f = Containers::arrayCast<2, Float>(destination); + + if(attribute._format == VertexFormat::Vector4) + Utility::copy(Containers::arrayCast<2, const Float>(attributeData, 4).transposed<0, 1>()[3], destination); + else if(attribute._format == VertexFormat::Vector4h) + Math::unpackHalfInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 4).suffix({0, 3}), destination1f); + else if(attribute._format == VertexFormat::Vector4bNormalized) + Math::unpackInto(Containers::arrayCast<2, const Byte>(attributeData, 4).suffix({0, 3}), destination1f); + else if(attribute._format == VertexFormat::Vector4sNormalized) + Math::unpackInto(Containers::arrayCast<2, const Short>(attributeData, 4).suffix({0, 3}), destination1f); + else CORRADE_ASSERT(false, "Trade::MeshData::bitangentSignsInto(): expected four-component tangents, but got" << attribute._format, ); +} + +Containers::Array MeshData::bitangentSignsAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + bitangentSignsInto(out, id); + return out; +} + +void MeshData::bitangentsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Bitangent, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::bitangentsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Bitangent) << "bitangent attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::bitangentsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::bitangentsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); + tangentsOrNormalsInto(attributeDataViewInternal(attribute), destination, attribute._format); +} + +Containers::Array MeshData::bitangentsAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + bitangentsInto(out, id); + return out; +} + +void MeshData::normalsInto(const Containers::StridedArrayView1D destination, const UnsignedInt id) const { + const UnsignedInt attributeId = attributeFor(MeshAttribute::Normal, id); + CORRADE_ASSERT(attributeId != ~UnsignedInt{}, "Trade::MeshData::normalsInto(): index" << id << "out of range for" << attributeCount(MeshAttribute::Normal) << "normal attributes", ); + CORRADE_ASSERT(destination.size() == _vertexCount, "Trade::MeshData::normalsInto(): expected a view with" << _vertexCount << "elements but got" << destination.size(), ); + const MeshAttributeData& attribute = _attributes[attributeId]; + CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format), + "Trade::MeshData::normalsInto(): can't extract data out of an implementation-specific vertex format" << reinterpret_cast(vertexFormatUnwrap(attribute._format)), ); + tangentsOrNormalsInto(attributeDataViewInternal(attribute), destination, attribute._format); +} + Containers::Array MeshData::normalsAsArray(const UnsignedInt id) const { Containers::Array out{_vertexCount}; normalsInto(out, id); @@ -704,6 +783,8 @@ Debug& operator<<(Debug& debug, const MeshAttribute value) { /* LCOV_EXCL_START */ #define _c(value) case MeshAttribute::value: return debug << "::" << Debug::nospace << #value; _c(Position) + _c(Tangent) + _c(Bitangent) _c(Normal) _c(TextureCoordinates) _c(Color) diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 351478cf4..0686ce71e 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -72,6 +72,36 @@ enum class MeshAttribute: UnsignedShort { */ Position = 1, + /** + * Tangent, optionally including bitangent sign. In the first case the type + * is usually @ref VertexFormat::Vector3, but can be also + * @ref VertexFormat::Vector3h, @ref VertexFormat::Vector3bNormalized or + * @ref VertexFormat::Vector3sNormalized; in the second case the type is + * @ref VertexFormat::Vector4 (or @ref VertexFormat::Vector4h, + * @ref VertexFormat::Vector4bNormalized, + * @ref VertexFormat::Vector4sNormalized) and the fourth component is a + * sign value (@cpp -1.0f @ce or @cpp +1.0f @ce) defining handedness of the + * tangent basis. Reconstruct the @ref MeshAttribute::Bitangent can be then + * done like this: + * + * @snippet MagnumTrade.cpp MeshAttribute-bitangent-from-tangent + * + * Corresponds to @ref Shaders::Generic::Tangent. + * @see @ref MeshData::tangentsAsArray(), + * @ref MeshData::bitangentSignsAsArray() + */ + Tangent, + + /** + * Bitangent. Type is usually @ref VertexFormat::Vector3, but can be also + * @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. + * @see @ref MeshData::bitangentsAsArray() + */ + Bitangent, + /** * Normal. Type is usually @ref VertexFormat::Vector3, but can be also * @ref VertexFormat::Vector3h. @ref VertexFormat::Vector3bNormalized or @@ -563,7 +593,8 @@ the @ref Primitives library. @section Trade-MeshData-usage Basic usage The simplest usage is through the convenience functions @ref positions2DAsArray(), -@ref positions3DAsArray(), @ref normalsAsArray(), @ref textureCoordinates2DAsArray() +@ref positions3DAsArray(), @ref tangentsAsArray(), @ref bitangentsAsArray(), +@ref normalsAsArray(), @ref tangentsAsArray(), @ref textureCoordinates2DAsArray() and @ref colorsAsArray(). 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 @@ -1293,10 +1324,12 @@ class MAGNUM_TRADE_EXPORT MeshData { * case you need to use the overload below by using @cpp T[] @ce * instead of @cpp T @ce. You can also use the non-templated * @ref positions2DAsArray(), @ref positions3DAsArray(), - * @ref normalsAsArray(), @ref textureCoordinates2DAsArray() and - * @ref colorsAsArray() accessors to get common attributes converted to - * usual types, but note that these operations involve extra allocation - * and data conversion. + * @ref tangentsAsArray(), @ref bitangentSignsAsArray(), + * @ref bitangentsAsArray(), @ref normalsAsArray(), + * @ref textureCoordinates2DAsArray() and @ref colorsAsArray() + * accessors to get common attributes converted to usual types, but + * note that these operations involve extra allocation and data + * conversion. * @see @ref attribute(MeshAttribute, UnsignedInt) const, * @ref mutableAttribute(MeshAttribute, UnsignedInt), * @ref isVertexFormatImplementationSpecific(), @@ -1483,6 +1516,90 @@ class MAGNUM_TRADE_EXPORT MeshData { */ void positions3DInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + /** + * @brief Tangents as 3D float vectors + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) 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. + * + * If the tangents contain a fourth component with bitangent direction, + * it's ignored here --- use @ref bitangentSignsAsArray() to get those + * instead. You can also use @ref tangentsInto() together with + * @ref bitangentSignsInto() to put them both in a single array. + * @see @ref bitangentsAsArray(), @ref normalsAsArray(), + * @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() + */ + Containers::Array tangentsAsArray(UnsignedInt id = 0) const; + + /** + * @brief Tangents as 3D float vectors into a pre-allocated view + * + * Like @ref tangentsAsArray(), 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 tangentsInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Bitangent signs as floats + * + * Counterpart to @ref tangentsAsArray() returning value of the fourth + * component. Expects that the type of @ref MeshAttribute::Tangent is + * four-component. You can also use @ref tangentsInto() together with + * @ref bitangentSignsInto() to put them both in a single array. + * @see @ref tangentsAsArray(), @ref bitangentsAsArray(), + * @ref normalsAsArray(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() + */ + Containers::Array bitangentSignsAsArray(UnsignedInt id = 0) const; + + /** + * @brief Bitangent signs as floats into a pre-allocated view + * + * Like @ref bitangentsAsArray(), 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 bitangentSignsInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + + /** + * @brief Bitangents as 3D float vectors + * + * Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) 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. + * + * Note that in some cases the bitangents aren't provided directly but + * calculated from normals and four-component tangents. In that case + * you'll need to get bitangent signs via @ref bitangentSignsAsArray() + * and calculate the bitangents as shown in the documentation of + * @ref MeshAttribute::Tangent. + * @see @ref bitangentsInto(), @ref tangentsAsArray(), + * @ref normalsAsArray(), @ref attributeFormat(), + * @ref isVertexFormatImplementationSpecific() + */ + Containers::Array bitangentsAsArray(UnsignedInt id = 0) const; + + /** + * @brief Bitangents as 3D float vectors into a pre-allocated view + * + * Like @ref bitangentsAsArray(), 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 bitangentsInto(Containers::StridedArrayView1D destination, UnsignedInt id = 0) const; + /** * @brief Normals as 3D float vectors * @@ -1492,7 +1609,8 @@ class MAGNUM_TRADE_EXPORT MeshData { * 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 normalsInto(), @ref attributeFormat(), + * @see @ref normalsInto(), @ref tangentsAsArray(), + * @ref bitangentsAsArray(), @ref attributeFormat(), * @ref isVertexFormatImplementationSpecific() */ Containers::Array normalsAsArray(UnsignedInt id = 0) const; @@ -1862,7 +1980,16 @@ namespace Implementation { format == VertexFormat::Vector3usNormalized || format == VertexFormat::Vector3s || format == VertexFormat::Vector3sNormalized)) || - (name == MeshAttribute::Normal && + (name == MeshAttribute::Tangent && + (format == VertexFormat::Vector3 || + format == VertexFormat::Vector3h || + format == VertexFormat::Vector3bNormalized || + format == VertexFormat::Vector3sNormalized || + format == VertexFormat::Vector4 || + format == VertexFormat::Vector4h || + format == VertexFormat::Vector4bNormalized || + format == VertexFormat::Vector4sNormalized)) || + ((name == MeshAttribute::Bitangent || name == MeshAttribute::Normal) && (format == VertexFormat::Vector3 || format == VertexFormat::Vector3h || format == VertexFormat::Vector3bNormalized || diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 628e20da3..8dfd8f7ed 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -121,6 +121,16 @@ struct MeshDataTest: TestSuite::Tester { template void positions3DAsArrayPackedUnsignedNormalized(); template void positions3DAsArrayPackedSignedNormalized(); void positions3DIntoArrayInvalidSize(); + template void tangentsAsArray(); + template void tangentsAsArrayPackedSignedNormalized(); + void tangentsIntoArrayInvalidSize(); + template void bitangentSignsAsArray(); + template void bitangentSignsAsArrayPackedSignedNormalized(); + void bitangentSignsAsArrayNotFourComponent(); + void bitangentSignsIntoArrayInvalidSize(); + template void bitangentsAsArray(); + template void bitangentsAsArrayPackedSignedNormalized(); + void bitangentsIntoArrayInvalidSize(); template void normalsAsArray(); template void normalsAsArrayPackedSignedNormalized(); void normalsIntoArrayInvalidSize(); @@ -302,6 +312,22 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::positions3DAsArrayPackedSignedNormalized, &MeshDataTest::positions3DAsArrayPackedSignedNormalized, &MeshDataTest::positions3DIntoArrayInvalidSize, + &MeshDataTest::tangentsAsArray, + &MeshDataTest::tangentsAsArray, + &MeshDataTest::tangentsAsArrayPackedSignedNormalized, + &MeshDataTest::tangentsAsArrayPackedSignedNormalized, + &MeshDataTest::tangentsIntoArrayInvalidSize, + &MeshDataTest::bitangentSignsAsArray, + &MeshDataTest::bitangentSignsAsArray, + &MeshDataTest::bitangentSignsAsArrayPackedSignedNormalized, + &MeshDataTest::bitangentSignsAsArrayPackedSignedNormalized, + &MeshDataTest::bitangentSignsAsArrayNotFourComponent, + &MeshDataTest::bitangentSignsIntoArrayInvalidSize, + &MeshDataTest::bitangentsAsArray, + &MeshDataTest::bitangentsAsArray, + &MeshDataTest::bitangentsAsArrayPackedSignedNormalized, + &MeshDataTest::bitangentsAsArrayPackedSignedNormalized, + &MeshDataTest::bitangentsIntoArrayInvalidSize, &MeshDataTest::normalsAsArray, &MeshDataTest::normalsAsArray, &MeshDataTest::normalsAsArrayPackedSignedNormalized, @@ -1946,6 +1972,158 @@ void MeshDataTest::positions3DIntoArrayInvalidSize() { "Trade::MeshData::positions3DInto(): expected a view with 3 elements but got 2\n"); } +template void MeshDataTest::tangentsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; + + Containers::Array vertexData{3*sizeof(T)}; + auto tangentView = Containers::arrayCast(vertexData); + /* Needs to be sufficiently representable to have the test work also for + half floats */ + tangentView[0] = {U(2.0f), U(1.0f), U(0.75f)}; + tangentView[1] = {U(0.0f), U(-1.0f), U(1.25f)}; + tangentView[2] = {U(-2.0f), U(3.0f), U(2.5f)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Tangent, tangentView}}}; + CORRADE_COMPARE_AS(data.tangentsAsArray(), Containers::arrayView({ + {2.0f, 1.0f, 0.75f}, {0.0f, -1.0f, 1.25f}, {-2.0f, 3.0f, 2.5f}, + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::tangentsAsArrayPackedSignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto tangentsView = Containers::arrayCast(vertexData); + tangentsView[0] = {Math::pack(1.0f), 0, Math::pack(1.0f)}; + tangentsView[1] = {0, Math::pack(-1.0f), 0}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Tangent, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + tangentsView}}}; + CORRADE_COMPARE_AS(data.tangentsAsArray(), Containers::arrayView({ + {1.0f, 0.0f, 1.0f}, {0.0f, -1.0f, 0.0f} + }), TestSuite::Compare::Container); +} + +void MeshDataTest::tangentsIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Vector3)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Tangent, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Vector3 destination[2]; + data.tangentsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::tangentsInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::bitangentSignsAsArray() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + Containers::Array vertexData{3*sizeof(Math::Vector4)}; + auto tangentView = Containers::arrayCast>(vertexData); + /* Needs to be sufficiently representable to have the test work also for + half floats */ + tangentView[0] = {T(2.0f), T(1.0f), T(0.75f), T(-1.0f)}; + tangentView[1] = {T(0.0f), T(-1.0f), T(1.25f), T(1.0f)}; + tangentView[2] = {T(-2.0f), T(3.0f), T(2.5f), T(-1.0f)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Tangent, tangentView}}}; + CORRADE_COMPARE_AS(data.bitangentSignsAsArray(), Containers::arrayView({ + -1.0f, 1.0f, -1.0f + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::bitangentSignsAsArrayPackedSignedNormalized() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + Containers::Array vertexData{2*sizeof(Math::Vector4)}; + auto bitangentsView = Containers::arrayCast>(vertexData); + bitangentsView[0] = {Math::pack(1.0f), 0, Math::pack(1.0f), Math::pack(-1.0f)}; + bitangentsView[1] = {0, Math::pack(-1.0f), 0, Math::pack(1.0f)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Tangent, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor>()) + 1), + bitangentsView}}}; + CORRADE_COMPARE_AS(data.bitangentSignsAsArray(), Containers::arrayView({ + -1.0f, 1.0f + }), TestSuite::Compare::Container); +} + +void MeshDataTest::bitangentSignsAsArrayNotFourComponent() { + Containers::Array vertexData{3*sizeof(Vector3s)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Tangent, VertexFormat::Vector3sNormalized, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Float destination[3]; + data.bitangentSignsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::bitangentSignsInto(): expected four-component tangents, but got VertexFormat::Vector3sNormalized\n"); +} + +void MeshDataTest::bitangentSignsIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Vector4)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Tangent, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Float destination[2]; + data.bitangentSignsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::bitangentSignsInto(): expected a view with 3 elements but got 2\n"); +} + +template void MeshDataTest::bitangentsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + typedef typename T::Type U; + + Containers::Array vertexData{3*sizeof(T)}; + auto bitangentsView = Containers::arrayCast(vertexData); + /* Needs to be sufficiently representable to have the test work also for + half floats */ + bitangentsView[0] = {U(2.0f), U(1.0f), U(0.75f)}; + bitangentsView[1] = {U(0.0f), U(-1.0f), U(1.25f)}; + bitangentsView[2] = {U(-2.0f), U(3.0f), U(2.5f)}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Bitangent, bitangentsView}}}; + CORRADE_COMPARE_AS(data.bitangentsAsArray(), Containers::arrayView({ + {2.0f, 1.0f, 0.75f}, {0.0f, -1.0f, 1.25f}, {-2.0f, 3.0f, 2.5f}, + }), TestSuite::Compare::Container); +} + +template void MeshDataTest::bitangentsAsArrayPackedSignedNormalized() { + setTestCaseTemplateName(NameTraits::name()); + + Containers::Array vertexData{2*sizeof(T)}; + auto bitangentsView = Containers::arrayCast(vertexData); + bitangentsView[0] = {Math::pack(1.0f), 0, Math::pack(1.0f)}; + bitangentsView[1] = {0, Math::pack(-1.0f), 0}; + + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Bitangent, + /* Assuming the normalized enum is always after the non-normalized */ + VertexFormat(UnsignedInt(Implementation::vertexFormatFor()) + 1), + bitangentsView}}}; + CORRADE_COMPARE_AS(data.bitangentsAsArray(), Containers::arrayView({ + {1.0f, 0.0f, 1.0f}, {0.0f, -1.0f, 0.0f} + }), TestSuite::Compare::Container); +} + +void MeshDataTest::bitangentsIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(Vector3)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Bitangent, Containers::arrayCast(vertexData)}}}; + + std::ostringstream out; + Error redirectError{&out}; + Vector3 destination[2]; + data.bitangentsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::MeshData::bitangentsInto(): expected a view with 3 elements but got 2\n"); +} + template void MeshDataTest::normalsAsArray() { setTestCaseTemplateName(NameTraits::name()); typedef typename T::Type U; @@ -2188,6 +2366,10 @@ void MeshDataTest::implementationSpecificVertexFormatWrongAccess() { MeshData data{MeshPrimitive::TriangleFan, DataFlag::Mutable, vertexData, { MeshAttributeData{MeshAttribute::Position, vertexFormatWrap(0xdead1), attribute}, + MeshAttributeData{MeshAttribute::Tangent, + vertexFormatWrap(0xdead2), attribute}, + MeshAttributeData{MeshAttribute::Bitangent, + vertexFormatWrap(0xdead2), attribute}, MeshAttributeData{MeshAttribute::Normal, vertexFormatWrap(0xdead2), attribute}, MeshAttributeData{MeshAttribute::TextureCoordinates, @@ -2207,6 +2389,9 @@ void MeshDataTest::implementationSpecificVertexFormatWrongAccess() { data.mutableAttribute(MeshAttribute::Color); data.positions2DAsArray(); data.positions3DAsArray(); + data.tangentsAsArray(); + data.bitangentSignsAsArray(); + data.bitangentsAsArray(); data.normalsAsArray(); data.textureCoordinates2DAsArray(); data.colorsAsArray(); @@ -2221,6 +2406,9 @@ void MeshDataTest::implementationSpecificVertexFormatWrongAccess() { "Trade::MeshData::mutableAttribute(): can't cast data from an implementation-specific vertex format 0xdead4\n" "Trade::MeshData::positions2DInto(): can't extract data out of an implementation-specific vertex format 0xdead1\n" "Trade::MeshData::positions3DInto(): can't extract data out of an implementation-specific vertex format 0xdead1\n" + "Trade::MeshData::tangentsInto(): can't extract data out of an implementation-specific vertex format 0xdead2\n" + "Trade::MeshData::bitangentSignsInto(): can't extract data out of an implementation-specific vertex format 0xdead2\n" + "Trade::MeshData::bitangentsInto(): can't extract data out of an implementation-specific vertex format 0xdead2\n" "Trade::MeshData::normalsInto(): can't extract data out of an implementation-specific vertex format 0xdead2\n" "Trade::MeshData::textureCoordinatesInto(): can't extract data out of an implementation-specific vertex format 0xdead3\n" "Trade::MeshData::colorsInto(): can't extract data out of an implementation-specific vertex format 0xdead4\n"); @@ -2430,6 +2618,9 @@ void MeshDataTest::attributeNotFound() { data.attribute(MeshAttribute::Color, 2); data.positions2DAsArray(); data.positions3DAsArray(); + data.tangentsAsArray(); + data.bitangentSignsAsArray(); + data.bitangentsAsArray(); data.normalsAsArray(); data.textureCoordinates2DAsArray(); data.colorsAsArray(2); @@ -2457,6 +2648,9 @@ void MeshDataTest::attributeNotFound() { "Trade::MeshData::attribute(): index 2 out of range for 2 Trade::MeshAttribute::Color attributes\n" "Trade::MeshData::positions2DInto(): index 0 out of range for 0 position attributes\n" "Trade::MeshData::positions3DInto(): index 0 out of range for 0 position attributes\n" + "Trade::MeshData::tangentsInto(): index 0 out of range for 0 tangent attributes\n" + "Trade::MeshData::bitangentSignsInto(): index 0 out of range for 0 tangent attributes\n" + "Trade::MeshData::bitangentsInto(): index 0 out of range for 0 bitangent attributes\n" "Trade::MeshData::normalsInto(): index 0 out of range for 0 normal attributes\n" "Trade::MeshData::textureCoordinates2DInto(): index 0 out of range for 0 texture coordinate attributes\n" "Trade::MeshData::colorsInto(): index 2 out of range for 2 color attributes\n");