From be36c8a0715eb6dd5a02174412ce218110b58c6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sat, 18 Sep 2021 11:57:38 +0200 Subject: [PATCH] Trade: convenience access to separate TRS components in SceneData. Because just the combined transformation may not be enough for handling animations. For example. --- src/Magnum/Trade/SceneData.cpp | 276 ++++++++++++++- src/Magnum/Trade/SceneData.h | 126 ++++++- src/Magnum/Trade/Test/SceneDataTest.cpp | 449 +++++++++++++++++++++++- 3 files changed, 821 insertions(+), 30 deletions(-) diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 03965be46..09d014748 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -26,8 +26,10 @@ #include "SceneData.h" #include +#include #include +#include "Magnum/Math/FunctionsBatch.h" #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" #include "Magnum/Math/DualComplex.h" @@ -863,6 +865,26 @@ std::size_t SceneData::findTransformFields(UnsignedInt& transformationFieldId, U ~std::size_t{} : _fields[fieldToCheckForSize]._size; } +std::size_t SceneData::findTranslationRotationScalingFields(UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId) const { + UnsignedInt fieldToCheckForSize = ~UnsignedInt{}; + translationFieldId = ~UnsignedInt{}; + rotationFieldId = ~UnsignedInt{}; + scalingFieldId = ~UnsignedInt{}; + for(std::size_t i = 0; i != _fields.size(); ++i) { + if(_fields[i]._name == SceneField::Translation) { + fieldToCheckForSize = translationFieldId = i; + } else if(_fields[i]._name == SceneField::Rotation) { + fieldToCheckForSize = rotationFieldId = i; + } else if(_fields[i]._name == SceneField::Scaling) { + fieldToCheckForSize = scalingFieldId = i; + } + } + + /* Assuming the caller fires an appropriate assertion */ + return fieldToCheckForSize == ~UnsignedInt{} ? + ~std::size_t{} : _fields[fieldToCheckForSize]._size; +} + void SceneData::transformations2DIntoInternal(const UnsignedInt transformationFieldId, const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const { /* *FieldId, offset and destination.size() is assumed to be in bounds (or an invalid field ID), checked by the callers */ @@ -960,10 +982,7 @@ void SceneData::transformations2DInto(const Containers::StridedArrayView1D& destination) const { UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; - #ifndef CORRADE_NO_ASSERT - const std::size_t expectedSize = - #endif - findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + const std::size_t expectedSize = findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); CORRADE_ASSERT(expectedSize != ~std::size_t{}, "Trade::SceneData::transformations2DInto(): no transformation-related field found", {}); CORRADE_ASSERT(offset <= expectedSize, @@ -985,6 +1004,128 @@ Containers::Array SceneData::transformations2DAsArray() const { return out; } +void SceneData::translationsRotationsScalings2DIntoInternal(const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, const std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const { + /* *FieldId, offset and *Destination.size() is assumed to be in bounds (or + an invalid field ID), checked by the callers */ + + /* Retrieve translation, if desired. If no field is present, output a zero + vector for all objects. */ + if(translationDestination) { + if(translationFieldId == ~UnsignedInt{}) { + constexpr Vector2 identity[]{Vector2{0.0f}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(translationDestination.size()), translationDestination); + } else { + const SceneFieldData& field = _fields[translationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, translationDestination.size()); + + if(field._fieldType == SceneFieldType::Vector2) { + Utility::copy(Containers::arrayCast(fieldData), translationDestination); + } else if(field._fieldType == SceneFieldType::Vector2d) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 2), Containers::arrayCast<2, Float>(translationDestination)); + } else if(field._fieldType == SceneFieldType::Vector3 || + field._fieldType == SceneFieldType::Vector3d) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::translationsRotationsScalings2DInto(): field has a 3D translation type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } + + /* Retrieve rotation, if desired. If no field is present, output an + identity rotation for all objects. */ + if(rotationDestination) { + if(rotationFieldId == ~UnsignedInt{}) { + constexpr Complex identity[]{Complex{Math::IdentityInit}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(rotationDestination.size()), rotationDestination); + } else { + const SceneFieldData& field = _fields[rotationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, rotationDestination.size()); + + if(field._fieldType == SceneFieldType::Complex) { + Utility::copy(Containers::arrayCast(fieldData), rotationDestination); + } else if(field._fieldType == SceneFieldType::Complexd) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 2), Containers::arrayCast<2, Float>(rotationDestination)); + } else if(field._fieldType == SceneFieldType::Quaternion || + field._fieldType == SceneFieldType::Quaterniond) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::translationsRotationsScalings2DInto(): field has a 3D rotation type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } + + /* Retrieve scaling, if desired. If no field is present, output an identity + scaling for all objects. */ + if(scalingDestination) { + if(scalingFieldId == ~UnsignedInt{}) { + constexpr Vector2 identity[]{Vector2{1.0f}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(scalingDestination.size()), scalingDestination); + } else { + const SceneFieldData& field = _fields[scalingFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, scalingDestination.size()); + + if(field._fieldType == SceneFieldType::Vector2) { + Utility::copy(Containers::arrayCast(fieldData), scalingDestination); + } else if(field._fieldType == SceneFieldType::Vector2d) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 2), Containers::arrayCast<2, Float>(scalingDestination)); + } else if(field._fieldType == SceneFieldType::Vector3 || + field._fieldType == SceneFieldType::Vector3d) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::translationsRotationsScalings2DInto(): field has a 3D scaling type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } +} + +void SceneData::translationsRotationsScalings2DInto(const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const { + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + #ifndef CORRADE_NO_ASSERT + const std::size_t expectedSize = + #endif + findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(expectedSize != ~std::size_t{}, + "Trade::SceneData::translationsRotationsScalings2DInto(): no transformation-related field found", ); + CORRADE_ASSERT(!translationDestination || translationDestination.size() == expectedSize, + "Trade::SceneData::translationsRotationsScalings2DInto(): expected translation destination view either empty or with" << expectedSize << "elements but got" << translationDestination.size(), ); + CORRADE_ASSERT(!rotationDestination || rotationDestination.size() == expectedSize, + "Trade::SceneData::translationsRotationsScalings2DInto(): expected rotation destination view either empty or with" << expectedSize << "elements but got" << rotationDestination.size(), ); + CORRADE_ASSERT(!scalingDestination || scalingDestination.size() == expectedSize, + "Trade::SceneData::translationsRotationsScalings2DInto(): expected scaling destination view either empty or with" << expectedSize << "elements but got" << scalingDestination.size(), ); + translationsRotationsScalings2DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, 0, translationDestination, rotationDestination, scalingDestination); +} + +std::size_t SceneData::translationsRotationsScalings2DInto(const std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const { + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + const std::size_t expectedSize = findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(expectedSize != ~std::size_t{}, + "Trade::SceneData::translationsRotationsScalings2DInto(): no transformation-related field found", {}); + CORRADE_ASSERT(offset <= expectedSize, + "Trade::SceneData::translationsRotationsScalings2DInto(): offset" << offset << "out of bounds for a field of size" << expectedSize, {}); + CORRADE_ASSERT(!translationDestination != !rotationDestination || translationDestination.size() == rotationDestination.size(), + "Trade::SceneData::translationsRotationsScalings2DInto(): translation and rotation destination views have different size," << translationDestination.size() << "vs" << rotationDestination.size(), {}); + CORRADE_ASSERT(!translationDestination != !scalingDestination || translationDestination.size() == scalingDestination.size(), + "Trade::SceneData::translationsRotationsScalings2DInto(): translation and scaling destination views have different size," << translationDestination.size() << "vs" << scalingDestination.size(), {}); + CORRADE_ASSERT(!rotationDestination != !scalingDestination || rotationDestination.size() == scalingDestination.size(), + "Trade::SceneData::translationsRotationsScalings2DInto(): rotation and scaling destination views have different size," << rotationDestination.size() << "vs" << scalingDestination.size(), {}); + const std::size_t size = Math::min(Math::max({translationDestination.size(), rotationDestination.size(), scalingDestination.size()}), expectedSize - offset); + translationsRotationsScalings2DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, offset, + translationDestination ? translationDestination.prefix(size) : nullptr, + rotationDestination ? rotationDestination.prefix(size) : nullptr, + scalingDestination ? scalingDestination.prefix(size) : nullptr); + return size; +} + +Containers::Array> SceneData::translationsRotationsScalings2DAsArray() const { + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + const std::size_t expectedSize = findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(expectedSize != ~std::size_t{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::translationsRotationsScalings2DInto(): no transformation-related field found", {}); + Containers::Array> out{NoInit, expectedSize}; + /** @todo use slicing once Triple exposes members somehow */ + const Containers::StridedArrayView1D translationsOut{out, reinterpret_cast(reinterpret_cast(out.data())), out.size(), sizeof(decltype(out)::Type)}; + const Containers::StridedArrayView1D rotationsOut{out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(Vector2)), out.size(), sizeof(decltype(out)::Type)}; + const Containers::StridedArrayView1D scalingsOut{out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(Vector2) + sizeof(Complex)), out.size(), sizeof(decltype(out)::Type)}; + translationsRotationsScalings2DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, 0, translationsOut, rotationsOut, scalingsOut); + return out; +} + void SceneData::transformations3DIntoInternal(const UnsignedInt transformationFieldId, const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, const std::size_t offset, const Containers::StridedArrayView1D& destination) const { /* *FieldId, offset and destination.size() is assumed to be in bounds (or an invalid field ID), checked by the callers */ @@ -1082,10 +1223,7 @@ void SceneData::transformations3DInto(const Containers::StridedArrayView1D& destination) const { UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; - #ifndef CORRADE_NO_ASSERT - const std::size_t expectedSize = - #endif - findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + const std::size_t expectedSize = findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); CORRADE_ASSERT(expectedSize != ~std::size_t{}, "Trade::SceneData::transformations3DInto(): no transformation-related field found", {}); CORRADE_ASSERT(offset <= expectedSize, @@ -1107,6 +1245,128 @@ Containers::Array SceneData::transformations3DAsArray() const { return out; } +void SceneData::translationsRotationsScalings3DIntoInternal(const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, const std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const { + /* *FieldId, offset and *Destination.size() is assumed to be in bounds (or + an invalid field ID), checked by the callers */ + + /* Retrieve translation, if desired. If no field is present, output a zero + vector for all objects. */ + if(translationDestination) { + if(translationFieldId == ~UnsignedInt{}) { + constexpr Vector3 identity[]{Vector3{0.0f}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(translationDestination.size()), translationDestination); + } else { + const SceneFieldData& field = _fields[translationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, translationDestination.size()); + + if(field._fieldType == SceneFieldType::Vector3) { + Utility::copy(Containers::arrayCast(fieldData), translationDestination); + } else if(field._fieldType == SceneFieldType::Vector3d) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 3), Containers::arrayCast<2, Float>(translationDestination)); + } else if(field._fieldType == SceneFieldType::Vector2 || + field._fieldType == SceneFieldType::Vector2d) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::translationsRotationsScalings3DInto(): field has a 2D translation type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } + + /* Retrieve rotation, if desired. If no field is present, output an + identity rotation for all objects. */ + if(rotationDestination) { + if(rotationFieldId == ~UnsignedInt{}) { + constexpr Quaternion identity[]{Quaternion{Math::IdentityInit}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(rotationDestination.size()), rotationDestination); + } else { + const SceneFieldData& field = _fields[rotationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, rotationDestination.size()); + + if(field._fieldType == SceneFieldType::Quaternion) { + Utility::copy(Containers::arrayCast(fieldData), rotationDestination); + } else if(field._fieldType == SceneFieldType::Quaterniond) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 4), Containers::arrayCast<2, Float>(rotationDestination)); + } else if(field._fieldType == SceneFieldType::Complex || + field._fieldType == SceneFieldType::Complexd) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::translationsRotationsScalings3DInto(): field has a 2D rotation type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } + + /* Retrieve scaling, if desired. If no field is present, output an identity + scaling for all objects. */ + if(scalingDestination) { + if(scalingFieldId == ~UnsignedInt{}) { + constexpr Vector3 identity[]{Vector3{1.0f}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(scalingDestination.size()), scalingDestination); + } else { + const SceneFieldData& field = _fields[scalingFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field, offset, scalingDestination.size()); + + if(field._fieldType == SceneFieldType::Vector3) { + Utility::copy(Containers::arrayCast(fieldData), scalingDestination); + } else if(field._fieldType == SceneFieldType::Vector3d) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 3), Containers::arrayCast<2, Float>(scalingDestination)); + } else if(field._fieldType == SceneFieldType::Vector2 || + field._fieldType == SceneFieldType::Vector2d) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::translationsRotationsScalings3DInto(): field has a 2D scaling type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + } +} + +void SceneData::translationsRotationsScalings3DInto(const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const { + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + #ifndef CORRADE_NO_ASSERT + const std::size_t expectedSize = + #endif + findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(expectedSize != ~std::size_t{}, + "Trade::SceneData::translationsRotationsScalings3DInto(): no transformation-related field found", ); + CORRADE_ASSERT(!translationDestination || translationDestination.size() == expectedSize, + "Trade::SceneData::translationsRotationsScalings3DInto(): expected translation destination view either empty or with" << expectedSize << "elements but got" << translationDestination.size(), ); + CORRADE_ASSERT(!rotationDestination || rotationDestination.size() == expectedSize, + "Trade::SceneData::translationsRotationsScalings3DInto(): expected rotation destination view either empty or with" << expectedSize << "elements but got" << rotationDestination.size(), ); + CORRADE_ASSERT(!scalingDestination || scalingDestination.size() == expectedSize, + "Trade::SceneData::translationsRotationsScalings3DInto(): expected scaling destination view either empty or with" << expectedSize << "elements but got" << scalingDestination.size(), ); + translationsRotationsScalings3DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, 0, translationDestination, rotationDestination, scalingDestination); +} + +std::size_t SceneData::translationsRotationsScalings3DInto(const std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const { + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + const std::size_t expectedSize = findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(expectedSize != ~std::size_t{}, + "Trade::SceneData::translationsRotationsScalings3DInto(): no transformation-related field found", {}); + CORRADE_ASSERT(offset <= expectedSize, + "Trade::SceneData::translationsRotationsScalings3DInto(): offset" << offset << "out of bounds for a field of size" << expectedSize, {}); + CORRADE_ASSERT(!translationDestination != !rotationDestination || translationDestination.size() == rotationDestination.size(), + "Trade::SceneData::translationsRotationsScalings3DInto(): translation and rotation destination views have different size," << translationDestination.size() << "vs" << rotationDestination.size(), {}); + CORRADE_ASSERT(!translationDestination != !scalingDestination || translationDestination.size() == scalingDestination.size(), + "Trade::SceneData::translationsRotationsScalings3DInto(): translation and scaling destination views have different size," << translationDestination.size() << "vs" << scalingDestination.size(), {}); + CORRADE_ASSERT(!rotationDestination != !scalingDestination || rotationDestination.size() == scalingDestination.size(), + "Trade::SceneData::translationsRotationsScalings3DInto(): rotation and scaling destination views have different size," << rotationDestination.size() << "vs" << scalingDestination.size(), {}); + const std::size_t size = Math::min(Math::max({translationDestination.size(), rotationDestination.size(), scalingDestination.size()}), expectedSize - offset); + translationsRotationsScalings3DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, offset, + translationDestination ? translationDestination.prefix(size) : nullptr, + rotationDestination ? rotationDestination.prefix(size) : nullptr, + scalingDestination ? scalingDestination.prefix(size) : nullptr); + return size; +} + +Containers::Array> SceneData::translationsRotationsScalings3DAsArray() const { + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId; + const std::size_t expectedSize = findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(expectedSize != ~std::size_t{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::translationsRotationsScalings3DInto(): no transformation-related field found", {}); + Containers::Array> out{NoInit, expectedSize}; + /** @todo use slicing once Triple exposes members somehow */ + const Containers::StridedArrayView1D translationsOut{out, reinterpret_cast(reinterpret_cast(out.data())), out.size(), sizeof(decltype(out)::Type)}; + const Containers::StridedArrayView1D rotationsOut{out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(Vector3)), out.size(), sizeof(decltype(out)::Type)}; + const Containers::StridedArrayView1D scalingsOut{out, reinterpret_cast(reinterpret_cast(out.data()) + sizeof(Vector3) + sizeof(Quaternion)), out.size(), sizeof(decltype(out)::Type)}; + translationsRotationsScalings3DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, 0, translationsOut, rotationsOut, scalingsOut); + return out; +} + void SceneData::unsignedIndexFieldIntoInternal(const UnsignedInt fieldId, const std::size_t offset, const Containers::StridedArrayView1D& destination) const { /* fieldId, offset and destination.size() is assumed to be in bounds, checked by the callers */ diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index 89bf35590..356a31c67 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -148,7 +148,9 @@ enum class SceneField: UnsignedInt { * or be provided just for a subset of it --- see its documentation for * details. * @see @ref SceneData::transformations2DAsArray(), - * @ref SceneData::transformations3DAsArray() + * @ref SceneData::transformations3DAsArray(), + * @ref SceneData::translationsRotationsScalings2DAsArray(), + * @ref SceneData::translationsRotationsScalings3DAsArray() */ Translation, @@ -167,7 +169,9 @@ enum class SceneField: UnsignedInt { * or be provided just for a subset of it --- see its documentation for * details. * @see @ref SceneData::transformations2DAsArray(), - * @ref SceneData::transformations3DAsArray() + * @ref SceneData::transformations3DAsArray(), + * @ref SceneData::translationsRotationsScalings2DAsArray(), + * @ref SceneData::translationsRotationsScalings3DAsArray() */ Rotation, @@ -186,7 +190,9 @@ enum class SceneField: UnsignedInt { * or be provided just for a subset of it --- see its documentation for * details. * @see @ref SceneData::transformations2DAsArray(), - * @ref SceneData::transformations3DAsArray() + * @ref SceneData::transformations3DAsArray(), + * @ref SceneData::translationsRotationsScalings2DAsArray(), + * @ref SceneData::translationsRotationsScalings3DAsArray() */ Scaling, @@ -1116,6 +1122,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * use the overload below by using @cpp T[] @ce instead of @cpp T @ce. * You can also use the non-templated @ref parentsAsArray(), * @ref transformations2DAsArray(), @ref transformations3DAsArray(), + * @ref translationsRotationsScalings2DAsArray(), + * @ref translationsRotationsScalings3DAsArray(), * @ref meshesMaterialsAsArray(), @ref lightsAsArray(), * @ref camerasAsArray(), @ref skinsAsArray() accessors to get common * fields converted to usual types, but note that these operations @@ -1192,11 +1200,14 @@ class MAGNUM_TRADE_EXPORT SceneData { * to @ref fieldType(SceneField) const. The field is also expected to * not be an array, in that 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 parentsAsArray(), @ref transformations2DAsArray(), - * @ref transformations3DAsArray(), @ref meshesMaterialsAsArray(), - * @ref lightsAsArray(), @ref camerasAsArray(), @ref skinsAsArray() - * accessors to get common fields converted to usual types, but note - * that these operations involve extra allocation and data conversion. + * non-templated @ref parentsAsArray(), + * @ref transformations2DAsArray(), @ref transformations3DAsArray(), + * @ref translationsRotationsScalings2DAsArray(), + * @ref translationsRotationsScalings3DAsArray(), + * @ref meshesMaterialsAsArray(), @ref lightsAsArray(), + * @ref camerasAsArray(), @ref skinsAsArray() accessors to get common + * fields converted to usual types, but note that these operations + * involve extra allocation and data conversion. * @see @ref field(UnsignedInt) const, @ref mutableField(SceneField) */ template::value>::type> Containers::StridedArrayView1D field(SceneField name) const; @@ -1402,6 +1413,54 @@ class MAGNUM_TRADE_EXPORT SceneData { */ std::size_t transformations2DInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** + * @brief 2D transformations as float translation, rotation and scaling components + * @m_since_latest + * + * Convenience alternative to @ref field(SceneField) const with + * @ref SceneField::Translation, @ref SceneField::Rotation and + * @ref SceneField::Scaling as the arguments, as these are required to + * share the same object mapping. Converts the fields from an arbitrary + * underlying type and returns them in a newly-allocated array. At + * least one of the fields is expected to exist and they are expected + * to have a type corresponding to 2D, otherwise you're supposed to use + * @ref translationsRotationsScalings3DAsArray(). If the + * @ref SceneField::Translation field isn't present, the first returned + * value is a zero vector. If the @relativeref{SceneField,Rotation} + * field isn't present, the second value is an identity rotation. If + * the @relativeref{SceneField,Scaling} field isn't present, the third + * value is an identity scaling (@cpp 1.0f @ce in both dimensions). + * @see @ref translationsRotationsScalings2DInto(), @ref hasField(), + * @ref fieldType(SceneField) const + */ + Containers::Array> translationsRotationsScalings2DAsArray() const; + + /** + * @brief 2D transformations as float translation, rotation and scaling components into a pre-allocated view + * @m_since_latest + * + * Like @ref translationsRotationsScalings2DAsArray(), but puts the + * result into @p translationDestination, @p rotationDestination and + * @p scalingDestination instead of allocating a new array. Expects + * that each view is either @cpp nullptr @ce or sized to contain + * exactly all data. + * @see @ref fieldSize(SceneField) const + */ + void translationsRotationsScalings2DInto(const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; + + /** + * @brief A subrange of 2D transformations as float translation, rotation and scaling components into a pre-allocated view + * @m_since_latest + * + * Compared to @ref translationsRotationsScalings2DInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the views, returning the count of items actually extracted. The + * @p offset is expected to not be larger than the field size, views + * that are not @cpp nullptr @ce are expected to have the same size. + * @see @ref fieldSize(SceneField) const + */ + std::size_t translationsRotationsScalings2DInto(std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; + /** * @brief 3D transformations as 4x4 float matrices * @m_since_latest @@ -1445,6 +1504,54 @@ class MAGNUM_TRADE_EXPORT SceneData { */ std::size_t transformations3DInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** + * @brief 3D transformations as float translation, rotation and scaling components + * @m_since_latest + * + * Convenience alternative to @ref field(SceneField) const with + * @ref SceneField::Translation, @ref SceneField::Rotation and + * @ref SceneField::Scaling as the arguments, as these are required to + * share the same object mapping. Converts the fields from an arbitrary + * underlying type and returns them in a newly-allocated array. At + * least one of the fields is expected to exist and they are expected + * to have a type corresponding to 3D, otherwise you're supposed to use + * @ref translationsRotationsScalings2DAsArray(). If the + * @ref SceneField::Translation field isn't present, the first returned + * value is a zero vector. If the @relativeref{SceneField,Rotation} + * field isn't present, the second value is an identity rotation. If + * the @relativeref{SceneField,Scaling} field isn't present, the third + * value is an identity scaling (@cpp 1.0f @ce in all dimensions). + * @see @ref translationsRotationsScalings3DInto(), @ref hasField(), + * @ref fieldType(SceneField) const + */ + Containers::Array> translationsRotationsScalings3DAsArray() const; + + /** + * @brief 3D transformations as float translation, rotation and scaling components into a pre-allocated view + * @m_since_latest + * + * Like @ref translationsRotationsScalings3DAsArray(), but puts the + * result into @p translationDestination, @p rotationDestination and + * @p scalingDestination instead of allocating a new array. Expects + * that each view is either @cpp nullptr @ce or sized to contain + * exactly all data. + * @see @ref fieldSize(SceneField) const + */ + void translationsRotationsScalings3DInto(const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; + + /** + * @brief A subrange of 3D transformations as float translation, rotation and scaling components into a pre-allocated view + * @m_since_latest + * + * Compared to @ref translationsRotationsScalings3DInto(const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) const + * extracts only a subrange of the field defined by @p offset and size + * of the views, returning the count of items actually extracted. The + * @p offset is expected to not be larger than the field size, views + * that are not @cpp nullptr @ce are expected to have the same size. + * @see @ref fieldSize(SceneField) const + */ + std::size_t translationsRotationsScalings3DInto(std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; + /** * @brief Mesh and material IDs as 32-bit integers * @m_since_latest @@ -1654,8 +1761,11 @@ class MAGNUM_TRADE_EXPORT SceneData { MAGNUM_TRADE_LOCAL void objectsIntoInternal(UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; MAGNUM_TRADE_LOCAL void parentsIntoInternal(UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; MAGNUM_TRADE_LOCAL std::size_t findTransformFields(UnsignedInt& transformationFieldId, UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId) const; + MAGNUM_TRADE_LOCAL std::size_t findTranslationRotationScalingFields(UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId) const; MAGNUM_TRADE_LOCAL void transformations2DIntoInternal(UnsignedInt transformationFieldId, UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL void translationsRotationsScalings2DIntoInternal(UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; MAGNUM_TRADE_LOCAL void transformations3DIntoInternal(UnsignedInt transformationFieldId, UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL void translationsRotationsScalings3DIntoInternal(UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; MAGNUM_TRADE_LOCAL void unsignedIndexFieldIntoInternal(const UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; MAGNUM_TRADE_LOCAL void indexFieldIntoInternal(const UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; MAGNUM_TRADE_LOCAL Containers::Array unsignedIndexFieldAsArrayInternal(const UnsignedInt fieldId) const; diff --git a/src/Magnum/Trade/Test/SceneDataTest.cpp b/src/Magnum/Trade/Test/SceneDataTest.cpp index d3cf758b4..b09ba66e8 100644 --- a/src/Magnum/Trade/Test/SceneDataTest.cpp +++ b/src/Magnum/Trade/Test/SceneDataTest.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -118,6 +119,7 @@ struct SceneDataTest: TestSuite::Tester { void transformations2DIntoArray(); void transformations2DIntoArrayTRS(); void transformations2DIntoArrayInvalidSizeOrOffset(); + void transformations2DIntoArrayInvalidSizeOrOffsetTRS(); template void transformations3DAsArray(); template void transformations3DAsArrayTRS(); template void transformations3DAsArrayBut2DType(); @@ -125,6 +127,7 @@ struct SceneDataTest: TestSuite::Tester { void transformations3DIntoArray(); void transformations3DIntoArrayTRS(); void transformations3DIntoArrayInvalidSizeOrOffset(); + void transformations3DIntoArrayInvalidSizeOrOffsetTRS(); template void meshesMaterialsAsArray(); void meshesMaterialsIntoArray(); void meshesMaterialsIntoArrayInvalidSizeOrOffset(); @@ -275,6 +278,7 @@ SceneDataTest::SceneDataTest() { Containers::arraySize(IntoArrayOffsetData)); addTests({&SceneDataTest::transformations2DIntoArrayInvalidSizeOrOffset, + &SceneDataTest::transformations2DIntoArrayInvalidSizeOrOffsetTRS, &SceneDataTest::transformations3DAsArray, &SceneDataTest::transformations3DAsArray, &SceneDataTest::transformations3DAsArray, @@ -293,6 +297,7 @@ SceneDataTest::SceneDataTest() { Containers::arraySize(IntoArrayOffsetData)); addTests({&SceneDataTest::transformations3DIntoArrayInvalidSizeOrOffset, + &SceneDataTest::transformations3DIntoArrayInvalidSizeOrOffsetTRS, &SceneDataTest::meshesMaterialsAsArray, &SceneDataTest::meshesMaterialsAsArray, &SceneDataTest::meshesMaterialsAsArray}); @@ -2147,6 +2152,13 @@ template void SceneDataTest::transformations2DAsArray Matrix3{Math::IdentityInit}, Matrix3::translation({1.5f, 2.5f}) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>({ + {{3.0f, 2.0f}, {}, Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{1.5f, 2.5f}, {}, Vector2{1.0f}}, + })), TestSuite::Compare::Container); } { SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { rotation @@ -2158,6 +2170,13 @@ template void SceneDataTest::transformations2DAsArray Matrix3{Math::IdentityInit}, Matrix3::rotation(-15.0_degf) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>({ + {{}, {}, Vector2{1.0f}}, + {{}, Complex::rotation(35.0_degf), Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{}, Complex::rotation(-15.0_degf), Vector2{1.0f}}, + })), TestSuite::Compare::Container); } { SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { scaling @@ -2169,6 +2188,13 @@ template void SceneDataTest::transformations2DAsArray Matrix3::scaling({2.0f, 1.0f}), Matrix3::scaling({-0.5f, 4.0f}) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>({ + {{}, {}, Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{}, {}, {2.0f, 1.0f}}, + {{}, {}, {-0.5f, 4.0f}}, + })), TestSuite::Compare::Container); } /* Pairs */ @@ -2184,6 +2210,13 @@ template void SceneDataTest::transformations2DAsArray Matrix3{Math::IdentityInit}, Matrix3::translation({1.5f, 2.5f})*Matrix3::rotation({-15.0_degf}) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>({ + {{3.0f, 2.0f}, {}, Vector2{1.0f}}, + {{}, Complex::rotation(35.0_degf), Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{1.5f, 2.5f}, Complex::rotation(-15.0_degf), Vector2{1.0f}}, + })), TestSuite::Compare::Container); } { SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { translation, @@ -2196,6 +2229,13 @@ template void SceneDataTest::transformations2DAsArray Matrix3::scaling({2.0f, 1.0f}), Matrix3::translation({1.5f, 2.5f})*Matrix3::scaling({-0.5f, 4.0f}) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>({ + {{3.0f, 2.0f}, {}, Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{}, {}, {2.0f, 1.0f}}, + {{1.5f, 2.5f}, {}, {-0.5f, 4.0f}}, + })), TestSuite::Compare::Container); } { SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { rotation, @@ -2208,6 +2248,13 @@ template void SceneDataTest::transformations2DAsArray Matrix3::scaling({2.0f, 1.0f}), Matrix3::rotation({-15.0_degf})*Matrix3::scaling({-0.5f, 4.0f}) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>({ + {{}, {}, Vector2{1.0f}}, + {{}, Complex::rotation(35.0_degf), Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{}, {}, {2.0f, 1.0f}}, + {{}, Complex::rotation(-15.0_degf), {-0.5f, 4.0f}}, + })), TestSuite::Compare::Container); } /* All */ @@ -2224,6 +2271,13 @@ template void SceneDataTest::transformations2DAsArray Matrix3::scaling({2.0f, 1.0f}), Matrix3::translation({1.5f, 2.5f})*Matrix3::rotation({-15.0_degf})*Matrix3::scaling({-0.5f, 4.0f}) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings2DAsArray(), (Containers::arrayView>({ + {{3.0f, 2.0f}, {}, Vector2{1.0f}}, + {{}, Complex::rotation(35.0_degf), Vector2{1.0f}}, + {{}, {}, Vector2{1.0f}}, + {{}, {}, {2.0f, 1.0f}}, + {{1.5f, 2.5f}, Complex::rotation(-15.0_degf), {-0.5f, 4.0f}}, + })), TestSuite::Compare::Container); } } @@ -2252,25 +2306,43 @@ template void SceneDataTest::transformations2DAsArrayBut3DTypeTRS() { CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif - SceneData translation{SceneObjectType::UnsignedInt, 0, nullptr, { - SceneFieldData{SceneField::Translation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + /* Because TRSAsArray() allocates an Array and then calls + TRSInto(), which skips views that are nullptr, we wouldn't get the + assertion for translations, as those are at offset 0, which would be + interpreted as an empty view if there were no elements. So we have to + supply at least one element to make all assertions trigger. */ + struct Field { + UnsignedInt object; + Math::Vector3 translation; + Math::Quaternion rotation; + Math::Vector3 scaling; + } data[1]; + Containers::StridedArrayView1D view = data; + SceneData translation{SceneObjectType::UnsignedInt, 1, {}, data, { + SceneFieldData{SceneField::Translation, view.slice(&Field::object), view.slice(&Field::translation)} }}; - SceneData rotation{SceneObjectType::UnsignedInt, 0, nullptr, { - SceneFieldData{SceneField::Rotation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + SceneData rotation{SceneObjectType::UnsignedInt, 1, {}, data, { + SceneFieldData{SceneField::Rotation, view.slice(&Field::object), view.slice(&Field::rotation)} }}; - SceneData scaling{SceneObjectType::UnsignedInt, 0, nullptr, { - SceneFieldData{SceneField::Scaling, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + SceneData scaling{SceneObjectType::UnsignedInt, 1, {}, data, { + SceneFieldData{SceneField::Scaling, view.slice(&Field::object), view.slice(&Field::scaling)} }}; std::ostringstream out; Error redirectError{&out}; translation.transformations2DAsArray(); + translation.translationsRotationsScalings2DAsArray(); rotation.transformations2DAsArray(); + rotation.translationsRotationsScalings2DAsArray(); scaling.transformations2DAsArray(); + scaling.translationsRotationsScalings2DAsArray(); CORRADE_COMPARE(out.str(), Utility::formatString( "Trade::SceneData::transformations2DInto(): field has a 3D translation type Trade::SceneFieldType::{0}\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): field has a 3D translation type Trade::SceneFieldType::{0}\n" "Trade::SceneData::transformations2DInto(): field has a 3D rotation type Trade::SceneFieldType::{1}\n" - "Trade::SceneData::transformations2DInto(): field has a 3D scaling type Trade::SceneFieldType::{0}\n", NameTraits>::name(), NameTraits>::name())); + "Trade::SceneData::translationsRotationsScalings2DInto(): field has a 3D rotation type Trade::SceneFieldType::{1}\n" + "Trade::SceneData::transformations2DInto(): field has a 3D scaling type Trade::SceneFieldType::{0}\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): field has a 3D scaling type Trade::SceneFieldType::{0}\n", NameTraits>::name(), NameTraits>::name())); } void SceneDataTest::transformations2DIntoArray() { @@ -2368,6 +2440,50 @@ void SceneDataTest::transformations2DIntoArrayTRS() { Containers::arrayView(expected), TestSuite::Compare::Container); + /* Variant with TRS components */ + } { + Vector2 translationsOut[3]; + Complex rotationsOut[3]; + Vector2 scalingsOut[3]; + scene.translationsRotationsScalings2DInto(translationsOut, rotationsOut, scalingsOut); + CORRADE_COMPARE_AS(Containers::stridedArrayView(translationsOut), + view.slice(&Field::translation), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::stridedArrayView(rotationsOut), + view.slice(&Field::rotation), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::stridedArrayView(scalingsOut), + view.slice(&Field::scaling), + TestSuite::Compare::Container); + + /* Variant with just translations */ + } { + Vector2 translationsOut[3]; + scene.translationsRotationsScalings2DInto(translationsOut, nullptr, nullptr); + CORRADE_COMPARE_AS(Containers::stridedArrayView(translationsOut), + view.slice(&Field::translation), + TestSuite::Compare::Container); + + /* Variant with just rotations */ + } { + Complex rotationsOut[3]; + scene.translationsRotationsScalings2DInto(nullptr, rotationsOut, nullptr); + CORRADE_COMPARE_AS(Containers::stridedArrayView(rotationsOut), + view.slice(&Field::rotation), + TestSuite::Compare::Container); + + /* Variant with just scalings */ + } { + Vector2 scalingsOut[3]; + scene.translationsRotationsScalings2DInto(nullptr, nullptr, scalingsOut); + CORRADE_COMPARE_AS(Containers::stridedArrayView(scalingsOut), + view.slice(&Field::scaling), + TestSuite::Compare::Container); + + /* Variant with neither is stupid, but should work too */ + } { + scene.translationsRotationsScalings2DInto(nullptr, nullptr, nullptr); + /* The offset variant only a subset */ } { Containers::Array out{data.size}; @@ -2375,6 +2491,56 @@ void SceneDataTest::transformations2DIntoArrayTRS() { CORRADE_COMPARE_AS(out.prefix(data.expectedSize), Containers::arrayView(expected).slice(data.offset, data.offset + data.expectedSize), TestSuite::Compare::Container); + + /* Variant with TRS components */ + } { + Containers::Array translationsOut{data.size}; + Containers::Array rotationsOut{data.size}; + Containers::Array scalingsOut{data.size}; + CORRADE_COMPARE(scene.translationsRotationsScalings2DInto(data.offset, translationsOut, rotationsOut, scalingsOut), data.expectedSize); + CORRADE_COMPARE_AS(translationsOut.prefix(data.expectedSize), + view.slice(&Field::translation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(rotationsOut.prefix(data.expectedSize), + view.slice(&Field::rotation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scalingsOut.prefix(data.expectedSize), + view.slice(&Field::scaling) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + + /* Variant with just translations */ + } { + Containers::Array translationsOut{data.size}; + CORRADE_COMPARE(scene.translationsRotationsScalings2DInto(data.offset, translationsOut, nullptr, nullptr), data.expectedSize); + CORRADE_COMPARE_AS(translationsOut.prefix(data.expectedSize), + view.slice(&Field::translation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + + /* Variant with just rotations */ + } { + Containers::Array rotationsOut{data.size}; + CORRADE_COMPARE(scene.translationsRotationsScalings2DInto(data.offset, nullptr, rotationsOut, nullptr), data.expectedSize); + CORRADE_COMPARE_AS(rotationsOut.prefix(data.expectedSize), + view.slice(&Field::rotation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + + /* Variant with just scalings */ + } { + Containers::Array scalingsOut{data.size}; + CORRADE_COMPARE(scene.translationsRotationsScalings2DInto(data.offset, nullptr, nullptr, scalingsOut), data.expectedSize); + CORRADE_COMPARE_AS(scalingsOut.prefix(data.expectedSize), + view.slice(&Field::scaling) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + + /* Variant with neither is stupid, but should work too */ + } { + CORRADE_COMPARE(scene.translationsRotationsScalings2DInto(data.offset, nullptr, nullptr, nullptr), 0); } } @@ -2404,6 +2570,47 @@ void SceneDataTest::transformations2DIntoArrayInvalidSizeOrOffset() { "Trade::SceneData::transformations2DInto(): offset 4 out of bounds for a field of size 3\n"); } +void SceneDataTest::transformations2DIntoArrayInvalidSizeOrOffsetTRS() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + Vector2 translation; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Translation, view.slice(&Field::object), view.slice(&Field::translation)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + Vector2 translationDestinationCorrect[3]; + Vector2 translationDestination[2]; + Complex rotationDestinationCorrect[3]; + Complex rotationDestination[2]; + Vector2 scalingDestinationCorrect[3]; + Vector2 scalingDestination[2]; + scene.translationsRotationsScalings2DInto(translationDestination, rotationDestinationCorrect, scalingDestinationCorrect); + scene.translationsRotationsScalings2DInto(translationDestinationCorrect, rotationDestination, scalingDestinationCorrect); + scene.translationsRotationsScalings2DInto(translationDestinationCorrect, rotationDestinationCorrect, scalingDestination); + scene.translationsRotationsScalings2DInto(4, translationDestination, rotationDestination, scalingDestination); + scene.translationsRotationsScalings2DInto(0, translationDestinationCorrect, rotationDestination, nullptr); + scene.translationsRotationsScalings2DInto(0, translationDestinationCorrect, nullptr, scalingDestination); + scene.translationsRotationsScalings2DInto(0, nullptr, rotationDestinationCorrect, scalingDestination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::translationsRotationsScalings2DInto(): expected translation destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): expected rotation destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): expected scaling destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): translation and rotation destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): translation and scaling destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): rotation and scaling destination views have different size, 3 vs 2\n"); +} + template void SceneDataTest::transformations3DAsArray() { setTestCaseTemplateName(NameTraits::name()); @@ -2520,6 +2727,13 @@ template void SceneDataTest::transformations3DAsArray Matrix4{Math::IdentityInit}, Matrix4::translation({1.5f, 2.5f, 3.5f}) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>({ + {{3.0f, 2.0, 1.0f}, {}, Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{1.5f, 2.5f, 3.5f}, {}, Vector3{1.0f}}, + })), TestSuite::Compare::Container); } { SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { rotation @@ -2531,6 +2745,13 @@ template void SceneDataTest::transformations3DAsArray Matrix4{Math::IdentityInit}, Matrix4::rotationX(-15.0_degf) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>({ + {{}, {}, Vector3{1.0f}}, + {{}, Quaternion::rotation(35.0_degf, Vector3::yAxis()), Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{}, Quaternion::rotation(-15.0_degf, Vector3::xAxis()), Vector3{1.0f}}, + })), TestSuite::Compare::Container); } { SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { scaling @@ -2542,6 +2763,13 @@ template void SceneDataTest::transformations3DAsArray Matrix4::scaling({2.0f, 1.0f, 0.0f}), Matrix4::scaling({-0.5f, 4.0f, -16.0f}) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>({ + {{}, {}, Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{}, {}, {2.0f, 1.0f, 0.0f}}, + {{}, {}, {-0.5f, 4.0f, -16.0f}}, + })), TestSuite::Compare::Container); } /* Pairs */ @@ -2557,6 +2785,13 @@ template void SceneDataTest::transformations3DAsArray Matrix4{Math::IdentityInit}, Matrix4::translation({1.5f, 2.5f, 3.5f})*Matrix4::rotationX(-15.0_degf) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>({ + {{3.0f, 2.0, 1.0f}, {}, Vector3{1.0f}}, + {{}, Quaternion::rotation(35.0_degf, Vector3::yAxis()), Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{1.5f, 2.5f, 3.5f}, Quaternion::rotation(-15.0_degf, Vector3::xAxis()), Vector3{1.0f}}, + })), TestSuite::Compare::Container); } { SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { translation, @@ -2569,6 +2804,13 @@ template void SceneDataTest::transformations3DAsArray Matrix4::scaling({2.0f, 1.0f, 0.0f}), Matrix4::translation({1.5f, 2.5f, 3.5f})*Matrix4::scaling({-0.5f, 4.0f, -16.0f}) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>({ + {{3.0f, 2.0, 1.0f}, {}, Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{}, {}, {2.0f, 1.0f, 0.0f}}, + {{1.5f, 2.5f, 3.5f}, {}, {-0.5f, 4.0f, -16.0f}}, + })), TestSuite::Compare::Container); } { SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { rotation, @@ -2581,6 +2823,13 @@ template void SceneDataTest::transformations3DAsArray Matrix4::scaling({2.0f, 1.0f, 0.0f}), Matrix4::rotationX({-15.0_degf})*Matrix4::scaling({-0.5f, 4.0f, -16.0f}) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>({ + {{}, {}, Vector3{1.0f}}, + {{}, Quaternion::rotation(35.0_degf, Vector3::yAxis()), Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{}, {}, {2.0f, 1.0f, 0.0f}}, + {{}, Quaternion::rotation(-15.0_degf, Vector3::xAxis()), {-0.5f, 4.0f, -16.0f}}, + })), TestSuite::Compare::Container); } /* All */ @@ -2597,6 +2846,13 @@ template void SceneDataTest::transformations3DAsArray Matrix4::scaling({2.0f, 1.0f, 0.0f}), Matrix4::translation({1.5f, 2.5f, 3.5f})*Matrix4::rotationX({-15.0_degf})*Matrix4::scaling({-0.5f, 4.0f, -16.0f}) }), TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.translationsRotationsScalings3DAsArray(), (Containers::arrayView>({ + {{3.0f, 2.0, 1.0f}, {}, Vector3{1.0f}}, + {{}, Quaternion::rotation(35.0_degf, Vector3::yAxis()), Vector3{1.0f}}, + {{}, {}, Vector3{1.0f}}, + {{}, {}, {2.0f, 1.0f, 0.0f}}, + {{1.5f, 2.5f, 3.5f}, Quaternion::rotation(-15.0_degf, Vector3::xAxis()), {-0.5f, 4.0f, -16.0f}}, + })), TestSuite::Compare::Container); } } @@ -2625,25 +2881,43 @@ template void SceneDataTest::transformations3DAsArrayBut2DTypeTRS() { CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif - SceneData translation{SceneObjectType::UnsignedInt, 0, nullptr, { - SceneFieldData{SceneField::Translation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + /* Because TRSAsArray() allocates an Array and then calls + TRSInto(), which skips views that are nullptr, we wouldn't get the + assertion for translations, as those are at offset 0, which would be + interpreted as an empty view if there were no elements. So we have to + supply at least one element to make all assertions trigger. */ + struct Field { + UnsignedInt object; + Math::Vector2 translation; + Math::Complex rotation; + Math::Vector2 scaling; + } data[1]; + Containers::StridedArrayView1D view = data; + SceneData translation{SceneObjectType::UnsignedInt, 1, {}, data, { + SceneFieldData{SceneField::Translation, view.slice(&Field::object), view.slice(&Field::translation)} }}; - SceneData rotation{SceneObjectType::UnsignedInt, 0, nullptr, { - SceneFieldData{SceneField::Rotation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + SceneData rotation{SceneObjectType::UnsignedInt, 1, {}, data, { + SceneFieldData{SceneField::Rotation, view.slice(&Field::object), view.slice(&Field::rotation)} }}; - SceneData scaling{SceneObjectType::UnsignedInt, 0, nullptr, { - SceneFieldData{SceneField::Scaling, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + SceneData scaling{SceneObjectType::UnsignedInt, 1, {}, data, { + SceneFieldData{SceneField::Scaling, view.slice(&Field::object), view.slice(&Field::scaling)} }}; std::ostringstream out; Error redirectError{&out}; translation.transformations3DAsArray(); + translation.translationsRotationsScalings3DAsArray(); rotation.transformations3DAsArray(); + rotation.translationsRotationsScalings3DAsArray(); scaling.transformations3DAsArray(); + scaling.translationsRotationsScalings3DAsArray(); CORRADE_COMPARE(out.str(), Utility::formatString( "Trade::SceneData::transformations3DInto(): field has a 2D translation type Trade::SceneFieldType::{0}\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): field has a 2D translation type Trade::SceneFieldType::{0}\n" "Trade::SceneData::transformations3DInto(): field has a 2D rotation type Trade::SceneFieldType::{1}\n" - "Trade::SceneData::transformations3DInto(): field has a 2D scaling type Trade::SceneFieldType::{0}\n", NameTraits>::name(), NameTraits>::name())); + "Trade::SceneData::translationsRotationsScalings3DInto(): field has a 2D rotation type Trade::SceneFieldType::{1}\n" + "Trade::SceneData::transformations3DInto(): field has a 2D scaling type Trade::SceneFieldType::{0}\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): field has a 2D scaling type Trade::SceneFieldType::{0}\n", NameTraits>::name(), NameTraits>::name())); } void SceneDataTest::transformations3DIntoArray() { @@ -2741,6 +3015,50 @@ void SceneDataTest::transformations3DIntoArrayTRS() { Containers::arrayView(expected), TestSuite::Compare::Container); + /* Variant with TRS components */ + } { + Vector3 translationsOut[3]; + Quaternion rotationsOut[3]; + Vector3 scalingsOut[3]; + scene.translationsRotationsScalings3DInto(translationsOut, rotationsOut, scalingsOut); + CORRADE_COMPARE_AS(Containers::stridedArrayView(translationsOut), + view.slice(&Field::translation), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::stridedArrayView(rotationsOut), + view.slice(&Field::rotation), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(Containers::stridedArrayView(scalingsOut), + view.slice(&Field::scaling), + TestSuite::Compare::Container); + + /* Variant with just translations */ + } { + Vector3 translationsOut[3]; + scene.translationsRotationsScalings3DInto(translationsOut, nullptr, nullptr); + CORRADE_COMPARE_AS(Containers::stridedArrayView(translationsOut), + view.slice(&Field::translation), + TestSuite::Compare::Container); + + /* Variant with just rotations */ + } { + Quaternion rotationsOut[3]; + scene.translationsRotationsScalings3DInto(nullptr, rotationsOut, nullptr); + CORRADE_COMPARE_AS(Containers::stridedArrayView(rotationsOut), + view.slice(&Field::rotation), + TestSuite::Compare::Container); + + /* Variant with just scalings */ + } { + Vector3 scalingsOut[3]; + scene.translationsRotationsScalings3DInto(nullptr, nullptr, scalingsOut); + CORRADE_COMPARE_AS(Containers::stridedArrayView(scalingsOut), + view.slice(&Field::scaling), + TestSuite::Compare::Container); + + /* Variant with neither is stupid, but should work too */ + } { + scene.translationsRotationsScalings3DInto(nullptr, nullptr, nullptr); + /* The offset variant only a subset */ } { Containers::Array out{data.size}; @@ -2748,6 +3066,56 @@ void SceneDataTest::transformations3DIntoArrayTRS() { CORRADE_COMPARE_AS(out.prefix(data.expectedSize), Containers::arrayView(expected).slice(data.offset, data.offset + data.expectedSize), TestSuite::Compare::Container); + + /* Variant with TRS components */ + } { + Containers::Array translationsOut{data.size}; + Containers::Array rotationsOut{data.size}; + Containers::Array scalingsOut{data.size}; + CORRADE_COMPARE(scene.translationsRotationsScalings3DInto(data.offset, translationsOut, rotationsOut, scalingsOut), data.expectedSize); + CORRADE_COMPARE_AS(translationsOut.prefix(data.expectedSize), + view.slice(&Field::translation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(rotationsOut.prefix(data.expectedSize), + view.slice(&Field::rotation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scalingsOut.prefix(data.expectedSize), + view.slice(&Field::scaling) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + + /* Variant with just translations */ + } { + Containers::Array translationsOut{data.size}; + CORRADE_COMPARE(scene.translationsRotationsScalings3DInto(data.offset, translationsOut, nullptr, nullptr), data.expectedSize); + CORRADE_COMPARE_AS(translationsOut.prefix(data.expectedSize), + view.slice(&Field::translation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + + /* Variant with just rotations */ + } { + Containers::Array rotationsOut{data.size}; + CORRADE_COMPARE(scene.translationsRotationsScalings3DInto(data.offset, nullptr, rotationsOut, nullptr), data.expectedSize); + CORRADE_COMPARE_AS(rotationsOut.prefix(data.expectedSize), + view.slice(&Field::rotation) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + + /* Variant with just scalings */ + } { + Containers::Array scalingsOut{data.size}; + CORRADE_COMPARE(scene.translationsRotationsScalings3DInto(data.offset, nullptr, nullptr, scalingsOut), data.expectedSize); + CORRADE_COMPARE_AS(scalingsOut.prefix(data.expectedSize), + view.slice(&Field::scaling) + .slice(data.offset, data.offset + data.expectedSize), + TestSuite::Compare::Container); + + /* Variant with neither is stupid, but should work too */ + } { + CORRADE_COMPARE(scene.translationsRotationsScalings3DInto(data.offset, nullptr, nullptr, nullptr), 0); } } @@ -2777,6 +3145,47 @@ void SceneDataTest::transformations3DIntoArrayInvalidSizeOrOffset() { "Trade::SceneData::transformations3DInto(): offset 4 out of bounds for a field of size 3\n"); } +void SceneDataTest::transformations3DIntoArrayInvalidSizeOrOffsetTRS() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + Vector2 translation; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Translation, view.slice(&Field::object), view.slice(&Field::translation)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + Vector3 translationDestinationCorrect[3]; + Vector3 translationDestination[2]; + Quaternion rotationDestinationCorrect[3]; + Quaternion rotationDestination[2]; + Vector3 scalingDestinationCorrect[3]; + Vector3 scalingDestination[2]; + scene.translationsRotationsScalings3DInto(translationDestination, rotationDestinationCorrect, scalingDestinationCorrect); + scene.translationsRotationsScalings3DInto(translationDestinationCorrect, rotationDestination, scalingDestinationCorrect); + scene.translationsRotationsScalings3DInto(translationDestinationCorrect, rotationDestinationCorrect, scalingDestination); + scene.translationsRotationsScalings3DInto(4, translationDestination, rotationDestination, scalingDestination); + scene.translationsRotationsScalings3DInto(0, translationDestinationCorrect, rotationDestination, nullptr); + scene.translationsRotationsScalings3DInto(0, translationDestinationCorrect, nullptr, scalingDestination); + scene.translationsRotationsScalings3DInto(0, nullptr, rotationDestinationCorrect, scalingDestination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::translationsRotationsScalings3DInto(): expected translation destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): expected rotation destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): expected scaling destination view either empty or with 3 elements but got 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): translation and rotation destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): translation and scaling destination views have different size, 3 vs 2\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): rotation and scaling destination views have different size, 3 vs 2\n"); +} + template void SceneDataTest::meshesMaterialsAsArray() { setTestCaseTemplateName({NameTraits::name(), NameTraits::name()}); @@ -3428,9 +3837,15 @@ void SceneDataTest::fieldNotFound() { scene.transformations2DAsArray(); scene.transformations2DInto(nullptr); scene.transformations2DInto(0, nullptr); + scene.translationsRotationsScalings2DAsArray(); + scene.translationsRotationsScalings2DInto(nullptr, nullptr, nullptr); + scene.translationsRotationsScalings2DInto(0, nullptr, nullptr, nullptr); scene.transformations3DAsArray(); scene.transformations3DInto(nullptr); scene.transformations3DInto(0, nullptr); + scene.translationsRotationsScalings3DAsArray(); + scene.translationsRotationsScalings3DInto(nullptr, nullptr, nullptr); + scene.translationsRotationsScalings3DInto(0, nullptr, nullptr, nullptr); scene.meshesMaterialsAsArray(); scene.meshesMaterialsInto(nullptr, nullptr); scene.meshesMaterialsInto(0, nullptr, nullptr); @@ -3476,9 +3891,15 @@ void SceneDataTest::fieldNotFound() { "Trade::SceneData::transformations2DInto(): no transformation-related field found\n" "Trade::SceneData::transformations2DInto(): no transformation-related field found\n" "Trade::SceneData::transformations2DInto(): no transformation-related field found\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): no transformation-related field found\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): no transformation-related field found\n" + "Trade::SceneData::translationsRotationsScalings2DInto(): no transformation-related field found\n" "Trade::SceneData::transformations3DInto(): no transformation-related field found\n" "Trade::SceneData::transformations3DInto(): no transformation-related field found\n" "Trade::SceneData::transformations3DInto(): no transformation-related field found\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): no transformation-related field found\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): no transformation-related field found\n" + "Trade::SceneData::translationsRotationsScalings3DInto(): no transformation-related field found\n" "Trade::SceneData::meshesMaterialsInto(): field Trade::SceneField::Mesh not found\n" "Trade::SceneData::meshesMaterialsInto(): field Trade::SceneField::Mesh not found\n" "Trade::SceneData::meshesMaterialsInto(): field Trade::SceneField::Mesh not found\n"