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"