From 6e1bb5f838deace22b892dc2078cf75273b93f3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Fri, 3 Dec 2021 16:52:22 +0100 Subject: [PATCH] Trade: add public SceneData APIs for finding fields and objects. Using hasField() + fieldId() was a bad usage pattern leading to a double linear lookup, so there's now findFieldId() returning an Optional which covers both. Similarly for finding an object offset in an field, there's a findFieldObjectOffset() returning an Optional, fieldObjectOffset() asserting if an object is not found (for convenience to avoid explicit error handling on user side) and hasFieldObject(). The internal helpers were also renamed and the offset argument moved to be last for consistency. --- src/Magnum/Trade/SceneData.cpp | 259 +++++++++++++------- src/Magnum/Trade/SceneData.h | 299 ++++++++++++++++-------- src/Magnum/Trade/Test/SceneDataTest.cpp | 218 +++++++++++++++-- 3 files changed, 574 insertions(+), 202 deletions(-) diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 69456234d..1b2028e64 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -831,36 +831,154 @@ UnsignedShort SceneData::fieldArraySize(const UnsignedInt id) const { return _fields[id]._fieldArraySize; } -UnsignedInt SceneData::fieldFor(const SceneField name) const { +UnsignedInt SceneData::findFieldIdInternal(const SceneField name) const { for(std::size_t i = 0; i != _fields.size(); ++i) if(_fields[i]._name == name) return i; return ~UnsignedInt{}; } -bool SceneData::hasField(const SceneField name) const { - return fieldFor(name) != ~UnsignedInt{}; +Containers::Optional SceneData::findFieldId(const SceneField name) const { + const UnsignedInt fieldId = findFieldIdInternal(name); + return fieldId == ~UnsignedInt{} ? Containers::Optional{} : fieldId; } UnsignedInt SceneData::fieldId(const SceneField name) const { - const UnsignedInt fieldId = fieldFor(name); + const UnsignedInt fieldId = findFieldIdInternal(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldId(): field" << name << "not found", {}); return fieldId; } +bool SceneData::hasField(const SceneField name) const { + return findFieldIdInternal(name) != ~UnsignedInt{}; +} + +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::findFieldObjectOffsetInternal(const SceneFieldData& field, const UnsignedInt object, const std::size_t offset) 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::Optional SceneData::findFieldObjectOffset(const UnsignedInt fieldId, const UnsignedInt object, const std::size_t offset) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::findFieldObjectOffset(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::findFieldObjectOffset(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + + const SceneFieldData& field = _fields[fieldId]; + CORRADE_ASSERT(offset <= field._size, + "Trade::SceneData::findFieldObjectOffset(): offset" << offset << "out of bounds for a field of size" << field._size, {}); + + const std::size_t found = findFieldObjectOffsetInternal(field, object, offset); + return found == field._size ? Containers::Optional{} : found; +} + +Containers::Optional SceneData::findFieldObjectOffset(const SceneField fieldName, const UnsignedInt object, const std::size_t offset) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::findFieldObjectOffset(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + const UnsignedInt fieldId = findFieldIdInternal(fieldName); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::findFieldObjectOffset(): field" << fieldName << "not found", {}); + + const SceneFieldData& field = _fields[fieldId]; + CORRADE_ASSERT(offset <= field._size, + "Trade::SceneData::findFieldObjectOffset(): offset" << offset << "out of bounds for a field of size" << field._size, {}); + + const std::size_t found = findFieldObjectOffsetInternal(field, object, offset); + return found == field._size ? Containers::Optional{} : found; +} + +std::size_t SceneData::fieldObjectOffset(const UnsignedInt fieldId, const UnsignedInt object, const std::size_t offset) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::fieldObjectOffset(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::fieldObjectOffset(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + + const SceneFieldData& field = _fields[fieldId]; + CORRADE_ASSERT(offset <= field._size, + "Trade::SceneData::fieldObjectOffset(): offset" << offset << "out of bounds for a field of size" << field._size, {}); + + const std::size_t found = findFieldObjectOffsetInternal(field, object, offset); + CORRADE_ASSERT(found != field._size, + "Trade::SceneData::fieldObjectOffset(): object" << object << "not found in field" << field._name << "starting at offset" << offset, {}); + return found; +} + +std::size_t SceneData::fieldObjectOffset(const SceneField fieldName, const UnsignedInt object, const std::size_t offset) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::fieldObjectOffset(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + const UnsignedInt fieldId = findFieldIdInternal(fieldName); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::fieldObjectOffset(): field" << fieldName << "not found", {}); + + const SceneFieldData& field = _fields[fieldId]; + CORRADE_ASSERT(offset <= field._size, + "Trade::SceneData::fieldObjectOffset(): offset" << offset << "out of bounds for a field of size" << field._size, {}); + + const std::size_t found = findFieldObjectOffsetInternal(field, object, offset); + CORRADE_ASSERT(found != field._size, + "Trade::SceneData::fieldObjectOffset(): object" << object << "not found in field" << field._name << "starting at offset" << offset, {}); + return found; +} + +bool SceneData::hasFieldObject(const UnsignedInt fieldId, const UnsignedInt object) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::hasFieldObject(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::hasFieldObject(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + + const SceneFieldData& field = _fields[fieldId]; + return findFieldObjectOffsetInternal(field, object, 0) != field._size; +} + +bool SceneData::hasFieldObject(const SceneField fieldName, const UnsignedInt object) const { + CORRADE_ASSERT(object < _objectCount, + "Trade::SceneData::hasFieldObject(): object" << object << "out of bounds for" << _objectCount << "objects", {}); + + const UnsignedInt fieldId = findFieldIdInternal(fieldName); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::hasFieldObject(): field" << fieldName << "not found", {}); + + const SceneFieldData& field = _fields[fieldId]; + return findFieldObjectOffsetInternal(field, object, 0) != field._size; +} + SceneFieldType SceneData::fieldType(const SceneField name) const { - const UnsignedInt fieldId = fieldFor(name); + const UnsignedInt fieldId = findFieldIdInternal(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldType(): field" << name << "not found", {}); return _fields[fieldId]._fieldType; } std::size_t SceneData::fieldSize(const SceneField name) const { - const UnsignedInt fieldId = fieldFor(name); + const UnsignedInt fieldId = findFieldIdInternal(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldSize(): field" << name << "not found", {}); return _fields[fieldId]._size; } UnsignedShort SceneData::fieldArraySize(const SceneField name) const { - const UnsignedInt fieldId = fieldFor(name); + const UnsignedInt fieldId = findFieldIdInternal(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldArraySize(): field" << name << "not found", {}); return _fields[fieldId]._fieldArraySize; } @@ -894,7 +1012,7 @@ Containers::StridedArrayView2D SceneData::mutableObjects(const UnsignedInt } Containers::StridedArrayView2D SceneData::objects(const SceneField fieldName) const { - const UnsignedInt fieldId = fieldFor(fieldName); + const UnsignedInt fieldId = findFieldIdInternal(fieldName); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::objects(): field" << fieldName << "not found", {}); return objects(fieldId); } @@ -902,7 +1020,7 @@ Containers::StridedArrayView2D SceneData::objects(const SceneField f Containers::StridedArrayView2D SceneData::mutableObjects(const SceneField fieldName) { CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, "Trade::SceneData::mutableObjects(): data not mutable", {}); - const UnsignedInt fieldId = fieldFor(fieldName); + const UnsignedInt fieldId = findFieldIdInternal(fieldName); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::mutableObjects(): field" << fieldName << "not found", {}); return mutableObjects(fieldId); } @@ -936,7 +1054,7 @@ Containers::StridedArrayView2D SceneData::mutableField(const UnsignedInt i } Containers::StridedArrayView2D SceneData::field(const SceneField name) const { - const UnsignedInt fieldId = fieldFor(name); + const UnsignedInt fieldId = findFieldIdInternal(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::field(): field" << name << "not found", {}); return field(fieldId); @@ -945,7 +1063,7 @@ Containers::StridedArrayView2D SceneData::field(const SceneField nam Containers::StridedArrayView2D SceneData::mutableField(const SceneField name) { CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, "Trade::SceneData::mutableField(): data not mutable", {}); - const UnsignedInt fieldId = fieldFor(name); + const UnsignedInt fieldId = findFieldIdInternal(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::mutableField(): field" << name << "not found", {}); return mutableField(fieldId); @@ -1000,21 +1118,21 @@ Containers::Array SceneData::objectsAsArray(const UnsignedInt field } void SceneData::objectsInto(const SceneField name, const Containers::StridedArrayView1D& destination) const { - const UnsignedInt fieldId = fieldFor(name); + const UnsignedInt fieldId = findFieldIdInternal(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::objectsInto(): field" << name << "not found", ); objectsInto(fieldId, destination); } std::size_t SceneData::objectsInto(const SceneField name, std::size_t offset, const Containers::StridedArrayView1D& destination) const { - const UnsignedInt fieldId = fieldFor(name); + const UnsignedInt fieldId = findFieldIdInternal(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::objectsInto(): field" << name << "not found", {}); return objectsInto(fieldId, offset, destination); } Containers::Array SceneData::objectsAsArray(const SceneField name) const { - const UnsignedInt fieldId = fieldFor(name); + const UnsignedInt fieldId = findFieldIdInternal(name); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, /* Using the same message as in Into() to avoid too many redundant strings in the binary */ @@ -1043,7 +1161,7 @@ void SceneData::parentsIntoInternal(const UnsignedInt fieldId, const std::size_t } void SceneData::parentsInto(const Containers::StridedArrayView1D& destination) const { - const UnsignedInt fieldId = fieldFor(SceneField::Parent); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Parent); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::parentsInto(): field not found", ); CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, @@ -1052,7 +1170,7 @@ void SceneData::parentsInto(const Containers::StridedArrayView1D& destinati } std::size_t SceneData::parentsInto(const std::size_t offset, const Containers::StridedArrayView1D& destination) const { - const UnsignedInt fieldId = fieldFor(SceneField::Parent); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Parent); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::parentsInto(): field not found", {}); CORRADE_ASSERT(offset <= _fields[fieldId]._size, @@ -1063,7 +1181,7 @@ std::size_t SceneData::parentsInto(const std::size_t offset, const Containers::S } Containers::Array SceneData::parentsAsArray() const { - const UnsignedInt fieldId = fieldFor(SceneField::Parent); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Parent); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, /* Using the same message as in Into() to avoid too many redundant strings in the binary */ @@ -1664,7 +1782,7 @@ void SceneData::meshesMaterialsIntoInternal(const UnsignedInt fieldId, const std /* Copy also the material, if desired. If no such field is present, output -1 for all meshes. */ if(meshMaterialDestination) { - const UnsignedInt materialFieldId = fieldFor(SceneField::MeshMaterial); + const UnsignedInt materialFieldId = findFieldIdInternal(SceneField::MeshMaterial); if(materialFieldId == ~UnsignedInt{}) { constexpr Int invalid[]{-1}; Utility::copy(Containers::stridedArrayView(invalid).broadcasted<0>(meshMaterialDestination.size()), meshMaterialDestination); @@ -1673,7 +1791,7 @@ void SceneData::meshesMaterialsIntoInternal(const UnsignedInt fieldId, const std } void SceneData::meshesMaterialsInto(const Containers::StridedArrayView1D& meshDestination, const Containers::StridedArrayView1D& meshMaterialDestination) const { - const UnsignedInt fieldId = fieldFor(SceneField::Mesh); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Mesh); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::meshesMaterialsInto(): field" << SceneField::Mesh << "not found", ); CORRADE_ASSERT(!meshDestination || meshDestination.size() == _fields[fieldId]._size, @@ -1684,7 +1802,7 @@ void SceneData::meshesMaterialsInto(const Containers::StridedArrayView1D& meshDestination, const Containers::StridedArrayView1D& meshMaterialDestination) const { - const UnsignedInt fieldId = fieldFor(SceneField::Mesh); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Mesh); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::meshesMaterialsInto(): field" << SceneField::Mesh << "not found", {}); CORRADE_ASSERT(offset <= _fields[fieldId]._size, @@ -1699,7 +1817,7 @@ std::size_t SceneData::meshesMaterialsInto(const std::size_t offset, const Conta } Containers::Array> SceneData::meshesMaterialsAsArray() const { - const UnsignedInt fieldId = fieldFor(SceneField::Mesh); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Mesh); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, /* Using the same message as in Into() to avoid too many redundant strings in the binary */ @@ -1713,7 +1831,7 @@ Containers::Array> SceneData::meshesMaterials } void SceneData::lightsInto(const Containers::StridedArrayView1D& destination) const { - const UnsignedInt fieldId = fieldFor(SceneField::Light); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Light); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::lightsInto(): field not found", ); CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, @@ -1722,7 +1840,7 @@ void SceneData::lightsInto(const Containers::StridedArrayView1D& de } std::size_t SceneData::lightsInto(const std::size_t offset, const Containers::StridedArrayView1D& destination) const { - const UnsignedInt fieldId = fieldFor(SceneField::Light); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Light); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::lightsInto(): field not found", {}); CORRADE_ASSERT(offset <= _fields[fieldId]._size, @@ -1733,7 +1851,7 @@ std::size_t SceneData::lightsInto(const std::size_t offset, const Containers::St } Containers::Array SceneData::lightsAsArray() const { - const UnsignedInt fieldId = fieldFor(SceneField::Light); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Light); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, /* Using the same message as in Into() to avoid too many redundant strings in the binary */ @@ -1742,7 +1860,7 @@ Containers::Array SceneData::lightsAsArray() const { } void SceneData::camerasInto(const Containers::StridedArrayView1D& destination) const { - const UnsignedInt fieldId = fieldFor(SceneField::Camera); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Camera); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::camerasInto(): field not found", ); CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, @@ -1751,7 +1869,7 @@ void SceneData::camerasInto(const Containers::StridedArrayView1D& d } std::size_t SceneData::camerasInto(const std::size_t offset, const Containers::StridedArrayView1D& destination) const { - const UnsignedInt fieldId = fieldFor(SceneField::Camera); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Camera); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::camerasInto(): field not found", {}); CORRADE_ASSERT(offset <= _fields[fieldId]._size, @@ -1762,7 +1880,7 @@ std::size_t SceneData::camerasInto(const std::size_t offset, const Containers::S } Containers::Array SceneData::camerasAsArray() const { - const UnsignedInt fieldId = fieldFor(SceneField::Camera); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Camera); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, /* Using the same message as in Into() to avoid too many redundant strings in the binary */ @@ -1771,7 +1889,7 @@ Containers::Array SceneData::camerasAsArray() const { } void SceneData::skinsInto(const Containers::StridedArrayView1D& destination) const { - const UnsignedInt fieldId = fieldFor(SceneField::Skin); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Skin); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::skinsInto(): field not found", ); CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, @@ -1780,7 +1898,7 @@ void SceneData::skinsInto(const Containers::StridedArrayView1D& des } std::size_t SceneData::skinsInto(const std::size_t offset, const Containers::StridedArrayView1D& destination) const { - const UnsignedInt fieldId = fieldFor(SceneField::Skin); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Skin); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::skinsInto(): field not found", {}); CORRADE_ASSERT(offset <= _fields[fieldId]._size, @@ -1791,7 +1909,7 @@ std::size_t SceneData::skinsInto(const std::size_t offset, const Containers::Str } Containers::Array SceneData::skinsAsArray() const { - const UnsignedInt fieldId = fieldFor(SceneField::Skin); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Skin); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, /* Using the same message as in Into() to avoid too many redundant strings in the binary */ @@ -1810,7 +1928,7 @@ void SceneData::importerStateIntoInternal(const UnsignedInt fieldId, const std:: } void SceneData::importerStateInto(const Containers::StridedArrayView1D& destination) const { - const UnsignedInt fieldId = fieldFor(SceneField::ImporterState); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::ImporterState); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::importerStateInto(): field not found", ); CORRADE_ASSERT(destination.size() == _fields[fieldId]._size, @@ -1819,7 +1937,7 @@ void SceneData::importerStateInto(const Containers::StridedArrayView1D& destination) const { - const UnsignedInt fieldId = fieldFor(SceneField::ImporterState); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::ImporterState); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::importerStateInto(): field not found", {}); CORRADE_ASSERT(offset <= _fields[fieldId]._size, @@ -1830,7 +1948,7 @@ std::size_t SceneData::importerStateInto(const std::size_t offset, const Contain } Containers::Array SceneData::importerStateAsArray() const { - const UnsignedInt fieldId = fieldFor(SceneField::ImporterState); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::ImporterState); CORRADE_ASSERT(fieldId != ~UnsignedInt{}, /* Using the same message as in Into() to avoid too many redundant strings in the binary */ @@ -1840,42 +1958,15 @@ Containers::Array SceneData::importerStateAsArray() const { return out; } -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::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); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::Parent); if(fieldId == ~UnsignedInt{}) return {}; const SceneFieldData& field = _fields[fieldId]; - const std::size_t offset = fieldFor(field, 0, object); + const std::size_t offset = findFieldObjectOffsetInternal(field, object, 0); if(offset == field._size) return {}; Int index[1]; @@ -1891,7 +1982,7 @@ 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", {}); - const UnsignedInt parentFieldId = fieldFor(SceneField::Parent); + const UnsignedInt parentFieldId = findFieldIdInternal(SceneField::Parent); if(parentFieldId == ~UnsignedInt{}) return {}; const SceneFieldData& parentField = _fields[parentFieldId]; @@ -1901,7 +1992,7 @@ Containers::Array SceneData::childrenFor(const Int object) const { Int parentIndexToLookFor; if(object == -1) parentIndexToLookFor = -1; else { - const std::size_t parentObjectIndex = fieldFor(parentField, 0, object); + const std::size_t parentObjectIndex = findFieldObjectOffsetInternal(parentField, object, 0); if(parentObjectIndex == parentField._size) return {}; parentIndexToLookFor = parentObjectIndex; } @@ -1934,8 +2025,9 @@ Containers::Optional SceneData::transformation2DFor(const UnsignedInt o is handled above. */ CORRADE_ASSERT(!is3D(), "Trade::SceneData::transformation2DFor(): scene has a 3D transformation type", {}); - const std::size_t offset = fieldFor(_fields[fieldWithObjectMapping], 0, object); - if(offset == _fields[fieldWithObjectMapping]._size) return {}; + const SceneFieldData& field = _fields[fieldWithObjectMapping]; + const std::size_t offset = findFieldObjectOffsetInternal(field, object, 0); + if(offset == field._size) return {}; Matrix3 transformation[1]; transformations2DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, offset, transformation); @@ -1953,8 +2045,9 @@ Containers::Optional> SceneData::t is handled above. */ CORRADE_ASSERT(!is3D(), "Trade::SceneData::translationRotationScaling2DFor(): scene has a 3D transformation type", {}); - const std::size_t offset = fieldFor(_fields[fieldWithObjectMapping], 0, object); - if(offset == _fields[fieldWithObjectMapping]._size) return {}; + const SceneFieldData& field = _fields[fieldWithObjectMapping]; + const std::size_t offset = findFieldObjectOffsetInternal(field, object, 0); + if(offset == field._size) return {}; Vector2 translation[1]; Complex rotation[1]; @@ -1974,8 +2067,9 @@ Containers::Optional SceneData::transformation3DFor(const UnsignedInt o is handled above. */ CORRADE_ASSERT(!is2D(), "Trade::SceneData::transformation3DFor(): scene has a 2D transformation type", {}); - const std::size_t offset = fieldFor(_fields[fieldWithObjectMapping], 0, object); - if(offset == _fields[fieldWithObjectMapping]._size) return {}; + const SceneFieldData& field = _fields[fieldWithObjectMapping]; + const std::size_t offset = findFieldObjectOffsetInternal(field, object, 0); + if(offset == field._size) return {}; Matrix4 transformation[1]; transformations3DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, offset, transformation); @@ -1993,8 +2087,9 @@ Containers::Optional> SceneData is handled above. */ CORRADE_ASSERT(!is2D(), "Trade::SceneData::translationRotationScaling3DFor(): scene has a 2D transformation type", {}); - const std::size_t offset = fieldFor(_fields[fieldWithObjectMapping], 0, object); - if(offset == _fields[fieldWithObjectMapping]._size) return {}; + const SceneFieldData& field = _fields[fieldWithObjectMapping]; + const std::size_t offset = findFieldObjectOffsetInternal(field, object, 0); + if(offset == field._size) return {}; Vector3 translation[1]; Quaternion rotation[1]; @@ -2007,14 +2102,14 @@ Containers::Array> SceneData::meshesMaterials CORRADE_ASSERT(object < _objectCount, "Trade::SceneData::meshesMaterialsFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); - const UnsignedInt meshFieldId = fieldFor(SceneField::Mesh); + const UnsignedInt meshFieldId = findFieldIdInternal(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); + offset = findFieldObjectOffsetInternal(field, object, offset); if(offset == field._size) break; UnsignedInt mesh[1]; @@ -2031,14 +2126,14 @@ Containers::Array SceneData::lightsFor(const UnsignedInt object) co CORRADE_ASSERT(object < _objectCount, "Trade::SceneData::lightsFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); - const UnsignedInt fieldId = fieldFor(SceneField::Light); + const UnsignedInt fieldId = findFieldIdInternal(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); + offset = findFieldObjectOffsetInternal(field, object, offset); if(offset == field._size) break; UnsignedInt index[1]; @@ -2054,14 +2149,14 @@ Containers::Array SceneData::camerasFor(const UnsignedInt object) c CORRADE_ASSERT(object < _objectCount, "Trade::SceneData::camerasFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); - const UnsignedInt fieldId = fieldFor(SceneField::Camera); + const UnsignedInt fieldId = findFieldIdInternal(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); + offset = findFieldObjectOffsetInternal(field, object, offset); if(offset == field._size) break; UnsignedInt index[1]; @@ -2077,14 +2172,14 @@ Containers::Array SceneData::skinsFor(const UnsignedInt object) con CORRADE_ASSERT(object < _objectCount, "Trade::SceneData::skinsFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); - const UnsignedInt fieldId = fieldFor(SceneField::Skin); + const UnsignedInt fieldId = findFieldIdInternal(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); + offset = findFieldObjectOffsetInternal(field, object, offset); if(offset == field._size) break; UnsignedInt index[1]; @@ -2100,11 +2195,11 @@ Containers::Optional SceneData::importerStateFor(const UnsignedInt CORRADE_ASSERT(object < _objectCount, "Trade::SceneData::importerStateFor(): object" << object << "out of bounds for" << _objectCount << "objects", {}); - const UnsignedInt fieldId = fieldFor(SceneField::ImporterState); + const UnsignedInt fieldId = findFieldIdInternal(SceneField::ImporterState); if(fieldId == ~UnsignedInt{}) return {}; const SceneFieldData& field = _fields[fieldId]; - const std::size_t offset = fieldFor(field, 0, object); + const std::size_t offset = findFieldObjectOffsetInternal(field, object, 0); if(offset == field._size) return {}; const void* importerState[1]; diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index 24a5bccf3..9ddbece3b 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -1080,22 +1080,112 @@ class MAGNUM_TRADE_EXPORT SceneData { bool is3D() const { return _dimensions == 3; } /** - * @brief Whether the scene has given field + * @brief Find an absolute ID of a named field * @m_since_latest * - * @see @ref is2D(), @ref is3D() + * If @p name doesn't exist, returns @ref Containers::NullOpt. The + * lookup is done in an @f$ \mathcal{O}(n) @f$ complexity with + * @f$ n @f$ being the field count. + * @see @ref hasField(), @ref fieldId() */ - bool hasField(SceneField name) const; + Containers::Optional findFieldId(SceneField name) const; /** * @brief Absolute ID of a named field * @m_since_latest * - * The @p name is expected to exist. + * Like @ref findFieldId(), but the @p name is expected to exist. * @see @ref hasField(), @ref fieldName(UnsignedInt) const */ UnsignedInt fieldId(SceneField name) const; + /** + * @brief Whether the scene has given field + * @m_since_latest + * + * @see @ref is2D(), @ref is3D() + */ + bool hasField(SceneField name) const; + + /** + * @brief Find offset of an object in given field + * @m_since_latest + * + * If @p object isn't present in @p fieldId starting at @p offset, + * returns @ref Containers::NullOpt. The @p fieldId is expected to be + * smaller than @ref fieldCount(), @p object smaller than + * @ref objectCount() and @p offset not larger than + * @ref fieldSize(UnsignedInt) const. + * + * The lookup is done in an @f$ \mathcal{O}(n) @f$ complexity with + * @f$ n @f$ being the size of the field. + * + * You can also use @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * to directly find offset of an object in given named field. + * @see @ref hasFieldObject(UnsignedInt, UnsignedInt) const, + * @ref fieldObjectOffset(UnsignedInt, UnsignedInt, std::size_t) const + */ + Containers::Optional findFieldObjectOffset(UnsignedInt fieldId, UnsignedInt object, std::size_t offset = 0) const; + + /** + * @brief Find offset of an object in given named field + * @m_since_latest + * + * If @p object isn't present in @p fieldName starting at @p offset, + * returns @ref Containers::NullOpt. The @p fieldName is expected to + * exist, @p object is expected to be smaller than @ref objectCount() + * and @p offset not be larger than @ref fieldSize(SceneField) const. + * + * 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 field. + * + * @see @ref hasField(), @ref hasFieldObject(SceneField, UnsignedInt) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + */ + Containers::Optional findFieldObjectOffset(SceneField fieldName, UnsignedInt object, std::size_t offset = 0) const; + + /** + * @brief Offset of an object in given field + * @m_since_latest + * + * Like @ref findFieldObjectOffset(UnsignedInt, UnsignedInt, std::size_t) const, + * but @p object is additionally expected to be present in @p fieldId + * starting at @p offset. + * + * You can also use @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * to directly get offset of an object in given named field. + */ + std::size_t fieldObjectOffset(UnsignedInt fieldId, UnsignedInt object, std::size_t offset = 0) const; + + /** + * @brief Offset of an object in given named field + * @m_since_latest + * + * Like @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const, + * but @p object is additionally expected to be present in @p fieldName + * starting at @p offset. + */ + std::size_t fieldObjectOffset(SceneField fieldName, UnsignedInt object, std::size_t offset = 0) const; + + /** + * @brief Whether a scene field has given object + * @m_since_latest + * + * The @p fieldId is expected to be smaller than @ref fieldCount() and + * @p object smaller than @ref objectCount(). + */ + bool hasFieldObject(UnsignedInt fieldId, UnsignedInt object) const; + + /** + * @brief Whether a named scene field has given object + * @m_since_latest + * + * The @p fieldName is expected to exist and @p object is expected to + * be smaller than @ref objectCount(). + * @see @ref hasField() + */ + bool hasFieldObject(SceneField fieldName, UnsignedInt object) const; + /** * @brief Type of a named field * @m_since_latest @@ -1416,7 +1506,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * and size of the @p destination view, returning the count of items * actually extracted. The @p offset is expected to not be larger than * the field size. - * @see @ref fieldSize(UnsignedInt) const + * @see @ref fieldSize(UnsignedInt) const, + * @ref fieldObjectOffset(UnsignedInt, UnsignedInt, std::size_t) const */ std::size_t objectsInto(UnsignedInt fieldId, std::size_t offset, const Containers::StridedArrayView1D& destination) const; @@ -1458,7 +1549,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * and size of the @p destination view, returning the count of items * actually extracted. The @p offset is expected to not be larger than * the field size. - * @see @ref fieldSize(SceneField) const + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const */ std::size_t objectsInto(SceneField fieldName, std::size_t offset, const Containers::StridedArrayView1D& destination) const; @@ -1500,7 +1592,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * of the @p destination view, returning the count of items actually * extracted. The @p offset is expected to not be larger than the field * size. - * @see @ref fieldSize(SceneField) const + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const */ std::size_t parentsInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; @@ -1543,7 +1636,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * of the @p destination view, returning the count of items actually * extracted. The @p offset is expected to not be larger than the field * size. - * @see @ref fieldSize(SceneField) const + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const */ std::size_t transformations2DInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; @@ -1592,7 +1686,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * 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 + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const */ std::size_t translationsRotationsScalings2DInto(std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; @@ -1635,7 +1730,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * of the @p destination view, returning the count of items actually * extracted. The @p offset is expected to not be larger than the field * size. - * @see @ref fieldSize(SceneField) const + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const */ std::size_t transformations3DInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; @@ -1684,7 +1780,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * 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 + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const */ std::size_t translationsRotationsScalings3DInto(std::size_t offset, const Containers::StridedArrayView1D& translationDestination, const Containers::StridedArrayView1D& rotationDestination, const Containers::StridedArrayView1D& scalingDestination) const; @@ -1725,7 +1822,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * 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 + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const */ std::size_t meshesMaterialsInto(std::size_t offset, const Containers::StridedArrayView1D& meshDestination, const Containers::StridedArrayView1D& meshMaterialsDestination) const; @@ -1761,7 +1859,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * of the @p destination view, returning the count of items actually * extracted. The @p offset is expected to not be larger than the field * size. - * @see @ref fieldSize(SceneField) const + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const */ std::size_t lightsInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; @@ -1797,7 +1896,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * of the @p destination view, returning the count of items actually * extracted. The @p offset is expected to not be larger than the field * size. - * @see @ref fieldSize(SceneField) const + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const */ std::size_t camerasInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; @@ -1833,7 +1933,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * of the @p destination view, returning the count of items actually * extracted. The @p offset is expected to not be larger than the field * size. - * @see @ref fieldSize(SceneField) const + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const */ std::size_t skinsInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; @@ -1872,7 +1973,8 @@ class MAGNUM_TRADE_EXPORT SceneData { * of the @p destination view, returning the count of items actually * extracted. The @p offset is expected to not be larger than the field * size. - * @see @ref fieldSize(SceneField) const + * @see @ref fieldSize(SceneField) const, + * @ref fieldObjectOffset(SceneField, UnsignedInt, std::size_t) const */ std::size_t importerStateInto(std::size_t offset, const Containers::StridedArrayView1D& destination) const; @@ -1880,12 +1982,13 @@ class MAGNUM_TRADE_EXPORT SceneData { * @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. + * Looks up the @ref SceneField::Parent field for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the field from an arbitrary underlying type the + * same way as @ref parentsAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving parent + * info for many objects it's recommended to access the field data + * directly. * * 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 @@ -1901,13 +2004,12 @@ class MAGNUM_TRADE_EXPORT SceneData { * @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. + * @ref SceneField::Parent equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const, + * converts the fields from an arbitrary underlying type the same way + * as @ref parentsAsArray(), returning a list of all object IDs that + * have it listed as the parent. See the lookup function documentation + * for operation complexity --- for retrieving parent/child info for + * many objects it's recommended to access the field data directly. * * If the @ref SceneField::Parent field doesn't exist or there are no * objects which would have @p object listed as their parent, returns @@ -1922,16 +2024,16 @@ class MAGNUM_TRADE_EXPORT SceneData { * @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, + * Looks up the @ref SceneField::Transformation field for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * 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. + * @relativeref{SceneField,Scaling}, converting the fields from + * arbitrary underlying types the same way as + * @ref transformations2DAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving + * transformation info for many objects it's recommended to access the + * field data directly. * * If neither @ref SceneField::Transformation nor any of * @ref SceneField::Translation, @relativeref{SceneField,Rotation} or @@ -1950,12 +2052,13 @@ class MAGNUM_TRADE_EXPORT SceneData { * * 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. + * @relativeref{SceneField,Scaling} fields for @p object equivalently + * to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the fields from arbitrary underlying types the + * same way as @ref translationsRotationsScalings2DAsArray(). See the + * lookup function documentation for operation complexity --- for + * retrieving transformation info for many objects it's recommended to + * access the field data directly. * * If the @ref SceneField::Translation field isn't present, the first * returned value is a zero vector. If the @@ -1976,16 +2079,16 @@ class MAGNUM_TRADE_EXPORT SceneData { * @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, + * Looks up the @ref SceneField::Transformation field for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * 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. + * @relativeref{SceneField,Scaling}, converting the fields from + * arbitrary underlying types the same way as + * @ref transformations3DAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving + * transformation info for many objects it's recommended to access the + * field data directly. * * If neither @ref SceneField::Transformation nor any of * @ref SceneField::Translation, @relativeref{SceneField,Rotation} or @@ -2004,12 +2107,13 @@ class MAGNUM_TRADE_EXPORT SceneData { * * 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. + * @relativeref{SceneField,Scaling} fields for @p object equivalently + * to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the fields from arbitrary underlying types the + * same way as @ref translationsRotationsScalings2DAsArray(). See the + * lookup function documentation for operation complexity --- for + * retrieving transformation info for many objects it's recommended to + * access the field data directly. * * If the @ref SceneField::Translation field isn't present, the first * returned value is a zero vector. If the @@ -2031,12 +2135,13 @@ class MAGNUM_TRADE_EXPORT SceneData { * @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. + * @relativeref{SceneField,Scaling} fields for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the field from an arbitrary underlying type the + * same way as @ref meshesMaterialsAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving mesh and + * material info for many objects it's recommended to access the field + * data directly. * * If the @ref SceneField::MeshMaterial field is not present, the * second returned value is always @cpp -1 @ce. If @@ -2051,12 +2156,12 @@ class MAGNUM_TRADE_EXPORT SceneData { * @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. + * Looks up all @ref SceneField::Light fields for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the field from an arbitrary underlying type the + * same way as @ref lightsAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving light info + * for many objects it's recommended to access the field data directly. * * If the @ref SceneField::Light field is not present or if there's no * light for @p object, returns an empty array. @@ -2069,12 +2174,13 @@ class MAGNUM_TRADE_EXPORT SceneData { * @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. + * Looks up all @ref SceneField::Camera fields for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the field from an arbitrary underlying type the + * same way as @ref camerasAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving camera + * info for many objects it's recommended to access the field data + * directly. * * If the @ref SceneField::Camera field is not present or if there's no * camera for @p object, returns an empty array. @@ -2087,11 +2193,12 @@ class MAGNUM_TRADE_EXPORT SceneData { * @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. + * Looks up all @ref SceneField::Skin fields for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the field from an arbitrary underlying type the + * same way as @ref skinsAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving skin info + * for many objects it's recommended to access the field data directly. * * If the @ref SceneField::Skin field is not present or if there's no * skin for @p object, returns an empty array. @@ -2104,12 +2211,13 @@ class MAGNUM_TRADE_EXPORT SceneData { * @brief Importer state for given object * @m_since_latest * - * Looks up the @ref SceneField::ImporterState field for @p object. The - * lookup is done in a @f$ \mathcal{O}(m + n) @f$ complexity with - * @f$ m @f$ being the field count and @f$ n @f$ the size of the - * importer state field, thus for retrieving importer state info for - * many objects it's recommended to access the field data directly with - * @ref importerStateAsArray() and related APIs. + * Looks up the @ref SceneField::ImporterState field for @p object + * equivalently to @ref findFieldObjectOffset(SceneField, UnsignedInt, std::size_t) const + * and then converts the field from an arbitrary underlying type the + * same way as @ref importerStateAsArray(). See the lookup function + * documentation for operation complexity --- for retrieving importer + * state info for many objects it's recommended to access the field + * data directly. * * If the @ref SceneField::ImporterState field is not present or if * there's no importer state for @p object, returns @@ -2187,13 +2295,14 @@ class MAGNUM_TRADE_EXPORT SceneData { implementations. */ friend AbstractImporter; - /* Internal helper that doesn't assert, unlike fieldId() */ - UnsignedInt fieldFor(SceneField name) const; + /* Internal helper without the extra overhead from Optional, returns + ~UnsignedInt{} on failure */ + UnsignedInt findFieldIdInternal(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; + std::size_t findFieldObjectOffsetInternal(const SceneFieldData& field, UnsignedInt object, std::size_t offset) const; /* Like objects() / field(), but returning just a 1D view, sliced from offset to offset + size. The parameterless overloads are equal to @@ -2566,7 +2675,7 @@ template Containers::StridedArrayView1D SceneData::fiel if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - if(!checkFieldTypeCompatibility(_fields[fieldFor(name)], "Trade::SceneData::field():")) return {}; + if(!checkFieldTypeCompatibility(_fields[findFieldIdInternal(name)], "Trade::SceneData::field():")) return {}; #endif return Containers::arrayCast<1, const T>(data); } @@ -2577,7 +2686,7 @@ template Containers::StridedArrayView2D(_fields[fieldFor(name)], "Trade::SceneData::field():")) return {}; + if(!checkFieldTypeCompatibility(_fields[findFieldIdInternal(name)], "Trade::SceneData::field():")) return {}; #endif return Containers::arrayCast<2, const typename std::remove_extent::type>(data); } @@ -2588,7 +2697,7 @@ template Containers::StridedArrayView1D SceneData::mutableFie if(!data.stride()[1]) return {}; #endif #ifndef CORRADE_NO_ASSERT - if(!checkFieldTypeCompatibility(_fields[fieldFor(name)], "Trade::SceneData::mutableField():")) return {}; + if(!checkFieldTypeCompatibility(_fields[findFieldIdInternal(name)], "Trade::SceneData::mutableField():")) return {}; #endif return Containers::arrayCast<1, T>(data); } @@ -2599,7 +2708,7 @@ template Containers::StridedArrayView2D(_fields[fieldFor(name)], "Trade::SceneData::mutableField():")) return {}; + if(!checkFieldTypeCompatibility(_fields[findFieldIdInternal(name)], "Trade::SceneData::mutableField():")) return {}; #endif return Containers::arrayCast<2, typename std::remove_extent::type>(data); } diff --git a/src/Magnum/Trade/Test/SceneDataTest.cpp b/src/Magnum/Trade/Test/SceneDataTest.cpp index 660371e8e..27e8cd094 100644 --- a/src/Magnum/Trade/Test/SceneDataTest.cpp +++ b/src/Magnum/Trade/Test/SceneDataTest.cpp @@ -111,6 +111,11 @@ struct SceneDataTest: TestSuite::Tester { void constructCopy(); void constructMove(); + void findFieldId(); + template void findFieldObjectOffset(); + void findFieldObjectOffsetInvalidOffset(); + void fieldObjectOffsetNotFound(); + template void objectsAsArrayByIndex(); template void objectsAsArrayByName(); void objectsAsArrayLongType(); @@ -163,9 +168,7 @@ struct SceneDataTest: TestSuite::Tester { void fieldWrongPointerType(); void fieldWrongArrayAccess(); - /* Different object types checked just for the parentFor(), other APIs - use the same helper */ - template void parentFor(); + void parentFor(); void childrenFor(); void transformation2DFor(); void transformation2DForTRS(); @@ -184,7 +187,7 @@ struct SceneDataTest: TestSuite::Tester { #endif void fieldForFieldMissing(); - void fieldForInvalidObject(); + void findFieldObjectOffsetInvalidObject(); void releaseFieldData(); void releaseData(); @@ -290,6 +293,14 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::constructCopy, &SceneDataTest::constructMove, + &SceneDataTest::findFieldId, + &SceneDataTest::findFieldObjectOffset, + &SceneDataTest::findFieldObjectOffset, + &SceneDataTest::findFieldObjectOffset, + &SceneDataTest::findFieldObjectOffset, + &SceneDataTest::findFieldObjectOffsetInvalidOffset, + &SceneDataTest::fieldObjectOffsetNotFound, + &SceneDataTest::objectsAsArrayByIndex, &SceneDataTest::objectsAsArrayByIndex, &SceneDataTest::objectsAsArrayByIndex, @@ -396,10 +407,7 @@ SceneDataTest::SceneDataTest() { &SceneDataTest::fieldWrongPointerType, &SceneDataTest::fieldWrongArrayAccess, - &SceneDataTest::parentFor, - &SceneDataTest::parentFor, - &SceneDataTest::parentFor, - &SceneDataTest::parentFor, + &SceneDataTest::parentFor, &SceneDataTest::childrenFor, &SceneDataTest::transformation2DFor, &SceneDataTest::transformation2DForTRS, @@ -419,7 +427,7 @@ SceneDataTest::SceneDataTest() { #endif addTests({&SceneDataTest::fieldForFieldMissing, - &SceneDataTest::fieldForInvalidObject, + &SceneDataTest::findFieldObjectOffsetInvalidObject, &SceneDataTest::releaseFieldData, &SceneDataTest::releaseData}); @@ -1298,15 +1306,6 @@ void SceneDataTest::construct() { CORRADE_COMPARE(scene.mutableField(3)[0][1], 1.5f); /* Field property access by name */ - CORRADE_COMPARE(scene.fieldId(SceneField::Transformation), 0); - CORRADE_COMPARE(scene.fieldId(SceneField::Parent), 1); - CORRADE_COMPARE(scene.fieldId(SceneField::Mesh), 2); - CORRADE_COMPARE(scene.fieldId(sceneFieldCustom(37)), 3); - CORRADE_VERIFY(scene.hasField(SceneField::Transformation)); - CORRADE_VERIFY(scene.hasField(SceneField::Parent)); - CORRADE_VERIFY(scene.hasField(SceneField::Mesh)); - CORRADE_VERIFY(scene.hasField(sceneFieldCustom(37))); - CORRADE_VERIFY(!scene.hasField(SceneField::Skin)); CORRADE_COMPARE(scene.fieldType(SceneField::Transformation), SceneFieldType::Matrix4x4); CORRADE_COMPARE(scene.fieldType(SceneField::Parent), SceneFieldType::Int); CORRADE_COMPARE(scene.fieldType(SceneField::Mesh), SceneFieldType::UnsignedByte); @@ -1957,6 +1956,151 @@ void SceneDataTest::constructMove() { CORRADE_VERIFY(std::is_nothrow_move_assignable::value); } +void SceneDataTest::findFieldId() { + SceneData scene{SceneObjectType::UnsignedInt, 0, {}, nullptr, { + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::UnsignedByte, nullptr} + }}; + + CORRADE_COMPARE(scene.findFieldId(SceneField::Parent), 0); + CORRADE_COMPARE(scene.findFieldId(SceneField::Mesh), 1); + CORRADE_COMPARE(scene.findFieldId(SceneField::MeshMaterial), Containers::NullOpt); + + CORRADE_COMPARE(scene.fieldId(SceneField::Parent), 0); + CORRADE_COMPARE(scene.fieldId(SceneField::Mesh), 1); + + CORRADE_VERIFY(scene.hasField(SceneField::Parent)); + CORRADE_VERIFY(scene.hasField(SceneField::Mesh)); + CORRADE_VERIFY(!scene.hasField(SceneField::MeshMaterial)); +} + +template void SceneDataTest::findFieldObjectOffset() { + setTestCaseTemplateName(NameTraits::name()); + + /** @todo update once field flags describing object order are present */ + + struct Field { + T object; + UnsignedInt mesh; + } fields[]{ + {4, 1}, + {1, 3}, + {2, 4}, + {0, 5}, + {2, 5} + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{Implementation::sceneObjectTypeFor(), 7, {}, fields, { + /* Test also with a completely empty field */ + SceneFieldData{SceneField::Parent, Implementation::sceneObjectTypeFor(), nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + CORRADE_COMPARE(scene.findFieldObjectOffset(0, 4), Containers::NullOpt); + CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Parent, 4), Containers::NullOpt); + CORRADE_COMPARE(scene.findFieldObjectOffset(1, 4), 0); + CORRADE_COMPARE(scene.findFieldObjectOffset(1, 1), 1); + CORRADE_COMPARE(scene.findFieldObjectOffset(1, 2), 2); + CORRADE_COMPARE(scene.findFieldObjectOffset(1, 2, 3), 4); + CORRADE_COMPARE(scene.findFieldObjectOffset(1, 2, 5), Containers::NullOpt); + CORRADE_COMPARE(scene.findFieldObjectOffset(1, 3), Containers::NullOpt); + CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, 4), 0); + CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, 1), 1); + CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, 2), 2); + CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, 2, 3), 4); + CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, 2, 5), Containers::NullOpt); + CORRADE_COMPARE(scene.findFieldObjectOffset(SceneField::Mesh, 3), Containers::NullOpt); + + CORRADE_COMPARE(scene.fieldObjectOffset(1, 4), 0); + CORRADE_COMPARE(scene.fieldObjectOffset(1, 1), 1); + CORRADE_COMPARE(scene.fieldObjectOffset(1, 2), 2); + CORRADE_COMPARE(scene.fieldObjectOffset(1, 2, 3), 4); + CORRADE_COMPARE(scene.fieldObjectOffset(SceneField::Mesh, 4), 0); + CORRADE_COMPARE(scene.fieldObjectOffset(SceneField::Mesh, 1), 1); + CORRADE_COMPARE(scene.fieldObjectOffset(SceneField::Mesh, 2), 2); + CORRADE_COMPARE(scene.fieldObjectOffset(SceneField::Mesh, 2, 3), 4); + + CORRADE_VERIFY(!scene.hasFieldObject(0, 4)); + CORRADE_VERIFY(!scene.hasFieldObject(SceneField::Parent, 4)); + CORRADE_VERIFY(scene.hasFieldObject(1, 4)); + CORRADE_VERIFY(scene.hasFieldObject(1, 1)); + CORRADE_VERIFY(scene.hasFieldObject(1, 2)); + CORRADE_VERIFY(!scene.hasFieldObject(1, 3)); + CORRADE_VERIFY(scene.hasFieldObject(SceneField::Mesh, 4)); + CORRADE_VERIFY(scene.hasFieldObject(SceneField::Mesh, 1)); + CORRADE_VERIFY(scene.hasFieldObject(SceneField::Mesh, 2)); + CORRADE_VERIFY(!scene.hasFieldObject(SceneField::Mesh, 3)); +} + +void SceneDataTest::findFieldObjectOffsetInvalidOffset() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + } fields[]{ + {4, 1}, + {1, 3}, + {2, 4} + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 7, {}, fields, { + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.findFieldObjectOffset(0, 1, 4); + scene.findFieldObjectOffset(SceneField::Mesh, 1, 4); + scene.fieldObjectOffset(0, 1, 4); + scene.fieldObjectOffset(SceneField::Mesh, 1, 4); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::findFieldObjectOffset(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::findFieldObjectOffset(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::fieldObjectOffset(): offset 4 out of bounds for a field of size 3\n" + "Trade::SceneData::fieldObjectOffset(): offset 4 out of bounds for a field of size 3\n"); +} + +void SceneDataTest::fieldObjectOffsetNotFound() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + } fields[]{ + {4, 1}, + {1, 3}, + {2, 4}, + {0, 5}, + {2, 5} + }; + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 7, {}, fields, { + /* Test also with a completely empty field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.fieldObjectOffset(0, 4); + scene.fieldObjectOffset(SceneField::Parent, 4); + scene.fieldObjectOffset(1, 1, 2); + scene.fieldObjectOffset(SceneField::Mesh, 1, 2); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::fieldObjectOffset(): object 4 not found in field Trade::SceneField::Parent starting at offset 0\n" + "Trade::SceneData::fieldObjectOffset(): object 4 not found in field Trade::SceneField::Parent starting at offset 0\n" + "Trade::SceneData::fieldObjectOffset(): object 1 not found in field Trade::SceneField::Mesh starting at offset 2\n" + "Trade::SceneData::fieldObjectOffset(): object 1 not found in field Trade::SceneField::Mesh starting at offset 2\n"); +} + template void SceneDataTest::objectsAsArrayByIndex() { setTestCaseTemplateName(NameTraits::name()); @@ -4108,6 +4252,9 @@ void SceneDataTest::fieldNotFound() { std::ostringstream out; Error redirectError{&out}; + scene.findFieldObjectOffset(2, 0); + scene.fieldObjectOffset(2, 0); + scene.hasFieldObject(2, 0); scene.fieldData(2); scene.fieldName(2); scene.fieldType(2); @@ -4121,6 +4268,9 @@ void SceneDataTest::fieldNotFound() { scene.mutableField(2); scene.fieldId(sceneFieldCustom(666)); + scene.findFieldObjectOffset(sceneFieldCustom(666), 0); + scene.fieldObjectOffset(sceneFieldCustom(666), 0); + scene.hasFieldObject(sceneFieldCustom(666), 0); scene.fieldType(sceneFieldCustom(666)); scene.fieldSize(sceneFieldCustom(666)); scene.fieldArraySize(sceneFieldCustom(666)); @@ -4162,6 +4312,9 @@ void SceneDataTest::fieldNotFound() { scene.importerStateInto(nullptr); scene.importerStateInto(0, nullptr); CORRADE_COMPARE(out.str(), + "Trade::SceneData::findFieldObjectOffset(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldObjectOffset(): index 2 out of range for 2 fields\n" + "Trade::SceneData::hasFieldObject(): index 2 out of range for 2 fields\n" "Trade::SceneData::fieldData(): index 2 out of range for 2 fields\n" "Trade::SceneData::fieldName(): index 2 out of range for 2 fields\n" "Trade::SceneData::fieldType(): index 2 out of range for 2 fields\n" @@ -4175,6 +4328,9 @@ void SceneDataTest::fieldNotFound() { "Trade::SceneData::mutableField(): index 2 out of range for 2 fields\n" "Trade::SceneData::fieldId(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::findFieldObjectOffset(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::fieldObjectOffset(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::hasFieldObject(): field Trade::SceneField::Custom(666) not found\n" "Trade::SceneData::fieldType(): field Trade::SceneField::Custom(666) not found\n" "Trade::SceneData::fieldSize(): field Trade::SceneField::Custom(666) not found\n" "Trade::SceneData::fieldArraySize(): field Trade::SceneField::Custom(666) not found\n" @@ -4370,11 +4526,9 @@ void SceneDataTest::fieldWrongArrayAccess() { "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n"); } -template void SceneDataTest::parentFor() { - setTestCaseTemplateName(NameTraits::name()); - +void SceneDataTest::parentFor() { struct Field { - T object; + UnsignedInt object; Int parent; } fields[]{ {3, -1}, @@ -4384,7 +4538,7 @@ template void SceneDataTest::parentFor() { }; 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)} }}; @@ -4867,15 +5021,23 @@ void SceneDataTest::fieldForFieldMissing() { TestSuite::Compare::Container); } -void SceneDataTest::fieldForInvalidObject() { +void SceneDataTest::findFieldObjectOffsetInvalidObject() { #ifdef CORRADE_NO_ASSERT CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); #endif - SceneData scene{SceneObjectType::UnsignedInt, 7, nullptr, {}}; + SceneData scene{SceneObjectType::UnsignedInt, 7, nullptr, { + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + }}; std::ostringstream out; Error redirectError{&out}; + scene.findFieldObjectOffset(0, 7); + scene.findFieldObjectOffset(SceneField::Parent, 7); + scene.fieldObjectOffset(0, 7); + scene.fieldObjectOffset(SceneField::Parent, 7); + scene.hasFieldObject(0, 7); + scene.hasFieldObject(SceneField::Parent, 7); scene.parentFor(7); scene.childrenFor(-2); scene.childrenFor(7); @@ -4888,6 +5050,12 @@ void SceneDataTest::fieldForInvalidObject() { scene.camerasFor(7); scene.skinsFor(7); CORRADE_COMPARE(out.str(), + "Trade::SceneData::findFieldObjectOffset(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::findFieldObjectOffset(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::fieldObjectOffset(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::fieldObjectOffset(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::hasFieldObject(): object 7 out of bounds for 7 objects\n" + "Trade::SceneData::hasFieldObject(): object 7 out of bounds for 7 objects\n" "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"