From 8a2a7e4547832eff0aa8f16efec208707559a7b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Mon, 16 Mar 2020 19:36:08 +0100 Subject: [PATCH] Trade: implement support for object IDs in MeshData. --- src/Magnum/Trade/MeshData.cpp | 26 +++++++++++++ src/Magnum/Trade/MeshData.h | 54 +++++++++++++++++++++----- src/Magnum/Trade/Test/MeshDataTest.cpp | 45 ++++++++++++++++++++- 3 files changed, 114 insertions(+), 11 deletions(-) diff --git a/src/Magnum/Trade/MeshData.cpp b/src/Magnum/Trade/MeshData.cpp index 254629a30d..22f164978e 100644 --- a/src/Magnum/Trade/MeshData.cpp +++ b/src/Magnum/Trade/MeshData.cpp @@ -747,6 +747,31 @@ Containers::Array MeshData::colorsAsArray(const UnsignedInt id) const { return out; } +void MeshData::objectIdsInto(const Containers::StridedArrayView1D 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(vertexFormatUnwrap(attribute._format)), ); + const Containers::StridedArrayView1D attributeData = attributeDataViewInternal(attribute); + const auto destination1ui = Containers::arrayCast<2, UnsignedInt>(destination); + + if(attribute._format == VertexFormat::UnsignedInt) + Utility::copy(Containers::arrayCast(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 MeshData::objectIdsAsArray(const UnsignedInt id) const { + Containers::Array out{_vertexCount}; + objectIdsInto(out, id); + return out; +} + Containers::Array MeshData::releaseIndexData() { _indices = {_indices.data(), 0}; Containers::Array out = std::move(_indexData); @@ -780,6 +805,7 @@ Debug& operator<<(Debug& debug, const MeshAttribute value) { _c(Normal) _c(TextureCoordinates) _c(Color) + _c(ObjectId) #undef _c /* LCOV_EXCL_STOP */ diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index c924266292..385a44c19b 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -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. @@ -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 @@ -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(), @@ -1675,6 +1683,30 @@ class MAGNUM_TRADE_EXPORT MeshData { */ void colorsInto(Containers::StridedArrayView1D 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 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 destination, UnsignedInt id = 0) const; + /** * @brief Release index data storage * @@ -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); } diff --git a/src/Magnum/Trade/Test/MeshDataTest.cpp b/src/Magnum/Trade/Test/MeshDataTest.cpp index 8dfd8f7ed9..278c562fce 100644 --- a/src/Magnum/Trade/Test/MeshDataTest.cpp +++ b/src/Magnum/Trade/Test/MeshDataTest.cpp @@ -143,6 +143,8 @@ struct MeshDataTest: TestSuite::Tester { template void colorsAsArray(); template void colorsAsArrayPackedUnsignedNormalized(); void colorsIntoArrayInvalidSize(); + template void objectIdsAsArray(); + void objectIdsIntoArrayInvalidSize(); void implementationSpecificVertexFormat(); void implementationSpecificVertexFormatWrongAccess(); @@ -353,6 +355,10 @@ MeshDataTest::MeshDataTest() { &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, &MeshDataTest::colorsAsArrayPackedUnsignedNormalized, &MeshDataTest::colorsIntoArrayInvalidSize, + &MeshDataTest::objectIdsAsArray, + &MeshDataTest::objectIdsAsArray, + &MeshDataTest::objectIdsAsArray, + &MeshDataTest::objectIdsIntoArrayInvalidSize, &MeshDataTest::implementationSpecificVertexFormat, &MeshDataTest::implementationSpecificVertexFormatWrongAccess, @@ -2308,6 +2314,35 @@ void MeshDataTest::colorsIntoArrayInvalidSize() { "Trade::MeshData::colorsInto(): expected a view with 3 elements but got 2\n"); } +template void MeshDataTest::objectIdsAsArray() { + setTestCaseTemplateName(Math::TypeTraits::name()); + + Containers::Array vertexData{3*sizeof(T)}; + auto objectIdsView = Containers::arrayCast(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({ + 157, 24, 1 + }), TestSuite::Compare::Container); +} + +void MeshDataTest::objectIdsIntoArrayInvalidSize() { + Containers::Array vertexData{3*sizeof(UnsignedInt)}; + MeshData data{MeshPrimitive::Points, std::move(vertexData), {MeshAttributeData{MeshAttribute::ObjectId, Containers::arrayCast(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 { @@ -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; @@ -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" @@ -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() { @@ -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" @@ -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() {