diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 09d014748..01ae0026d 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -25,6 +25,8 @@ #include "SceneData.h" +#include +#include #include #include #include @@ -839,8 +841,8 @@ template void applyScaling(const Containers::St } -std::size_t SceneData::findTransformFields(UnsignedInt& transformationFieldId, UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId) const { - UnsignedInt fieldToCheckForSize = ~UnsignedInt{}; +std::size_t SceneData::findTransformFields(UnsignedInt& transformationFieldId, UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId, UnsignedInt* const fieldWithObjectMappingDestination) const { + UnsignedInt fieldWithObjectMapping = ~UnsignedInt{}; transformationFieldId = ~UnsignedInt{}; translationFieldId = ~UnsignedInt{}; rotationFieldId = ~UnsignedInt{}; @@ -849,40 +851,46 @@ std::size_t SceneData::findTransformFields(UnsignedInt& transformationFieldId, U /* If we find a transformation field, we don't need to look any further */ if(_fields[i]._name == SceneField::Transformation) { - fieldToCheckForSize = transformationFieldId = i; + fieldWithObjectMapping = transformationFieldId = i; break; } else if(_fields[i]._name == SceneField::Translation) { - fieldToCheckForSize = translationFieldId = i; + fieldWithObjectMapping = translationFieldId = i; } else if(_fields[i]._name == SceneField::Rotation) { - fieldToCheckForSize = rotationFieldId = i; + fieldWithObjectMapping = rotationFieldId = i; } else if(_fields[i]._name == SceneField::Scaling) { - fieldToCheckForSize = scalingFieldId = i; + fieldWithObjectMapping = scalingFieldId = i; } } + if(fieldWithObjectMappingDestination) + *fieldWithObjectMappingDestination = fieldWithObjectMapping; + /* Assuming the caller fires an appropriate assertion */ - return fieldToCheckForSize == ~UnsignedInt{} ? - ~std::size_t{} : _fields[fieldToCheckForSize]._size; + return fieldWithObjectMapping == ~UnsignedInt{} ? + ~std::size_t{} : _fields[fieldWithObjectMapping]._size; } -std::size_t SceneData::findTranslationRotationScalingFields(UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId) const { - UnsignedInt fieldToCheckForSize = ~UnsignedInt{}; +std::size_t SceneData::findTranslationRotationScalingFields(UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId, UnsignedInt* const fieldWithObjectMappingDestination) const { + UnsignedInt fieldWithObjectMapping = ~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; + fieldWithObjectMapping = translationFieldId = i; } else if(_fields[i]._name == SceneField::Rotation) { - fieldToCheckForSize = rotationFieldId = i; + fieldWithObjectMapping = rotationFieldId = i; } else if(_fields[i]._name == SceneField::Scaling) { - fieldToCheckForSize = scalingFieldId = i; + fieldWithObjectMapping = scalingFieldId = i; } } + if(fieldWithObjectMappingDestination) + *fieldWithObjectMappingDestination = fieldWithObjectMapping; + /* Assuming the caller fires an appropriate assertion */ - return fieldToCheckForSize == ~UnsignedInt{} ? - ~std::size_t{} : _fields[fieldToCheckForSize]._size; + return fieldWithObjectMapping == ~UnsignedInt{} ? + ~std::size_t{} : _fields[fieldWithObjectMapping]._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 { @@ -1553,6 +1561,342 @@ Containers::Array SceneData::skinsAsArray() const { return unsignedIndexFieldAsArrayInternal(fieldId); } +namespace { + +template std::size_t findObject(const Containers::StridedArrayView1D& objects, const UnsignedInt object) { + const Containers::StridedArrayView1D objectsT = Containers::arrayCast(objects); + const std::size_t max = objectsT.size(); + /** @todo implement something faster than O(n) when field-specific flags + can annotate how the object mapping is done */ + for(std::size_t i = 0; i != max; ++i) + if(objectsT[i] == object) return i; + return max; +} + +} + +std::size_t SceneData::fieldFor(const SceneFieldData& field, const std::size_t offset, const UnsignedInt object) const { + const Containers::StridedArrayView1D objects = fieldDataObjectViewInternal(field, offset, field._size - offset); + if(field._objectType == SceneObjectType::UnsignedInt) + return offset + findObject(objects, object); + else if(field._objectType == SceneObjectType::UnsignedShort) + return offset + findObject(objects, object); + else if(field._objectType == SceneObjectType::UnsignedByte) + return offset + findObject(objects, object); + else if(field._objectType == SceneObjectType::UnsignedLong) + return offset + findObject(objects, object); + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array SceneData::childrenFor(Int object) const { + CORRADE_ASSERT(object >= -1 && object < Long(_objectCount), + "Trade::SceneData::childrenFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + const UnsignedInt parentFieldId = fieldFor(SceneField::Parent); + if(parentFieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& parentField = _fields[parentFieldId]; + + /* Figure out the parent object index to look for or -1 if we want + top-level objects */ + Int parentIndexToLookFor; + if(object == -1) parentIndexToLookFor = -1; + else { + const std::size_t parentObjectIndex = fieldFor(parentField, 0, object); + if(parentObjectIndex == parentField._size) return {}; + parentIndexToLookFor = parentObjectIndex; + } + + /* Collect IDs of all objects that reference this index */ + Containers::Array out; + for(std::size_t offset = 0; offset != parentField.size(); ++offset) { + Int parentIndex[1]; + parentsIntoInternal(parentFieldId, offset, parentIndex); + if(*parentIndex == parentIndexToLookFor) { + UnsignedInt child[1]; + /** @todo bleh slow, use the children <-> parent field proxying + when implemented */ + objectsIntoInternal(parentFieldId, offset, child); + arrayAppend(out, *child); + } + } + + return out; +} + +Containers::Optional SceneData::transformation2DFor(const UnsignedInt object) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::transformation2DFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, fieldWithObjectMapping; + if(findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, &fieldWithObjectMapping) == ~std::size_t{}) return {}; + + #ifndef CORRADE_NO_ASSERT + if(transformationFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[transformationFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Matrix3x3 || + type == SceneFieldType::Matrix3x3d || + type == SceneFieldType::DualComplex || + type == SceneFieldType::DualComplexd, + "Trade::SceneData::transformation2DFor(): field has a 3D transformation type" << type, {}); + } else { + if(translationFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[translationFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Vector2 || + type == SceneFieldType::Vector2d, + "Trade::SceneData::transformation2DFor(): field has a 3D translation type" << type, {}); + } + if(rotationFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[rotationFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Complex || + type == SceneFieldType::Complexd, + "Trade::SceneData::transformation2DFor(): field has a 3D rotation type" << type, {}); + } + if(scalingFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[scalingFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Vector2 || + type == SceneFieldType::Vector2d, + "Trade::SceneData::transformation2DFor(): field has a 3D scaling type" << type, {}); + } + } + #endif + + const std::size_t offset = fieldFor(_fields[fieldWithObjectMapping], 0, object); + if(offset == _fields[fieldWithObjectMapping]._size) return {}; + + Matrix3 transformation[1]; + transformations2DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, offset, transformation); + return *transformation; +} + +Containers::Optional> SceneData::translationRotationScaling2DFor(const UnsignedInt object) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::translationRotationScaling2DFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId, fieldWithObjectMapping; + if(findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId, &fieldWithObjectMapping) == ~std::size_t{}) return {}; + + #ifndef CORRADE_NO_ASSERT + if(translationFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[translationFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Vector2 || + type == SceneFieldType::Vector2d, + "Trade::SceneData::translationRotationScaling2DFor(): field has a 3D translation type" << type, {}); + } + if(rotationFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[rotationFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Complex || + type == SceneFieldType::Complexd, + "Trade::SceneData::translationRotationScaling2DFor(): field has a 3D rotation type" << type, {}); + } + if(scalingFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[scalingFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Vector2 || + type == SceneFieldType::Vector2d, + "Trade::SceneData::translationRotationScaling2DFor(): field has a 3D scaling type" << type, {}); + } + #endif + + const std::size_t offset = fieldFor(_fields[fieldWithObjectMapping], 0, object); + if(offset == _fields[fieldWithObjectMapping]._size) return {}; + + Vector2 translation[1]; + Complex rotation[1]; + Vector2 scaling[1]; + translationsRotationsScalings2DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, offset, translation, rotation, scaling); + return {InPlaceInit, *translation, *rotation, *scaling}; +} + +Containers::Optional SceneData::transformation3DFor(const UnsignedInt object) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::transformation3DFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, fieldWithObjectMapping; + if(findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, &fieldWithObjectMapping) == ~std::size_t{}) return {}; + + #ifndef CORRADE_NO_ASSERT + if(transformationFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[transformationFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Matrix4x4 || + type == SceneFieldType::Matrix4x4d || + type == SceneFieldType::DualQuaternion || + type == SceneFieldType::DualQuaterniond, + "Trade::SceneData::transformation3DFor(): field has a 2D transformation type" << type, {}); + } else { + if(translationFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[translationFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Vector3 || + type == SceneFieldType::Vector3d, + "Trade::SceneData::transformation3DFor(): field has a 2D translation type" << type, {}); + } + if(rotationFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[rotationFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Quaternion || + type == SceneFieldType::Quaterniond, + "Trade::SceneData::transformation3DFor(): field has a 2D rotation type" << type, {}); + } + if(scalingFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[scalingFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Vector3 || + type == SceneFieldType::Vector3d, + "Trade::SceneData::transformation3DFor(): field has a 2D scaling type" << type, {}); + } + } + #endif + + const std::size_t offset = fieldFor(_fields[fieldWithObjectMapping], 0, object); + if(offset == _fields[fieldWithObjectMapping]._size) return {}; + + Matrix4 transformation[1]; + transformations3DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, offset, transformation); + return *transformation; +} + +Containers::Optional> SceneData::translationRotationScaling3DFor(const UnsignedInt object) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::translationRotationScaling3DFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + UnsignedInt translationFieldId, rotationFieldId, scalingFieldId, fieldWithObjectMapping; + if(findTranslationRotationScalingFields(translationFieldId, rotationFieldId, scalingFieldId, &fieldWithObjectMapping) == ~std::size_t{}) return {}; + + #ifndef CORRADE_NO_ASSERT + if(translationFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[translationFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Vector3 || + type == SceneFieldType::Vector3d, + "Trade::SceneData::translationRotationScaling3DFor(): field has a 2D translation type" << type, {}); + } + if(rotationFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[rotationFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Quaternion || + type == SceneFieldType::Quaterniond, + "Trade::SceneData::translationRotationScaling3DFor(): field has a 2D rotation type" << type, {}); + } + if(scalingFieldId != ~UnsignedInt{}) { + const SceneFieldType type = _fields[scalingFieldId]._fieldType; + CORRADE_ASSERT( + type == SceneFieldType::Vector3 || + type == SceneFieldType::Vector3d, + "Trade::SceneData::translationRotationScaling3DFor(): field has a 2D scaling type" << type, {}); + } + #endif + + const std::size_t offset = fieldFor(_fields[fieldWithObjectMapping], 0, object); + if(offset == _fields[fieldWithObjectMapping]._size) return {}; + + Vector3 translation[1]; + Quaternion rotation[1]; + Vector3 scaling[1]; + translationsRotationsScalings3DIntoInternal(translationFieldId, rotationFieldId, scalingFieldId, offset, translation, rotation, scaling); + return {InPlaceInit, *translation, *rotation, *scaling}; +} + +Containers::Array> SceneData::meshesMaterialsFor(const UnsignedInt object) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::meshesMaterialsFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + const UnsignedInt meshFieldId = fieldFor(SceneField::Mesh); + if(meshFieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& field = _fields[meshFieldId]; + Containers::Array> out; + std::size_t offset = 0; + for(;;) { + offset = fieldFor(field, offset, object); + if(offset == field._size) break; + + UnsignedInt mesh[1]; + Int material[1]; + meshesMaterialsIntoInternal(meshFieldId, offset, mesh, material); + arrayAppend(out, InPlaceInit, *mesh, *material); + ++offset; + } + + return out; +} + +Containers::Array SceneData::lightsFor(const UnsignedInt object) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::lightsFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + const UnsignedInt fieldId = fieldFor(SceneField::Light); + if(fieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& field = _fields[fieldId]; + Containers::Array out; + std::size_t offset = 0; + for(;;) { + offset = fieldFor(field, offset, object); + if(offset == field._size) break; + + UnsignedInt index[1]; + unsignedIndexFieldIntoInternal(fieldId, offset, index); + arrayAppend(out, InPlaceInit, *index); + ++offset; + } + + return out; +} + +Containers::Array SceneData::camerasFor(const UnsignedInt object) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::camerasFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + const UnsignedInt fieldId = fieldFor(SceneField::Camera); + if(fieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& field = _fields[fieldId]; + Containers::Array out; + std::size_t offset = 0; + for(;;) { + offset = fieldFor(field, offset, object); + if(offset == field._size) break; + + UnsignedInt index[1]; + unsignedIndexFieldIntoInternal(fieldId, offset, index); + arrayAppend(out, InPlaceInit, *index); + ++offset; + } + + return out; +} + +Containers::Array SceneData::skinsFor(const UnsignedInt object) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::skinsFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + const UnsignedInt fieldId = fieldFor(SceneField::Skin); + if(fieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& field = _fields[fieldId]; + Containers::Array out; + std::size_t offset = 0; + for(;;) { + offset = fieldFor(field, offset, object); + if(offset == field._size) break; + + UnsignedInt index[1]; + unsignedIndexFieldIntoInternal(fieldId, offset, index); + arrayAppend(out, InPlaceInit, *index); + ++offset; + } + + return out; +} + Containers::Array SceneData::releaseFieldData() { Containers::Array out = std::move(_fields); _fields = {}; diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index 356a31c67..f46295518 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -102,7 +102,7 @@ enum class SceneField: UnsignedInt { * Note that the index points to the parent array itself and isn't the * actual object index --- the object mapping is stored in the * corresponding @ref SceneData::objects() array. - * @see @ref SceneData::parentsAsArray() + * @see @ref SceneData::parentsAsArray(), @ref SceneData::childrenFor() */ Parent = 1, @@ -129,7 +129,9 @@ enum class SceneField: UnsignedInt { * only for a subset of transformed objects, useful for example when only * certain objects have these properties animated. * @see @ref SceneData::transformations2DAsArray(), - * @ref SceneData::transformations3DAsArray() + * @ref SceneData::transformations3DAsArray(), + * @ref SceneData::transformation2DFor(), + * @ref SceneData::transformation3DFor() */ Transformation, @@ -149,8 +151,12 @@ enum class SceneField: UnsignedInt { * details. * @see @ref SceneData::transformations2DAsArray(), * @ref SceneData::transformations3DAsArray(), + * @ref SceneData::transformation2DFor(), + * @ref SceneData::transformation3DFor(), * @ref SceneData::translationsRotationsScalings2DAsArray(), - * @ref SceneData::translationsRotationsScalings3DAsArray() + * @ref SceneData::translationsRotationsScalings3DAsArray(), + * @ref SceneData::translationRotationScaling2DFor(), + * @ref SceneData::translationRotationScaling3DFor() */ Translation, @@ -170,8 +176,12 @@ enum class SceneField: UnsignedInt { * details. * @see @ref SceneData::transformations2DAsArray(), * @ref SceneData::transformations3DAsArray(), + * @ref SceneData::transformation2DFor(), + * @ref SceneData::transformation3DFor(), * @ref SceneData::translationsRotationsScalings2DAsArray(), - * @ref SceneData::translationsRotationsScalings3DAsArray() + * @ref SceneData::translationsRotationsScalings3DAsArray(), + * @ref SceneData::translationRotationScaling2DFor(), + * @ref SceneData::translationRotationScaling3DFor() */ Rotation, @@ -191,8 +201,12 @@ enum class SceneField: UnsignedInt { * details. * @see @ref SceneData::transformations2DAsArray(), * @ref SceneData::transformations3DAsArray(), + * @ref SceneData::transformation2DFor(), + * @ref SceneData::transformation3DFor(), * @ref SceneData::translationsRotationsScalings2DAsArray(), - * @ref SceneData::translationsRotationsScalings3DAsArray() + * @ref SceneData::translationsRotationsScalings3DAsArray(), + * @ref SceneData::translationRotationScaling2DFor(), + * @ref SceneData::translationRotationScaling3DFor() */ Scaling, @@ -208,7 +222,8 @@ enum class SceneField: UnsignedInt { * required. If present, both should share the same object mapping view. * Objects with multiple meshes then have the Nth mesh associated with the * Nth material. - * @see @ref SceneData::meshesMaterialsAsArray() + * @see @ref SceneData::meshesMaterialsAsArray(), + * @ref SceneData::meshesMaterialsFor() */ Mesh, @@ -219,7 +234,8 @@ enum class SceneField: UnsignedInt { * but can be also any of @relativeref{SceneFieldType,Byte} or * @relativeref{SceneFieldType,Short}. Expected to share the * object mapping view with @ref SceneField::Mesh. - * @see @ref SceneData::meshesMaterialsAsArray() + * @see @ref SceneData::meshesMaterialsAsArray(), + * @ref SceneData::meshesMaterialsFor() */ MeshMaterial, @@ -230,7 +246,7 @@ enum class SceneField: UnsignedInt { * @relativeref{SceneFieldType,UnsignedByte} or * @relativeref{SceneFieldType,UnsignedShort}. An object can have multiple * lights associated. - * @see @ref SceneData::lightsAsArray() + * @see @ref SceneData::lightsAsArray(), @ref SceneData::lightsFor() */ Light, @@ -241,7 +257,7 @@ enum class SceneField: UnsignedInt { * @relativeref{SceneFieldType,UnsignedByte} or * @relativeref{SceneFieldType,UnsignedShort}. An object can have multiple * cameras associated. - * @see @ref SceneData::camerasAsArray() + * @see @ref SceneData::camerasAsArray(), @ref SceneData::camerasFor() */ Camera, @@ -252,7 +268,7 @@ enum class SceneField: UnsignedInt { * @relativeref{SceneFieldType,UnsignedByte} or * @relativeref{SceneFieldType,UnsignedShort}. An object can have multiple * skins associated. - * @see @ref SceneData::skinsAsArray() + * @see @ref SceneData::skinsAsArray(), @ref SceneData::skinsFor() */ Skin, @@ -1342,7 +1358,7 @@ class MAGNUM_TRADE_EXPORT SceneData { * is larger than the max representable 32-bit value, this * function can't be used, only an appropriately typed * @ref field(SceneField) const. - * @see @ref parentsInto(), @ref hasField() + * @see @ref parentsInto(), @ref hasField(), @ref childrenFor() */ Containers::Array parentsAsArray() const; @@ -1385,7 +1401,7 @@ class MAGNUM_TRADE_EXPORT SceneData { * to 2D, otherwise you're supposed to use * @ref transformations3DAsArray(). * @see @ref transformations2DInto(), @ref hasField(), - * @ref fieldType(SceneField) const + * @ref fieldType(SceneField) const, @ref transformation2DFor() */ Containers::Array transformations2DAsArray() const; @@ -1431,7 +1447,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * 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 + * @ref fieldType(SceneField) const, + * @ref translationRotationScaling2DFor() */ Containers::Array> translationsRotationsScalings2DAsArray() const; @@ -1476,7 +1493,7 @@ class MAGNUM_TRADE_EXPORT SceneData { * to 3D, otherwise you're supposed to use * @ref transformations2DAsArray(). * @see @ref transformations3DInto(), @ref hasField(), - * @ref fieldType(SceneField) const + * @ref fieldType(SceneField) const, @ref transformation3DFor() */ Containers::Array transformations3DAsArray() const; @@ -1522,7 +1539,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * 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 + * @ref fieldType(SceneField) const, + * @ref translationRotationScaling3DFor() */ Containers::Array> translationsRotationsScalings3DAsArray() const; @@ -1563,7 +1581,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * in a newly-allocated array. The @ref SceneField::Mesh field is * expected to exist, if @ref SceneField::MeshMaterial isn't present, * the second returned values are all @cpp -1 @ce. - * @see @ref meshesMaterialsInto(), @ref hasField() + * @see @ref meshesMaterialsInto(), @ref hasField(), + * @ref meshesMaterialsFor() */ Containers::Array> meshesMaterialsAsArray() const; @@ -1600,7 +1619,7 @@ class MAGNUM_TRADE_EXPORT SceneData { * @ref SceneField::Light as the argument that converts the field from * an arbitrary underlying type and returns it in a newly-allocated * array. The field is expected to exist. - * @see @ref lightsInto(), @ref hasField() + * @see @ref lightsInto(), @ref hasField(), @ref lightsFor() */ Containers::Array lightsAsArray() const; @@ -1636,7 +1655,7 @@ class MAGNUM_TRADE_EXPORT SceneData { * @ref SceneField::Camera as the argument that converts the field from * an arbitrary underlying type and returns it in a newly-allocated * array. The field is expected to exist. - * @see @ref camerasInto(), @ref hasField() + * @see @ref camerasInto(), @ref hasField(), @ref camerasFor() */ Containers::Array camerasAsArray() const; @@ -1672,7 +1691,7 @@ class MAGNUM_TRADE_EXPORT SceneData { * @ref SceneField::Skin as the argument that converts the field from * an arbitrary underlying type and returns it in a newly-allocated * array. The field is expected to exist. - * @see @ref skinsInto(), @ref hasField() + * @see @ref skinsInto(), @ref hasField(), @ref skinsFor() */ Containers::Array skinsAsArray() const; @@ -1700,6 +1719,209 @@ class MAGNUM_TRADE_EXPORT SceneData { */ std::size_t skinsInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** + * @brief Children for given object + * @m_since_latest + * + * Looks up @p object in the object mapping array for + * @ref SceneField::Parent and returns a list of all object IDs that + * have it listed as the parent. The lookup is done in an + * @f$ \mathcal{O}(m + n) @f$ complexity with @f$ m @f$ being the field + * count and @f$ n @f$ the size of the parent field, thus for + * retrieving parent/child info for many objects it's recommended to + * access the field data directly with @ref parentsAsArray() and + * related APIs. + * + * If the @ref SceneField::Parent field doesn't exist or there are no + * objects which would have @p object listed as their parent, returns + * an empty array. Pass @cpp -1 @ce to get a list of top-level objects. + * + * The @p object is expected to be less than @ref objectCount(). + */ + Containers::Array childrenFor(Int object) const; + + /** + * @brief 2D transformation for given object + * @m_since_latest + * + * Looks up the @ref SceneField::Transformation field for @p object or + * combines it from a @ref SceneField::Translation, + * @relativeref{SceneField,Rotation} and + * @relativeref{SceneField,Scaling} in the same way as + * @ref transformations2DAsArray(). The lookup is done in an + * @f$ \mathcal{O}(m + n) @f$ complexity with @f$ m @f$ being the field + * count and @f$ n @f$ the size of the transformation field, thus for + * retrieving transformation info for many objects it's recommended to + * access the field data directly with @ref transformations2DAsArray() + * and related APIs. + * + * If neither @ref SceneField::Transformation nor any of + * @ref SceneField::Translation, @relativeref{SceneField,Rotation} or + * @relativeref{SceneField,Scaling} is present, the fields represent a + * 3D transformation or there's no transformation for @p object, + * returns @ref Containers::NullOpt. + * + * The @p object is expected to be less than @ref objectCount(). + * @see @ref translationRotationScaling2DFor() + */ + Containers::Optional transformation2DFor(UnsignedInt object) const; + + /** + * @brief 2D translation, rotation and scaling for given object + * @m_since_latest + * + * Looks up the @ref SceneField::Translation, + * @relativeref{SceneField,Rotation} and + * @relativeref{SceneField,Scaling} fields for @p object. The lookup + * is done in an @f$ \mathcal{O}(m + n) @f$ complexity with @f$ m @f$ + * being the field count and @f$ n @f$ the size of the transformation + * field, thus for retrieving transformation info for many objects it's + * recommended to access the field data directly with + * @ref translationsRotationsScalings2DAsArray() and related APIs. + * + * 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). If neither of those fields is + * present, if any of the fields represents a 3D transformation or if + * there's no transformation for @p object, returns + * @ref Containers::NullOpt. + * + * The @p object is expected to be less than @ref objectCount(). + * @see @ref transformation2DFor() + */ + Containers::Optional> translationRotationScaling2DFor(UnsignedInt object) const; + + /** + * @brief 3D transformation for given object + * @m_since_latest + * + * Looks up the @ref SceneField::Transformation field for @p object or + * combines it from a @ref SceneField::Translation, + * @relativeref{SceneField,Rotation} and + * @relativeref{SceneField,Scaling} in the same way as + * @ref transformations3DAsArray(). The lookup is done in an + * @f$ \mathcal{O}(m + n) @f$ complexity with @f$ m @f$ being the field + * count and @f$ n @f$ the size of the transformation field, thus for + * retrieving transformation info for many objects it's recommended to + * access the field data directly with @ref transformations3DAsArray() + * and related APIs. + * + * If neither @ref SceneField::Transformation nor any of + * @ref SceneField::Translation, @relativeref{SceneField,Rotation} or + * @relativeref{SceneField,Scaling} is present, the fields represent a + * 2D transformation or there's no transformation for @p object, + * returns @ref Containers::NullOpt. + * + * The @p object is expected to be less than @ref objectCount(). + * @see @ref translationRotationScaling3DFor() + */ + Containers::Optional transformation3DFor(UnsignedInt object) const; + + /** + * @brief 3D translation, rotation and scaling for given object + * @m_since_latest + * + * Looks up the @ref SceneField::Translation, + * @relativeref{SceneField,Rotation} and + * @relativeref{SceneField,Scaling} fields for @p object. The lookup + * is done in an @f$ \mathcal{O}(m + n) @f$ complexity with @f$ m @f$ + * being the field count and @f$ n @f$ the size of the transformation + * field, thus for retrieving transformation info for many objects it's + * recommended to access the field data directly with + * @ref translationsRotationsScalings3DAsArray() and related APIs. + * + * 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). If neither of those fields is + * present, if any of the fields represents a 2D transformation or if + * there's no transformation for @p object, returns + * @ref Containers::NullOpt. + * + * The @p object is expected to be less than @ref objectCount(). + * @see @ref transformation3DFor() + */ + Containers::Optional> translationRotationScaling3DFor(UnsignedInt object) const; + + /** + * @brief Meshes and materials for given object + * @m_since_latest + * + * Looks up all @ref SceneField::Mesh and @ref SceneField::MeshMaterial + * @relativeref{SceneField,Scaling} fields for @p object. The lookup + * is done in an @f$ \mathcal{O}(m + n) @f$ complexity with @f$ m @f$ + * being the field count and @f$ n @f$ the size of the mesh field, thus + * for retrieving mesh info for many objects it's recommended to access + * the field data directly with @ref meshesMaterialsAsArray() and + * related APIs. + * + * If the @ref SceneField::MeshMaterial field is not present, the + * second returned value is always @cpp -1 @ce. If + * @ref SceneField::Mesh is not present or if there's no mesh for + * @p object, returns an empty array. + * + * The @p object is expected to be less than @ref objectCount(). + */ + Containers::Array> meshesMaterialsFor(UnsignedInt object) const; + + /** + * @brief Lights for given object + * @m_since_latest + * + * Looks up all @ref SceneField::Light fields for @p object. The lookup + * is done in an @f$ \mathcal{O}(m + n) @f$ complexity with @f$ m @f$ + * being the field count and @f$ n @f$ the size of the light field, + * thus for retrieving light info for many objects it's recommended to + * access the field data directly with @ref lightsAsArray() and related + * APIs. + * + * If the @ref SceneField::Light field is not present or if there's no + * light for @p object, returns an empty array. + * + * The @p object is expected to be less than @ref objectCount(). + */ + Containers::Array lightsFor(UnsignedInt object) const; + + /** + * @brief Cameras for given object + * @m_since_latest + * + * Looks up all @ref SceneField::Camera fields for @p object. The + * lookup is done in an @f$ \mathcal{O}(m + n) @f$ complexity with + * @f$ m @f$ being the field count and @f$ n @f$ the size of the camera + * field, thus for retrieving camera info for many objects it's + * recommended to access the field data directly with + * @ref camerasAsArray() and related APIs. + * + * If the @ref SceneField::Camera field is not present or if there's no + * camera for @p object, returns an empty array. + * + * The @p object is expected to be less than @ref objectCount(). + */ + Containers::Array camerasFor(UnsignedInt object) const; + + /** + * @brief Skins for given object + * @m_since_latest + * + * Looks up all @ref SceneField::Skin fields for @p object. The lookup + * is done in an @f$ \mathcal{O}(m + n) @f$ complexity with @f$ m @f$ + * being the field count and @f$ n @f$ the size of the skin field, thus + * for retrieving skin info for many objects it's recommended to access + * the field data directly with @ref skinsAsArray() and related APIs. + * + * If the @ref SceneField::Skin field is not present or if there's no + * skin for @p object, returns an empty array. + * + * The @p object is expected to be less than @ref objectCount(). + */ + Containers::Array skinsFor(UnsignedInt object) const; + /** * @brief Release field data storage * @m_since_latest @@ -1746,6 +1968,11 @@ class MAGNUM_TRADE_EXPORT SceneData { /* Internal helper that doesn't assert, unlike fieldId() */ UnsignedInt fieldFor(SceneField name) const; + /* Returns the offset at which `object` is for field at index `id`, or + the end offset if the object is not found. The returned offset can + be then passed to fieldData{Object,Field}ViewInternal(). */ + std::size_t fieldFor(const SceneFieldData& field, std::size_t offset, UnsignedInt object) const; + /* Like objects() / field(), but returning just a 1D view, sliced from offset to offset + size. The parameterless overloads are equal to offset = 0 and size = field.size(). */ @@ -1760,8 +1987,8 @@ 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 std::size_t findTransformFields(UnsignedInt& transformationFieldId, UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId, UnsignedInt* fieldWithObjectMappingDestination = nullptr) const; + MAGNUM_TRADE_LOCAL std::size_t findTranslationRotationScalingFields(UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId, UnsignedInt* fieldWithObjectMappingDestination = nullptr) 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; diff --git a/src/Magnum/Trade/Test/SceneDataTest.cpp b/src/Magnum/Trade/Test/SceneDataTest.cpp index b09ba66e8..797b0d589 100644 --- a/src/Magnum/Trade/Test/SceneDataTest.cpp +++ b/src/Magnum/Trade/Test/SceneDataTest.cpp @@ -25,6 +25,7 @@ #include #include +#include #include #include #include @@ -150,6 +151,25 @@ struct SceneDataTest: TestSuite::Tester { void fieldWrongType(); void fieldWrongArrayAccess(); + /* Different object types checked just for the childrenFor(), other APIs + use the same helper */ + template void childrenFor(); + void transformation2DFor(); + void transformation2DForTRS(); + template void transformation2DForBut3DType(); + template void transformation2DForBut3DTypeTRS(); + void transformation3DFor(); + void transformation3DForTRS(); + template void transformation3DForBut2DType(); + template void transformation3DForBut2DTypeTRS(); + void meshesMaterialsFor(); + void lightsFor(); + void camerasFor(); + void skinsFor(); + + void fieldForFieldMissing(); + void fieldForInvalidObject(); + void releaseFieldData(); void releaseData(); }; @@ -340,6 +360,34 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::fieldWrongType, &SceneDataTest::fieldWrongArrayAccess, + &SceneDataTest::childrenFor, + &SceneDataTest::childrenFor, + &SceneDataTest::childrenFor, + &SceneDataTest::childrenFor, + &SceneDataTest::transformation2DFor, + &SceneDataTest::transformation2DForTRS, + &SceneDataTest::transformation2DForBut3DType, + &SceneDataTest::transformation2DForBut3DType, + &SceneDataTest::transformation2DForBut3DType, + &SceneDataTest::transformation2DForBut3DType, + &SceneDataTest::transformation2DForBut3DTypeTRS, + &SceneDataTest::transformation2DForBut3DTypeTRS, + &SceneDataTest::transformation3DFor, + &SceneDataTest::transformation3DForTRS, + &SceneDataTest::transformation3DForBut2DType, + &SceneDataTest::transformation3DForBut2DType, + &SceneDataTest::transformation3DForBut2DType, + &SceneDataTest::transformation3DForBut2DType, + &SceneDataTest::transformation3DForBut2DTypeTRS, + &SceneDataTest::transformation3DForBut2DTypeTRS, + &SceneDataTest::meshesMaterialsFor, + &SceneDataTest::lightsFor, + &SceneDataTest::camerasFor, + &SceneDataTest::skinsFor, + + &SceneDataTest::fieldForFieldMissing, + &SceneDataTest::fieldForInvalidObject, + &SceneDataTest::releaseFieldData, &SceneDataTest::releaseData}); } @@ -3992,6 +4040,505 @@ void SceneDataTest::fieldWrongArrayAccess() { "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n"); } +template void SceneDataTest::childrenFor() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + T object; + Int parent; + } fields[]{ + {4, -1}, + {3, 0}, + {2, 1}, + {1, 0}, + {5, 0}, + {0, -1}, + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{Implementation::sceneObjectTypeFor(), 7, {}, fields, { + SceneFieldData{SceneField::Parent, view.slice(&Field::object), view.slice(&Field::parent)} + }}; + + /* Just one child */ + CORRADE_COMPARE_AS(scene.childrenFor(3), + Containers::arrayView({2}), + TestSuite::Compare::Container); + + /* More */ + CORRADE_COMPARE_AS(scene.childrenFor(-1), + Containers::arrayView({4, 0}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.childrenFor(4), + Containers::arrayView({3, 1, 5}), + TestSuite::Compare::Container); + + /* Object that is present in the parent array but has no children */ + CORRADE_COMPARE_AS(scene.childrenFor(5), + Containers::arrayView({}), + TestSuite::Compare::Container); + + /* Object that is not in the parent array at all */ + CORRADE_COMPARE_AS(scene.childrenFor(6), + Containers::arrayView({}), + TestSuite::Compare::Container); +} + +void SceneDataTest::transformation2DFor() { + const struct Field { + UnsignedInt object; + Matrix3 transformation; + } fields[] { + {1, Matrix3::translation({3.0f, 2.0f})*Matrix3::scaling({1.5f, 2.0f})}, + {0, Matrix3::rotation(35.0_degf)}, + {4, Matrix3::translation({3.0f, 2.0f})*Matrix3::rotation(35.0_degf)}, + {1, Matrix3::translation({1.0f, 2.0f})} /* duplicate, ignored */ + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Transformation, view.slice(&Field::object), view.slice(&Field::transformation)} + }}; + + CORRADE_COMPARE(scene.transformation2DFor(4), + Matrix3::translation({3.0f, 2.0f})*Matrix3::rotation(35.0_degf)); + CORRADE_COMPARE(scene.transformation2DFor(0), + Matrix3::Matrix3::Matrix3::Matrix3::rotation(35.0_degf)); + + /* Duplicate entries -- only the first one gets used, it doesn't traverse + further */ + CORRADE_COMPARE(scene.transformation2DFor(1), + Matrix3::translation({3.0f, 2.0f})*Matrix3::scaling({1.5f, 2.0f})); + + /* Object that's not in the array at all */ + CORRADE_COMPARE(scene.transformation2DFor(2), + Containers::NullOpt); +} + +void SceneDataTest::transformation2DForTRS() { + const struct Field { + UnsignedInt object; + Vector2 translation; + Complex rotation; + Vector2 scaling; + } fields[] { + {1, {3.0f, 2.0f}, {}, {1.5f, 2.0f}}, + {0, {}, Complex::rotation(35.0_degf), {1.0f, 1.0f}}, + {4, {3.0f, 2.0f}, Complex::rotation(35.0_degf), {1.0f, 1.0f}}, + {1, {1.0f, 2.0f}, {}, {1.0f, 1.0f}} /* duplicate, ignored */ + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Translation, view.slice(&Field::object), view.slice(&Field::translation)}, + SceneFieldData{SceneField::Rotation, view.slice(&Field::object), view.slice(&Field::rotation)}, + SceneFieldData{SceneField::Scaling, view.slice(&Field::object), view.slice(&Field::scaling)} + }}; + + CORRADE_COMPARE(scene.transformation2DFor(4), + Matrix3::translation({3.0f, 2.0f})*Matrix3::rotation(35.0_degf)); + CORRADE_COMPARE(scene.translationRotationScaling2DFor(4), + Containers::triple(Vector2{3.0f, 2.0f}, Complex::rotation(35.0_degf), Vector2{1.0f})); + CORRADE_COMPARE(scene.transformation2DFor(0), + Matrix3::rotation(35.0_degf)); + CORRADE_COMPARE(scene.translationRotationScaling2DFor(0), + Containers::triple(Vector2{}, Complex::rotation(35.0_degf), Vector2{1.0f})); + + /* Duplicate entries -- only the first one gets used, it doesn't traverse + further */ + CORRADE_COMPARE(scene.transformation2DFor(1), + Matrix3::translation({3.0f, 2.0f})*Matrix3::scaling({1.5f, 2.0f})); + CORRADE_COMPARE(scene.translationRotationScaling2DFor(1), + Containers::triple(Vector2{3.0f, 2.0f}, Complex{}, Vector2{1.5f, 2.0f})); + + /* Object that's not in the array at all */ + CORRADE_COMPARE(scene.transformation2DFor(2), + Containers::NullOpt); + CORRADE_COMPARE(scene.translationRotationScaling2DFor(2), + Containers::NullOpt); +} + +template void SceneDataTest::transformation2DForBut3DType() { + setTestCaseTemplateName(NameTraits::name()); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneData scene{SceneObjectType::UnsignedInt, 1, nullptr, { + SceneFieldData{SceneField::Transformation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor::type(), nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.transformation2DFor(0); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Trade::SceneData::transformation2DFor(): field has a 3D transformation type Trade::SceneFieldType::{}\n", NameTraits::name())); +} + +template void SceneDataTest::transformation2DForBut3DTypeTRS() { + setTestCaseTemplateName(NameTraits::name()); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneData translation{SceneObjectType::UnsignedInt, 1, nullptr, { + SceneFieldData{SceneField::Translation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + }}; + SceneData rotation{SceneObjectType::UnsignedInt, 1, nullptr, { + SceneFieldData{SceneField::Rotation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + }}; + SceneData scaling{SceneObjectType::UnsignedInt, 1, nullptr, { + SceneFieldData{SceneField::Scaling, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + translation.transformation2DFor(0); + translation.translationRotationScaling2DFor(0); + rotation.transformation2DFor(0); + rotation.translationRotationScaling2DFor(0); + scaling.transformation2DFor(0); + scaling.translationRotationScaling2DFor(0); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Trade::SceneData::transformation2DFor(): field has a 3D translation type Trade::SceneFieldType::{0}\n" + "Trade::SceneData::translationRotationScaling2DFor(): field has a 3D translation type Trade::SceneFieldType::{0}\n" + "Trade::SceneData::transformation2DFor(): field has a 3D rotation type Trade::SceneFieldType::{1}\n" + "Trade::SceneData::translationRotationScaling2DFor(): field has a 3D rotation type Trade::SceneFieldType::{1}\n" + "Trade::SceneData::transformation2DFor(): field has a 3D scaling type Trade::SceneFieldType::{0}\n" + "Trade::SceneData::translationRotationScaling2DFor(): field has a 3D scaling type Trade::SceneFieldType::{0}\n", NameTraits>::name(), NameTraits>::name())); +} + +void SceneDataTest::transformation3DFor() { + const struct Field { + UnsignedInt object; + Matrix4 transformation; + } fields[] { + {1, Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::scaling({1.5f, 2.0f, 4.5f})}, + {0, Matrix4::rotationX(35.0_degf)}, + {4, Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::rotationX(35.0_degf)}, + {1, Matrix4::translation({1.0f, 2.0f, 3.0f})} /* duplicate, ignored */ + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Transformation, view.slice(&Field::object), view.slice(&Field::transformation)} + }}; + + CORRADE_COMPARE(scene.transformation3DFor(4), + Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::rotationX(35.0_degf)); + CORRADE_COMPARE(scene.transformation3DFor(0), + Matrix4::rotationX(35.0_degf)); + + /* Duplicate entries -- only the first one gets used, it doesn't traverse + further */ + CORRADE_COMPARE(scene.transformation3DFor(1), + Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::scaling({1.5f, 2.0f, 4.5f})); + + /* Object that's not in the array at all */ + CORRADE_COMPARE(scene.transformation3DFor(2), + Containers::NullOpt); +} + +void SceneDataTest::transformation3DForTRS() { + const struct Field { + UnsignedInt object; + Vector3 translation; + Quaternion rotation; + Vector3 scaling; + } fields[] { + {1, {3.0f, 2.0f, 1.0f}, {}, {1.5f, 2.0f, 4.5f}}, + {0, {}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), {1.0f, 1.0f, 1.0f}}, + {4, {3.0f, 2.0f, 1.0f}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), {1.0f, 1.0f, 1.0f}}, + {1, {1.0f, 2.0f, 3.0f}, Quaternion{}, Vector3{1.0f}} /* duplicate, ignored */ + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Translation, view.slice(&Field::object), view.slice(&Field::translation)}, + SceneFieldData{SceneField::Rotation, view.slice(&Field::object), view.slice(&Field::rotation)}, + SceneFieldData{SceneField::Scaling, view.slice(&Field::object), view.slice(&Field::scaling)} + }}; + + CORRADE_COMPARE(scene.transformation3DFor(4), + Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::rotationX(35.0_degf)); + CORRADE_COMPARE(scene.translationRotationScaling3DFor(4), + Containers::triple(Vector3{3.0f, 2.0f, 1.0f}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), Vector3{1.0f})); + CORRADE_COMPARE(scene.transformation3DFor(0), + Matrix4::rotationX(35.0_degf)); + CORRADE_COMPARE(scene.translationRotationScaling3DFor(0), + Containers::triple(Vector3{}, Quaternion::rotation(35.0_degf, Vector3::xAxis()), Vector3{1.0f})); + + /* Duplicate entries -- only the first one gets used, it doesn't traverse + further */ + CORRADE_COMPARE(scene.transformation3DFor(1), + Matrix4::translation({3.0f, 2.0f, 1.0f})*Matrix4::scaling({1.5f, 2.0f, 4.5f})); + CORRADE_COMPARE(scene.translationRotationScaling3DFor(1), + Containers::triple(Vector3{3.0f, 2.0f, 1.0f}, Quaternion{}, Vector3{1.5f, 2.0f, 4.5f})); + + /* Object that's not in the array at all */ + CORRADE_COMPARE(scene.transformation3DFor(2), + Containers::NullOpt); + CORRADE_COMPARE(scene.translationRotationScaling3DFor(2), + Containers::NullOpt); +} + +template void SceneDataTest::transformation3DForBut2DType() { + setTestCaseTemplateName(NameTraits::name()); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneData scene{SceneObjectType::UnsignedInt, 1, nullptr, { + SceneFieldData{SceneField::Transformation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor::type(), nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.transformation3DFor(0); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Trade::SceneData::transformation3DFor(): field has a 2D transformation type Trade::SceneFieldType::{}\n", NameTraits::name())); +} + +template void SceneDataTest::transformation3DForBut2DTypeTRS() { + setTestCaseTemplateName(NameTraits::name()); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneData translation{SceneObjectType::UnsignedInt, 1, nullptr, { + SceneFieldData{SceneField::Translation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + }}; + SceneData rotation{SceneObjectType::UnsignedInt, 1, nullptr, { + SceneFieldData{SceneField::Rotation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + }}; + SceneData scaling{SceneObjectType::UnsignedInt, 1, nullptr, { + SceneFieldData{SceneField::Scaling, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + translation.transformation3DFor(0); + translation.translationRotationScaling3DFor(0); + rotation.transformation3DFor(0); + rotation.translationRotationScaling3DFor(0); + scaling.transformation3DFor(0); + scaling.translationRotationScaling3DFor(0); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Trade::SceneData::transformation3DFor(): field has a 2D translation type Trade::SceneFieldType::{0}\n" + "Trade::SceneData::translationRotationScaling3DFor(): field has a 2D translation type Trade::SceneFieldType::{0}\n" + "Trade::SceneData::transformation3DFor(): field has a 2D rotation type Trade::SceneFieldType::{1}\n" + "Trade::SceneData::translationRotationScaling3DFor(): field has a 2D rotation type Trade::SceneFieldType::{1}\n" + "Trade::SceneData::transformation3DFor(): field has a 2D scaling type Trade::SceneFieldType::{0}\n" + "Trade::SceneData::translationRotationScaling3DFor(): field has a 2D scaling type Trade::SceneFieldType::{0}\n", NameTraits>::name(), NameTraits>::name())); +} + +void SceneDataTest::meshesMaterialsFor() { + struct Field { + UnsignedInt object; + UnsignedInt mesh; + Int meshMaterial; + } fields[]{ + {4, 1, -1}, + {1, 3, 0}, + {2, 4, 1}, + {2, 5, -1}, + {2, 1, 0}, + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)}, + SceneFieldData{SceneField::MeshMaterial, view.slice(&Field::object), view.slice(&Field::meshMaterial)} + }}; + + /* Just one */ + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(1), + (Containers::arrayView>({{3, 0}})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(4), + (Containers::arrayView>({{1, -1}})), + TestSuite::Compare::Container); + + /* More */ + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(2), + (Containers::arrayView>({ + {4, 1}, {5, -1}, {1, 0} + })), TestSuite::Compare::Container); + + /* Object that is not in the array at all */ + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(6), + (Containers::arrayView>({})), + TestSuite::Compare::Container); +} + +void SceneDataTest::lightsFor() { + struct Field { + UnsignedInt object; + UnsignedInt light; + } fields[]{ + {4, 1}, + {1, 3}, + {2, 4}, + {2, 5} + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Light, view.slice(&Field::object), view.slice(&Field::light)} + }}; + + /* Just one */ + CORRADE_COMPARE_AS(scene.lightsFor(1), + (Containers::arrayView({3})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.lightsFor(4), + (Containers::arrayView({1})), + TestSuite::Compare::Container); + + /* More */ + CORRADE_COMPARE_AS(scene.lightsFor(2), + (Containers::arrayView({ + 4, 5 + })), TestSuite::Compare::Container); + + /* Object that is not in the array at all */ + CORRADE_COMPARE_AS(scene.lightsFor(6), + (Containers::arrayView({})), + TestSuite::Compare::Container); +} + +void SceneDataTest::camerasFor() { + struct Field { + UnsignedInt object; + UnsignedInt camera; + } fields[]{ + {4, 1}, + {1, 3}, + {2, 4}, + {2, 5} + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Camera, view.slice(&Field::object), view.slice(&Field::camera)} + }}; + + /* Just one */ + CORRADE_COMPARE_AS(scene.camerasFor(1), + (Containers::arrayView({3})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.camerasFor(4), + (Containers::arrayView({1})), + TestSuite::Compare::Container); + + /* More */ + CORRADE_COMPARE_AS(scene.camerasFor(2), + (Containers::arrayView({ + 4, 5 + })), TestSuite::Compare::Container); + + /* Object that is not in the array at all */ + CORRADE_COMPARE_AS(scene.camerasFor(6), + (Containers::arrayView({})), + TestSuite::Compare::Container); +} + +void SceneDataTest::skinsFor() { + struct Field { + UnsignedInt object; + UnsignedInt skin; + } fields[]{ + {4, 1}, + {1, 3}, + {2, 4}, + {2, 5} + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Skin, view.slice(&Field::object), view.slice(&Field::skin)} + }}; + + /* Just one */ + CORRADE_COMPARE_AS(scene.skinsFor(1), + (Containers::arrayView({3})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.skinsFor(4), + (Containers::arrayView({1})), + TestSuite::Compare::Container); + + /* More */ + CORRADE_COMPARE_AS(scene.skinsFor(2), + (Containers::arrayView({ + 4, 5 + })), TestSuite::Compare::Container); + + /* Object that is not in the array at all */ + CORRADE_COMPARE_AS(scene.skinsFor(6), + (Containers::arrayView({})), + TestSuite::Compare::Container); +} + +void SceneDataTest::fieldForFieldMissing() { + SceneData scene{SceneObjectType::UnsignedInt, 7, nullptr, {}}; + + CORRADE_COMPARE_AS(scene.childrenFor(6), + Containers::arrayView({}), + TestSuite::Compare::Container); + CORRADE_COMPARE(scene.transformation2DFor(6), Containers::NullOpt); + CORRADE_COMPARE(scene.translationRotationScaling2DFor(6), Containers::NullOpt); + CORRADE_COMPARE(scene.transformation3DFor(6), Containers::NullOpt); + CORRADE_COMPARE(scene.translationRotationScaling3DFor(6), Containers::NullOpt); + CORRADE_COMPARE_AS(scene.meshesMaterialsFor(6), + (Containers::arrayView>({})), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.lightsFor(6), + Containers::arrayView({}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.camerasFor(6), + Containers::arrayView({}), + TestSuite::Compare::Container); + CORRADE_COMPARE_AS(scene.skinsFor(6), + Containers::arrayView({}), + TestSuite::Compare::Container); +} + +void SceneDataTest::fieldForInvalidObject() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneData scene{SceneObjectType::UnsignedInt, 7, nullptr, {}}; + + std::ostringstream out; + Error redirectError{&out}; + scene.childrenFor(-2); + scene.childrenFor(7); + scene.transformation2DFor(7); + scene.translationRotationScaling2DFor(7); + scene.transformation3DFor(7); + scene.translationRotationScaling3DFor(7); + scene.meshesMaterialsFor(7); + scene.lightsFor(7); + scene.camerasFor(7); + scene.skinsFor(7); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::childrenFor(): object -2 out of bounds for 7 objects\n" + "Trade::SceneData::childrenFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::transformation2DFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::translationRotationScaling2DFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::transformation3DFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::translationRotationScaling3DFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::meshesMaterialsFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::lightsFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::camerasFor(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::skinsFor(): object 7 out of bounds for 7 objects\n"); +} + void SceneDataTest::releaseFieldData() { struct Field { UnsignedByte object;