diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 01ae0026d..e1904cbe2 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -1588,7 +1588,27 @@ std::size_t SceneData::fieldFor(const SceneFieldData& field, const std::size_t o else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ } -Containers::Array SceneData::childrenFor(Int object) const { +Containers::Optional SceneData::parentFor(const UnsignedInt object) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::parentFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + const UnsignedInt fieldId = fieldFor(SceneField::Parent); + if(fieldId == ~UnsignedInt{}) return {}; + + const SceneFieldData& field = _fields[fieldId]; + const std::size_t offset = fieldFor(field, 0, object); + if(offset == field._size) return {}; + + Int index[1]; + parentsIntoInternal(fieldId, offset, index); + if(*index == -1) return -1; + + UnsignedInt parent[1]; + objectsIntoInternal(fieldId, *index, parent); + return Int(*parent); +} + +Containers::Array SceneData::childrenFor(const Int object) const { CORRADE_ASSERT(object >= -1 && object < Long(_objectCount), "Trade::SceneData::childrenFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index f46295518..3e7b30544 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -102,7 +102,8 @@ 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(), @ref SceneData::childrenFor() + * @see @ref SceneData::parentsAsArray(), @ref SceneData::parentFor(), + * @ref SceneData::childrenFor() */ Parent = 1, @@ -1358,7 +1359,8 @@ 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(), @ref childrenFor() + * @see @ref parentsInto(), @ref hasField(), @ref parentFor(), + * @ref childrenFor() */ Containers::Array parentsAsArray() const; @@ -1719,6 +1721,26 @@ class MAGNUM_TRADE_EXPORT SceneData { */ std::size_t skinsInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; + /** + * @brief Parent for given object + * @m_since_latest + * + * Looks up the @ref SceneField::Parent field 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 parent field, + * thus for retrieving parent 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 is not present or if there's no + * parent for @p object, returns @ref Containers::NullOpt. If @p object + * is top-level, returns @cpp -1 @ce. + * + * The @p object is expected to be less than @ref objectCount(). + * @see @ref childrenFor() + */ + Containers::Optional parentFor(UnsignedInt object) const; + /** * @brief Children for given object * @m_since_latest @@ -1737,6 +1759,7 @@ class MAGNUM_TRADE_EXPORT SceneData { * 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(). + * @see @ref parentFor() */ Containers::Array childrenFor(Int object) const; diff --git a/src/Magnum/Trade/Test/SceneDataTest.cpp b/src/Magnum/Trade/Test/SceneDataTest.cpp index 797b0d589..17e268625 100644 --- a/src/Magnum/Trade/Test/SceneDataTest.cpp +++ b/src/Magnum/Trade/Test/SceneDataTest.cpp @@ -151,9 +151,10 @@ struct SceneDataTest: TestSuite::Tester { void fieldWrongType(); void fieldWrongArrayAccess(); - /* Different object types checked just for the childrenFor(), other APIs + /* Different object types checked just for the parentFor(), other APIs use the same helper */ - template void childrenFor(); + template void parentFor(); + void childrenFor(); void transformation2DFor(); void transformation2DForTRS(); template void transformation2DForBut3DType(); @@ -360,10 +361,11 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::fieldWrongType, &SceneDataTest::fieldWrongArrayAccess, - &SceneDataTest::childrenFor, - &SceneDataTest::childrenFor, - &SceneDataTest::childrenFor, - &SceneDataTest::childrenFor, + &SceneDataTest::parentFor, + &SceneDataTest::parentFor, + &SceneDataTest::parentFor, + &SceneDataTest::parentFor, + &SceneDataTest::childrenFor, &SceneDataTest::transformation2DFor, &SceneDataTest::transformation2DForTRS, &SceneDataTest::transformation2DForBut3DType, @@ -4040,12 +4042,39 @@ void SceneDataTest::fieldWrongArrayAccess() { "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n"); } -template void SceneDataTest::childrenFor() { +template void SceneDataTest::parentFor() { setTestCaseTemplateName(NameTraits::name()); struct Field { T object; Int parent; + } fields[]{ + {3, -1}, + {4, 0}, + {2, 1}, + {4, 2} /* duplicate, ignored */ + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{Implementation::sceneObjectTypeFor(), 7, {}, fields, { + SceneFieldData{SceneField::Parent, view.slice(&Field::object), view.slice(&Field::parent)} + }}; + + CORRADE_COMPARE(scene.parentFor(2), 4); + CORRADE_COMPARE(scene.parentFor(3), -1); + + /* Duplicate entries -- only the first one gets used, it doesn't traverse + further */ + CORRADE_COMPARE(scene.parentFor(4), 3); + + /* Object that's not in the array at all */ + CORRADE_COMPARE(scene.parentFor(1), Containers::NullOpt); +} + +void SceneDataTest::childrenFor() { + struct Field { + UnsignedInt object; + Int parent; } fields[]{ {4, -1}, {3, 0}, @@ -4056,7 +4085,7 @@ template void SceneDataTest::childrenFor() { }; Containers::StridedArrayView1D view = fields; - SceneData scene{Implementation::sceneObjectTypeFor(), 7, {}, fields, { + SceneData scene{SceneObjectType::UnsignedInt, 7, {}, fields, { SceneFieldData{SceneField::Parent, view.slice(&Field::object), view.slice(&Field::parent)} }}; @@ -4486,6 +4515,7 @@ void SceneDataTest::skinsFor() { void SceneDataTest::fieldForFieldMissing() { SceneData scene{SceneObjectType::UnsignedInt, 7, nullptr, {}}; + CORRADE_COMPARE(scene.parentFor(6), Containers::NullOpt); CORRADE_COMPARE_AS(scene.childrenFor(6), Containers::arrayView({}), TestSuite::Compare::Container); @@ -4516,6 +4546,7 @@ void SceneDataTest::fieldForInvalidObject() { std::ostringstream out; Error redirectError{&out}; + scene.parentFor(7); scene.childrenFor(-2); scene.childrenFor(7); scene.transformation2DFor(7); @@ -4527,6 +4558,7 @@ void SceneDataTest::fieldForInvalidObject() { scene.camerasFor(7); scene.skinsFor(7); CORRADE_COMPARE(out.str(), + "Trade::SceneData::parentFor(): object 7 out of bounds for 7 objects\n" "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"