Browse Source

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.
pull/430/head
Vladimír Vondruš 6 years ago
parent
commit
f4bca9b51e
  1. 9
      doc/snippets/MagnumTrade.cpp
  2. 105
      src/Magnum/Trade/MeshData.cpp
  3. 141
      src/Magnum/Trade/MeshData.h
  4. 194
      src/Magnum/Trade/Test/MeshDataTest.cpp

9
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<void>(bitangent);
}
{
/* [MeshAttributeData-usage] */
Containers::StridedArrayView1D<const Vector3> positions;

105
src/Magnum/Trade/MeshData.cpp

@ -557,27 +557,106 @@ Containers::Array<Vector3> MeshData::positions3DAsArray(const UnsignedInt id) co
return out;
}
void MeshData::normalsInto(const Containers::StridedArrayView1D<Vector3> 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<void*>(vertexFormatUnwrap(attribute._format)), );
const Containers::StridedArrayView1D<const void> attributeData = attributeDataViewInternal(attribute);
namespace {
void tangentsOrNormalsInto(const Containers::StridedArrayView1D<const void> attributeData, const Containers::StridedArrayView1D<Vector3> destination, VertexFormat format) {
const auto destination3f = Containers::arrayCast<2, Float>(destination);
if(attribute._format == VertexFormat::Vector3)
if(format == VertexFormat::Vector3)
Utility::copy(Containers::arrayCast<const Vector3>(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<Vector3> 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<void*>(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<Vector3> MeshData::tangentsAsArray(const UnsignedInt id) const {
Containers::Array<Vector3> out{_vertexCount};
tangentsInto(out, id);
return out;
}
void MeshData::bitangentSignsInto(const Containers::StridedArrayView1D<Float> 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<void*>(vertexFormatUnwrap(attribute._format)), );
const Containers::StridedArrayView1D<const void> 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<Float> MeshData::bitangentSignsAsArray(const UnsignedInt id) const {
Containers::Array<Float> out{_vertexCount};
bitangentSignsInto(out, id);
return out;
}
void MeshData::bitangentsInto(const Containers::StridedArrayView1D<Vector3> 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<void*>(vertexFormatUnwrap(attribute._format)), );
tangentsOrNormalsInto(attributeDataViewInternal(attribute), destination, attribute._format);
}
Containers::Array<Vector3> MeshData::bitangentsAsArray(const UnsignedInt id) const {
Containers::Array<Vector3> out{_vertexCount};
bitangentsInto(out, id);
return out;
}
void MeshData::normalsInto(const Containers::StridedArrayView1D<Vector3> 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<void*>(vertexFormatUnwrap(attribute._format)), );
tangentsOrNormalsInto(attributeDataViewInternal(attribute), destination, attribute._format);
}
Containers::Array<Vector3> MeshData::normalsAsArray(const UnsignedInt id) const {
Containers::Array<Vector3> 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)

141
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<Vector3> 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<Vector3> 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<Vector3> 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<Float> 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<Float> 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<Vector3> 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<Vector3> 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<Vector3> 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 ||

194
src/Magnum/Trade/Test/MeshDataTest.cpp

@ -121,6 +121,16 @@ struct MeshDataTest: TestSuite::Tester {
template<class T> void positions3DAsArrayPackedUnsignedNormalized();
template<class T> void positions3DAsArrayPackedSignedNormalized();
void positions3DIntoArrayInvalidSize();
template<class T> void tangentsAsArray();
template<class T> void tangentsAsArrayPackedSignedNormalized();
void tangentsIntoArrayInvalidSize();
template<class T> void bitangentSignsAsArray();
template<class T> void bitangentSignsAsArrayPackedSignedNormalized();
void bitangentSignsAsArrayNotFourComponent();
void bitangentSignsIntoArrayInvalidSize();
template<class T> void bitangentsAsArray();
template<class T> void bitangentsAsArrayPackedSignedNormalized();
void bitangentsIntoArrayInvalidSize();
template<class T> void normalsAsArray();
template<class T> void normalsAsArrayPackedSignedNormalized();
void normalsIntoArrayInvalidSize();
@ -302,6 +312,22 @@ MeshDataTest::MeshDataTest() {
&MeshDataTest::positions3DAsArrayPackedSignedNormalized<Vector3b>,
&MeshDataTest::positions3DAsArrayPackedSignedNormalized<Vector3s>,
&MeshDataTest::positions3DIntoArrayInvalidSize,
&MeshDataTest::tangentsAsArray<Vector3>,
&MeshDataTest::tangentsAsArray<Vector3h>,
&MeshDataTest::tangentsAsArrayPackedSignedNormalized<Vector3b>,
&MeshDataTest::tangentsAsArrayPackedSignedNormalized<Vector3s>,
&MeshDataTest::tangentsIntoArrayInvalidSize,
&MeshDataTest::bitangentSignsAsArray<Float>,
&MeshDataTest::bitangentSignsAsArray<Half>,
&MeshDataTest::bitangentSignsAsArrayPackedSignedNormalized<Byte>,
&MeshDataTest::bitangentSignsAsArrayPackedSignedNormalized<Short>,
&MeshDataTest::bitangentSignsAsArrayNotFourComponent,
&MeshDataTest::bitangentSignsIntoArrayInvalidSize,
&MeshDataTest::bitangentsAsArray<Vector3>,
&MeshDataTest::bitangentsAsArray<Vector3h>,
&MeshDataTest::bitangentsAsArrayPackedSignedNormalized<Vector3b>,
&MeshDataTest::bitangentsAsArrayPackedSignedNormalized<Vector3s>,
&MeshDataTest::bitangentsIntoArrayInvalidSize,
&MeshDataTest::normalsAsArray<Vector3>,
&MeshDataTest::normalsAsArray<Vector3h>,
&MeshDataTest::normalsAsArrayPackedSignedNormalized<Vector3b>,
@ -1946,6 +1972,158 @@ void MeshDataTest::positions3DIntoArrayInvalidSize() {
"Trade::MeshData::positions3DInto(): expected a view with 3 elements but got 2\n");
}
template<class T> void MeshDataTest::tangentsAsArray() {
setTestCaseTemplateName(NameTraits<T>::name());
typedef typename T::Type U;
Containers::Array<char> vertexData{3*sizeof(T)};
auto tangentView = Containers::arrayCast<T>(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<Vector3>({
{2.0f, 1.0f, 0.75f}, {0.0f, -1.0f, 1.25f}, {-2.0f, 3.0f, 2.5f},
}), TestSuite::Compare::Container);
}
template<class T> void MeshDataTest::tangentsAsArrayPackedSignedNormalized() {
setTestCaseTemplateName(NameTraits<T>::name());
Containers::Array<char> vertexData{2*sizeof(T)};
auto tangentsView = Containers::arrayCast<T>(vertexData);
tangentsView[0] = {Math::pack<typename T::Type>(1.0f), 0, Math::pack<typename T::Type>(1.0f)};
tangentsView[1] = {0, Math::pack<typename T::Type>(-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<T>()) + 1),
tangentsView}}};
CORRADE_COMPARE_AS(data.tangentsAsArray(), Containers::arrayView<Vector3>({
{1.0f, 0.0f, 1.0f}, {0.0f, -1.0f, 0.0f}
}), TestSuite::Compare::Container);
}
void MeshDataTest::tangentsIntoArrayInvalidSize() {
Containers::Array<char> vertexData{3*sizeof(Vector3)};
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Tangent, Containers::arrayCast<Vector3>(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<class T> void MeshDataTest::bitangentSignsAsArray() {
setTestCaseTemplateName(Math::TypeTraits<T>::name());
Containers::Array<char> vertexData{3*sizeof(Math::Vector4<T>)};
auto tangentView = Containers::arrayCast<Math::Vector4<T>>(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<Float>({
-1.0f, 1.0f, -1.0f
}), TestSuite::Compare::Container);
}
template<class T> void MeshDataTest::bitangentSignsAsArrayPackedSignedNormalized() {
setTestCaseTemplateName(Math::TypeTraits<T>::name());
Containers::Array<char> vertexData{2*sizeof(Math::Vector4<T>)};
auto bitangentsView = Containers::arrayCast<Math::Vector4<T>>(vertexData);
bitangentsView[0] = {Math::pack<T>(1.0f), 0, Math::pack<T>(1.0f), Math::pack<T>(-1.0f)};
bitangentsView[1] = {0, Math::pack<T>(-1.0f), 0, Math::pack<T>(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<Math::Vector4<T>>()) + 1),
bitangentsView}}};
CORRADE_COMPARE_AS(data.bitangentSignsAsArray(), Containers::arrayView<Float>({
-1.0f, 1.0f
}), TestSuite::Compare::Container);
}
void MeshDataTest::bitangentSignsAsArrayNotFourComponent() {
Containers::Array<char> vertexData{3*sizeof(Vector3s)};
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Tangent, VertexFormat::Vector3sNormalized, Containers::arrayCast<Vector3s>(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<char> vertexData{3*sizeof(Vector4)};
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Tangent, Containers::arrayCast<Vector4>(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<class T> void MeshDataTest::bitangentsAsArray() {
setTestCaseTemplateName(NameTraits<T>::name());
typedef typename T::Type U;
Containers::Array<char> vertexData{3*sizeof(T)};
auto bitangentsView = Containers::arrayCast<T>(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<Vector3>({
{2.0f, 1.0f, 0.75f}, {0.0f, -1.0f, 1.25f}, {-2.0f, 3.0f, 2.5f},
}), TestSuite::Compare::Container);
}
template<class T> void MeshDataTest::bitangentsAsArrayPackedSignedNormalized() {
setTestCaseTemplateName(NameTraits<T>::name());
Containers::Array<char> vertexData{2*sizeof(T)};
auto bitangentsView = Containers::arrayCast<T>(vertexData);
bitangentsView[0] = {Math::pack<typename T::Type>(1.0f), 0, Math::pack<typename T::Type>(1.0f)};
bitangentsView[1] = {0, Math::pack<typename T::Type>(-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<T>()) + 1),
bitangentsView}}};
CORRADE_COMPARE_AS(data.bitangentsAsArray(), Containers::arrayView<Vector3>({
{1.0f, 0.0f, 1.0f}, {0.0f, -1.0f, 0.0f}
}), TestSuite::Compare::Container);
}
void MeshDataTest::bitangentsIntoArrayInvalidSize() {
Containers::Array<char> vertexData{3*sizeof(Vector3)};
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Bitangent, Containers::arrayCast<Vector3>(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<class T> void MeshDataTest::normalsAsArray() {
setTestCaseTemplateName(NameTraits<T>::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<Float>(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<Vector2>(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");

Loading…
Cancel
Save