Skip to content

Commit

Permalink
Trade: implement support for object IDs in MeshData.
Browse files Browse the repository at this point in the history
  • Loading branch information
mosra committed Mar 16, 2020
1 parent 5e02e0c commit 8a2a7e4
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 11 deletions.
26 changes: 26 additions & 0 deletions src/Magnum/Trade/MeshData.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -747,6 +747,31 @@ Containers::Array<Color4> MeshData::colorsAsArray(const UnsignedInt id) const {
return out;
}

void MeshData::objectIdsInto(const Containers::StridedArrayView1D<UnsignedInt> destination, const UnsignedInt id) const {
const UnsignedInt attributeId = attributeFor(MeshAttribute::ObjectId, id);
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];
CORRADE_ASSERT(!isVertexFormatImplementationSpecific(attribute._format),
"Trade::MeshData::objectIdsInto(): 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 destination1ui = Containers::arrayCast<2, UnsignedInt>(destination);

if(attribute._format == VertexFormat::UnsignedInt)
Utility::copy(Containers::arrayCast<const UnsignedInt>(attributeData), destination);
else if(attribute._format == VertexFormat::UnsignedShort)
Math::castInto(Containers::arrayCast<2, const UnsignedShort>(attributeData, 1), destination1ui);
else if(attribute._format == VertexFormat::UnsignedByte)
Math::castInto(Containers::arrayCast<2, const UnsignedByte>(attributeData, 1), destination1ui);
else CORRADE_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */
}

Containers::Array<UnsignedInt> MeshData::objectIdsAsArray(const UnsignedInt id) const {
Containers::Array<UnsignedInt> out{_vertexCount};
objectIdsInto(out, id);
return out;
}

Containers::Array<char> MeshData::releaseIndexData() {
_indices = {_indices.data(), 0};
Containers::Array<char> out = std::move(_indexData);
Expand Down Expand Up @@ -780,6 +805,7 @@ Debug& operator<<(Debug& debug, const MeshAttribute value) {
_c(Normal)
_c(TextureCoordinates)
_c(Color)
_c(ObjectId)
#undef _c
/* LCOV_EXCL_STOP */

Expand Down
54 changes: 45 additions & 9 deletions src/Magnum/Trade/MeshData.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,14 @@ enum class MeshAttribute: UnsignedShort {
*/
Color,

/**
* (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.
* @see @ref MeshData::objectIdsAsArray()
*/
ObjectId,

/**
* 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 @@ -594,11 +602,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()
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
@ref attributeCount(MeshAttribute) const:
@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:
@snippet MagnumTrade.cpp MeshData-usage
Expand Down Expand Up @@ -1326,10 +1334,10 @@ class MAGNUM_TRADE_EXPORT MeshData {
* @ref positions2DAsArray(), @ref positions3DAsArray(),
* @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.
* @ref textureCoordinates2DAsArray(), @ref colorsAsArray() and
* @ref objectIdsAsArray() 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(),
Expand Down Expand Up @@ -1675,6 +1683,30 @@ class MAGNUM_TRADE_EXPORT MeshData {
*/
void colorsInto(Containers::StridedArrayView1D<Color4> destination, UnsignedInt id = 0) const;

/**
* @brief Object IDs as 32-bit integers
*
* Convenience alternative to @ref attribute(MeshAttribute, UnsignedInt) 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.
* @see @ref objectIdsInto(), @ref attributeFormat(),
* @ref isVertexFormatImplementationSpecific()
*/
Containers::Array<UnsignedInt> objectIdsAsArray(UnsignedInt id = 0) const;

/**
* @brief Object IDs as 32-bit integers into a pre-allocated view
*
* Like @ref objectIdsAsArray(), 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 objectIdsInto(Containers::StridedArrayView1D<UnsignedInt> destination, UnsignedInt id = 0) const;

/**
* @brief Release index data storage
*
Expand Down Expand Up @@ -2014,6 +2046,10 @@ namespace Implementation {
format == VertexFormat::Vector2usNormalized ||
format == VertexFormat::Vector2s ||
format == VertexFormat::Vector2sNormalized)) ||
(name == MeshAttribute::ObjectId &&
(format == VertexFormat::UnsignedInt ||
format == VertexFormat::UnsignedShort ||
format == VertexFormat::UnsignedByte)) ||
/* Custom attributes can be anything */
isMeshAttributeCustom(name);
}
Expand Down
45 changes: 43 additions & 2 deletions src/Magnum/Trade/Test/MeshDataTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ struct MeshDataTest: TestSuite::Tester {
template<class T> void colorsAsArray();
template<class T> void colorsAsArrayPackedUnsignedNormalized();
void colorsIntoArrayInvalidSize();
template<class T> void objectIdsAsArray();
void objectIdsIntoArrayInvalidSize();

void implementationSpecificVertexFormat();
void implementationSpecificVertexFormatWrongAccess();
Expand Down Expand Up @@ -353,6 +355,10 @@ MeshDataTest::MeshDataTest() {
&MeshDataTest::colorsAsArrayPackedUnsignedNormalized<Color4ub>,
&MeshDataTest::colorsAsArrayPackedUnsignedNormalized<Color4us>,
&MeshDataTest::colorsIntoArrayInvalidSize,
&MeshDataTest::objectIdsAsArray<UnsignedByte>,
&MeshDataTest::objectIdsAsArray<UnsignedShort>,
&MeshDataTest::objectIdsAsArray<UnsignedInt>,
&MeshDataTest::objectIdsIntoArrayInvalidSize,

&MeshDataTest::implementationSpecificVertexFormat,
&MeshDataTest::implementationSpecificVertexFormatWrongAccess,
Expand Down Expand Up @@ -2308,6 +2314,35 @@ void MeshDataTest::colorsIntoArrayInvalidSize() {
"Trade::MeshData::colorsInto(): expected a view with 3 elements but got 2\n");
}

template<class T> void MeshDataTest::objectIdsAsArray() {
setTestCaseTemplateName(Math::TypeTraits<T>::name());

Containers::Array<char> vertexData{3*sizeof(T)};
auto objectIdsView = Containers::arrayCast<T>(vertexData);
/* Can't use e.g. 0xff3366_rgbf because that's not representable in
half-floats */
objectIdsView[0] = {157};
objectIdsView[1] = {24};
objectIdsView[2] = {1};

MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::ObjectId, objectIdsView}}};
CORRADE_COMPARE_AS(data.objectIdsAsArray(), Containers::arrayView<UnsignedInt>({
157, 24, 1
}), TestSuite::Compare::Container);
}

