Skip to content

Commit

Permalink
Trade: Implement support for Weights & JointIndices in MeshData.
Browse files Browse the repository at this point in the history
Signed-off-by: Squareys <[email protected]>
  • Loading branch information
Squareys committed May 11, 2020
1 parent 52af410 commit 12dd74a
Show file tree
Hide file tree
Showing 3 changed files with 239 additions and 4 deletions.
54 changes: 54 additions & 0 deletions src/Magnum/Trade/MeshData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,58 @@ Containers::Array<UnsignedInt> MeshData::objectIdsAsArray(const UnsignedInt id)
return out;
}

void MeshData::weightsInto(const Containers::StridedArrayView1D<Vector4> 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<void*>(vertexFormatUnwrap(attribute._format)), );
const Containers::StridedArrayView1D<const void> attributeData = attributeDataViewInternal(attribute);
const Containers::StridedArrayView2D<Float> destination4f = Containers::arrayCast<2, Float>(destination);

if(attribute._format == VertexFormat::Vector4)
Utility::copy(Containers::arrayCast<const Vector4>(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<Vector4> MeshData::weightsAsArray(const UnsignedInt id) const {
Containers::Array<Vector4> out{_vertexCount};
weightsInto(out, id);
return out;
}

void MeshData::jointIdsInto(const Containers::StridedArrayView1D<Vector4ui> 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<void*>(vertexFormatUnwrap(attribute._format)), );
const Containers::StridedArrayView1D<const void> attributeData = attributeDataViewInternal(attribute);
const Containers::StridedArrayView2D<UnsignedInt> destination4ui = Containers::arrayCast<2, UnsignedInt>(destination);

if(attribute._format == VertexFormat::Vector4ui)
Utility::copy(Containers::arrayCast<const Vector4ui>(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<Vector4ui> MeshData::jointIdsAsArray(const UnsignedInt id) const {
Containers::Array<Vector4ui> out{_vertexCount};
jointIdsInto(out, id);
return out;
}

Containers::Array<char> MeshData::releaseIndexData() {
_indexCount = 0;
Containers::Array<char> out = std::move(_indexData);
Expand Down Expand Up @@ -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 */

Expand Down
83 changes: 79 additions & 4 deletions src/Magnum/Trade/MeshData.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -1717,6 +1735,54 @@ class MAGNUM_TRADE_EXPORT MeshData {
*/
void objectIdsInto(Containers::StridedArrayView1D<UnsignedInt> 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<Vector4> 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<Vector4> 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<Vector4ui> 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<Vector4ui> destination, UnsignedInt id = 0) const;

/**
* @brief Release index data storage
*
Expand Down Expand Up @@ -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);
}
Expand Down
106 changes: 106 additions & 0 deletions src/Magnum/Trade/Test/MeshDataTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,14 @@ struct MeshDataTest: TestSuite::Tester {
template<class T> void objectIdsAsArray();
void objectIdsIntoArrayInvalidSize();

template<class T> void weightsAsArray();
template<class T> void weightsAsArrayPackedUnsignedNormalized();
template<class T> void weightsAsArrayPackedSignedNormalized();
void weightsIntoArrayInvalidSize();

template<class T> void jointIdsAsArray();
void jointIdsIntoArrayInvalidSize();

void implementationSpecificVertexFormat();
void implementationSpecificVertexFormatWrongAccess();
void implementationSpecificVertexFormatNotContained();
Expand Down Expand Up @@ -364,6 +372,17 @@ MeshDataTest::MeshDataTest() {
&MeshDataTest::objectIdsAsArray<UnsignedInt>,
&MeshDataTest::objectIdsIntoArrayInvalidSize,

&MeshDataTest::weightsAsArray<Vector4>,
&MeshDataTest::weightsAsArray<Vector4h>,
&MeshDataTest::weightsAsArrayPackedUnsignedNormalized<Vector4ub>,
&MeshDataTest::weightsAsArrayPackedUnsignedNormalized<Vector4us>,
&MeshDataTest::weightsIntoArrayInvalidSize,

&MeshDataTest::jointIdsAsArray<Vector4ui>,
&MeshDataTest::jointIdsAsArray<Vector4us>,
&MeshDataTest::jointIdsAsArray<Vector4ub>,
&MeshDataTest::jointIdsIntoArrayInvalidSize,

&MeshDataTest::implementationSpecificVertexFormat,
&MeshDataTest::implementationSpecificVertexFormatWrongAccess,
&MeshDataTest::implementationSpecificVertexFormatNotContained,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -2500,6 +2522,90 @@ void MeshDataTest::objectIdsIntoArrayInvalidSize() {
"Trade::MeshData::objectIdsInto(): expected a view with 3 elements but got 2\n");
}

template<class T> void MeshDataTest::weightsAsArray() {
setTestCaseTemplateName(NameTraits<T>::name());
typedef typename T::Type U;

Containers::Array<char> vertexData{3*sizeof(T)};
auto weightsView = Containers::arrayCast<T>(vertexData);
/* Needs to be sufficiently representable to have the test work also for
half floats */
weightsView[0] = T::pad(Math::Vector4<U>{U(2.0f), U(1.0f), U(0.75f), U(3.0f)});
weightsView[1] = T::pad(Math::Vector4<U>{U(0.0f), U(-1.0f), U(1.25f), U(1.0f)});
weightsView[2] = T::pad(Math::Vector4<U>{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<Vector4>({
{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<class T> void MeshDataTest::weightsAsArrayPackedUnsignedNormalized() {
setTestCaseTemplateName(NameTraits<T>::name());

Containers::Array<char> vertexData{2*sizeof(T)};
auto weightsView = Containers::arrayCast<T>(vertexData);
weightsView[0] = T::pad(Math::Vector4<typename T::Type>{Math::pack<typename T::Type>(1.0f), 0, Math::pack<typename T::Type>(1.0f), Math::pack<typename T::Type>(0.8)});
weightsView[1] = T::pad(Math::Vector4<typename T::Type>{0, Math::pack<typename T::Type>(1.0f), 0, Math::pack<typename T::Type>(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<T>()) + 1),
weightsView}}};
CORRADE_COMPARE_AS(data.weightsAsArray(), Containers::arrayView<Vector4>({
Vector4::pad(Math::Vector<T::Size, Float>::pad(Vector4{1.0f, 0.0f, 1.0f, 0.8})),
Vector4::pad(Math::Vector<T::Size, Float>::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<char> vertexData{3*sizeof(Vector4)};
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::Weights, Containers::arrayCast<Vector4>(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<class T> void MeshDataTest::jointIdsAsArray() {
setTestCaseTemplateName(NameTraits<T>::name());
typedef typename T::Type U;

Containers::Array<char> vertexData{3*sizeof(T)};
auto joinIdsView = Containers::arrayCast<T>(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<Vector4ui>({
{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<char> vertexData{3*sizeof(Vector4ui)};
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::JointIds, Containers::arrayCast<Vector4ui>(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 {
Expand Down

0 comments on commit 12dd74a

Please sign in to comment.