void MeshDataTest::objectIdsIntoArrayInvalidSize() {
Containers::Array<char> vertexData{3*sizeof(UnsignedInt)};
MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::ObjectId, Containers::arrayCast<UnsignedInt>(vertexData)}}};

std::ostringstream out;
Error redirectError{&out};
UnsignedInt destination[2];
data.objectIdsInto(destination);
CORRADE_COMPARE(out.str(),
"Trade::MeshData::objectIdsInto(): 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 Expand Up @@ -2375,6 +2410,8 @@ void MeshDataTest::implementationSpecificVertexFormatWrongAccess() {
MeshAttributeData{MeshAttribute::TextureCoordinates,
vertexFormatWrap(0xdead3), attribute},
MeshAttributeData{MeshAttribute::Color,
vertexFormatWrap(0xdead4), attribute},
MeshAttributeData{MeshAttribute::ObjectId,
vertexFormatWrap(0xdead4), attribute}}};

std::ostringstream out;
Expand All @@ -2395,6 +2432,7 @@ void MeshDataTest::implementationSpecificVertexFormatWrongAccess() {
data.normalsAsArray();
data.textureCoordinates2DAsArray();
data.colorsAsArray();
data.objectIdsAsArray();
CORRADE_COMPARE(out.str(),
"Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead1\n"
"Trade::MeshData::attribute(): can't cast data from an implementation-specific vertex format 0xdead2\n"
Expand All @@ -2411,7 +2449,8 @@ void MeshDataTest::implementationSpecificVertexFormatWrongAccess() {
"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");
"Trade::MeshData::colorsInto(): can't extract data out of an implementation-specific vertex format 0xdead4\n"
"Trade::MeshData::objectIdsInto(): can't extract data out of an implementation-specific vertex format 0xdead4\n");
}

void MeshDataTest::implementationSpecificVertexFormatNotContained() {
Expand Down Expand Up @@ -2624,6 +2663,7 @@ void MeshDataTest::attributeNotFound() {
data.normalsAsArray();
data.textureCoordinates2DAsArray();
data.colorsAsArray(2);
data.objectIdsAsArray();
CORRADE_COMPARE(out.str(),
"Trade::MeshData::attributeName(): index 2 out of range for 2 attributes\n"
"Trade::MeshData::attributeFormat(): index 2 out of range for 2 attributes\n"
Expand Down Expand Up @@ -2653,7 +2693,8 @@ void MeshDataTest::attributeNotFound() {
"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");
"Trade::MeshData::colorsInto(): index 2 out of range for 2 color attributes\n"
"Trade::MeshData::objectIdsInto(): index 0 out of range for 0 object ID attributes\n");
}

void MeshDataTest::attributeWrongType() {
Expand Down

0 comments on commit 8a2a7e4

Please sign in to comment.