diff --git a/doc/changelog.dox b/doc/changelog.dox index 00c587c04..db333ca6b 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -219,6 +219,9 @@ See also: @ref Trade::PhongMaterialData::normalTextureScale() and @ref Trade::PhongMaterialData::normalTextureSwizzle() to make new features added for PBR materials recognizable also in classic Phong workflows. +- A completely redesigned @ref Trade::SceneData class that stores data of + the whole scene in a data-oriented way, allowing for storing custom fields + as well. See [mosra/magnum#525](https://github.com/mosra/magnum/pull/525). - New @ref Trade::SkinData class and @ref Trade::AbstractImporter::skin2D() / @ref Trade::AbstractImporter::skin3D() family of APIs for skin import, as well as support in @ref Trade::AnySceneImporter "AnySceneImporter" diff --git a/doc/vulkan-mapping.dox b/doc/vulkan-mapping.dox index 617d805c0..4e0b52354 100644 --- a/doc/vulkan-mapping.dox +++ b/doc/vulkan-mapping.dox @@ -429,7 +429,7 @@ Vulkan structure | Matching API @type_vk{ClearColorValue} | convertible from/to @ref Magnum::Vector3 "Vector3", @ref Magnum::Color3 "Color3", @ref Magnum::Vector4 "Vector4", @ref Magnum::Color4 "Color4", @ref Magnum::Vector4i "Vector4i", @ref Magnum::Vector4ui "Vector4ui" using @ref Magnum/Vk/Integration.h; only exposed through @ref RenderPassBeginInfo::clearColor() and @ref CommandBuffer::clearColorImage() overloads @type_vk{ClearDepthStencilValue} | only exposed through @ref RenderPassBeginInfo::clearDepthStencil(), @ref CommandBuffer::clearDepthStencilImage() and friends @type_vk{ClearValue} | only exposed through @ref RenderPassBeginInfo::clearColor() and @relativeref{RenderPassBeginInfo,clearDepthStencil()} overloads -@type_vk{ClearRect} | convertible from/to @ref Range3Di using @ref Magnum/Vk/Integration.h +@type_vk{ClearRect} | convertible from/to @relativeref{Magnum,Range3Di} using @ref Magnum/Vk/Integration.h @type_vk{CommandBufferAllocateInfo} | not exposed, internal to @ref CommandPool::allocate() @type_vk{CommandBufferBeginInfo} | @ref CommandBufferBeginInfo @type_vk{CommandBufferInheritanceInfo} | | @@ -692,7 +692,7 @@ Vulkan structure | Matching API @type_vk{RayTracingPipelineCreateInfoKHR} @m_class{m-label m-flat m-warning} **KHR** | | @type_vk{RayTracingPipelineInterfaceCreateInfoKHR} @m_class{m-label m-flat m-warning} **KHR** | | @type_vk{RayTracingShaderGroupCreateInfoKHR} @m_class{m-label m-flat m-warning} **KHR** | | -@type_vk{Rect2D} | convertible from/to @ref Range2Di using @ref Magnum/Vk/Integration.h +@type_vk{Rect2D} | convertible from/to @relativeref{Magnum,Range2Di} using @ref Magnum/Vk/Integration.h @type_vk{RenderPassBeginInfo} | @ref RenderPassBeginInfo @type_vk{RenderPassAttachmentBeginInfo} @m_class{m-label m-flat m-success} **KHR, 1.2** | | @type_vk{RenderPassCreateInfo}, \n @type_vk{RenderPassCreateInfo2} @m_class{m-label m-flat m-success} **KHR, 1.2** | @ref RenderPassCreateInfo @@ -755,7 +755,7 @@ Vulkan structure | Matching API @type_vk{VertexInputBindingDescription} | @ref MeshLayout @type_vk{VertexInputBindingDivisorDescriptionEXT} @m_class{m-label m-flat m-warning} **EXT** | @ref MeshLayout @type_vk{VertexInputAttributeDescription} | @ref MeshLayout -@type_vk{Viewport} | convertible from/to @ref Range3D using @ref Magnum/Vk/Integration.h +@type_vk{Viewport} | convertible from/to @relativeref{Magnum,Range3D} using @ref Magnum/Vk/Integration.h @subsection vulkan-mapping-structures-w W diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index 9aa7a27c6..ec032592e 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -30,7 +30,6 @@ set(MagnumTrade_SRCS Data.cpp MeshObjectData2D.cpp MeshObjectData3D.cpp - SceneData.cpp TextureData.cpp) set(MagnumTrade_GracefulAssert_SRCS @@ -50,6 +49,7 @@ set(MagnumTrade_GracefulAssert_SRCS PbrMetallicRoughnessMaterialData.cpp PbrSpecularGlossinessMaterialData.cpp PhongMaterialData.cpp + SceneData.cpp SkinData.cpp) set(MagnumTrade_HEADERS diff --git a/src/Magnum/Trade/SceneData.cpp b/src/Magnum/Trade/SceneData.cpp index 44c6bf29e..4e92ebe25 100644 --- a/src/Magnum/Trade/SceneData.cpp +++ b/src/Magnum/Trade/SceneData.cpp @@ -25,20 +25,1127 @@ #include "SceneData.h" +#include + +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" +#include "Magnum/Math/DualComplex.h" +#include "Magnum/Math/DualQuaternion.h" +#include "Magnum/Math/PackingBatch.h" +#include "Magnum/Trade/Implementation/arrayUtilities.h" + namespace Magnum { namespace Trade { -SceneData::SceneData(std::vector children2D, std::vector children3D, const void* const importerState): _children2D{std::move(children2D)}, _children3D{std::move(children3D)}, _importerState{importerState} {} +Debug& operator<<(Debug& debug, const SceneObjectType value) { + debug << "Trade::SceneObjectType" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case SceneObjectType::value: return debug << "::" #value; + _c(UnsignedByte) + _c(UnsignedInt) + _c(UnsignedShort) + _c(UnsignedLong) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +UnsignedInt sceneObjectTypeSize(const SceneObjectType type) { + switch(type) { + case SceneObjectType::UnsignedByte: return 1; + case SceneObjectType::UnsignedShort: return 2; + case SceneObjectType::UnsignedInt: return 4; + case SceneObjectType::UnsignedLong: return 8; + } + + CORRADE_ASSERT_UNREACHABLE("Trade::sceneObjectTypeSize(): invalid type" << type, {}); +} + +Debug& operator<<(Debug& debug, const SceneField value) { + debug << "Trade::SceneField" << Debug::nospace; + + if(UnsignedInt(value) >= UnsignedInt(SceneField::Custom)) + return debug << "::Custom(" << Debug::nospace << (UnsignedInt(value) - UnsignedInt(SceneField::Custom)) << Debug::nospace << ")"; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case SceneField::value: return debug << "::" #value; + _c(Parent) + _c(Transformation) + _c(Translation) + _c(Rotation) + _c(Scaling) + _c(Mesh) + _c(MeshMaterial) + _c(Light) + _c(Camera) + _c(Skin) + #undef _c + /* LCOV_EXCL_STOP */ + + /* To silence compiler warning about unhandled values */ + case SceneField::Custom: CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const SceneFieldType value) { + debug << "Trade::SceneFieldType" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(value) case SceneFieldType::value: return debug << "::" #value; + _c(Float) + _c(Half) + _c(Double) + _c(UnsignedByte) + _c(Byte) + _c(UnsignedShort) + _c(Short) + _c(UnsignedInt) + _c(Int) + _c(UnsignedLong) + _c(Long) + _c(Vector2) + _c(Vector2h) + _c(Vector2d) + _c(Vector2ub) + _c(Vector2b) + _c(Vector2us) + _c(Vector2s) + _c(Vector2ui) + _c(Vector2i) + _c(Vector3) + _c(Vector3h) + _c(Vector3d) + _c(Vector3ub) + _c(Vector3b) + _c(Vector3us) + _c(Vector3s) + _c(Vector3ui) + _c(Vector3i) + _c(Vector4) + _c(Vector4h) + _c(Vector4d) + _c(Vector4ub) + _c(Vector4b) + _c(Vector4us) + _c(Vector4s) + _c(Vector4ui) + _c(Vector4i) + _c(Matrix2x2) + _c(Matrix2x2h) + _c(Matrix2x2d) + _c(Matrix2x3) + _c(Matrix2x3h) + _c(Matrix2x3d) + _c(Matrix2x4) + _c(Matrix2x4h) + _c(Matrix2x4d) + _c(Matrix3x2) + _c(Matrix3x2h) + _c(Matrix3x2d) + _c(Matrix3x3) + _c(Matrix3x3h) + _c(Matrix3x3d) + _c(Matrix3x4) + _c(Matrix3x4h) + _c(Matrix3x4d) + _c(Matrix4x2) + _c(Matrix4x2h) + _c(Matrix4x2d) + _c(Matrix4x3) + _c(Matrix4x3h) + _c(Matrix4x3d) + _c(Matrix4x4) + _c(Matrix4x4h) + _c(Matrix4x4d) + _c(Range1D) + _c(Range1Dh) + _c(Range1Dd) + _c(Range1Di) + _c(Range2D) + _c(Range2Dh) + _c(Range2Dd) + _c(Range2Di) + _c(Range3D) + _c(Range3Dh) + _c(Range3Dd) + _c(Range3Di) + _c(Complex) + _c(Complexd) + _c(DualComplex) + _c(DualComplexd) + _c(Quaternion) + _c(Quaterniond) + _c(DualQuaternion) + _c(DualQuaterniond) + _c(Deg) + _c(Degh) + _c(Degd) + _c(Rad) + _c(Radh) + _c(Radd) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedShort(value)) << Debug::nospace << ")"; +} + +UnsignedInt sceneFieldTypeSize(const SceneFieldType type) { + switch(type) { + case SceneFieldType::UnsignedByte: + case SceneFieldType::Byte: + return 1; + case SceneFieldType::UnsignedShort: + case SceneFieldType::Short: + case SceneFieldType::Half: + case SceneFieldType::Vector2ub: + case SceneFieldType::Vector2b: + case SceneFieldType::Degh: + case SceneFieldType::Radh: + return 2; + case SceneFieldType::Vector3ub: + case SceneFieldType::Vector3b: + return 3; + case SceneFieldType::UnsignedInt: + case SceneFieldType::Int: + case SceneFieldType::Float: + case SceneFieldType::Vector2us: + case SceneFieldType::Vector2s: + case SceneFieldType::Vector2h: + case SceneFieldType::Vector4ub: + case SceneFieldType::Vector4b: + case SceneFieldType::Range1Dh: + case SceneFieldType::Deg: + case SceneFieldType::Rad: + return 4; + case SceneFieldType::Vector3us: + case SceneFieldType::Vector3s: + case SceneFieldType::Vector3h: + return 6; + case SceneFieldType::UnsignedLong: + case SceneFieldType::Long: + case SceneFieldType::Double: + case SceneFieldType::Vector2: + case SceneFieldType::Vector2ui: + case SceneFieldType::Vector2i: + case SceneFieldType::Vector4us: + case SceneFieldType::Vector4s: + case SceneFieldType::Vector4h: + case SceneFieldType::Matrix2x2h: + case SceneFieldType::Range1D: + case SceneFieldType::Range1Di: + case SceneFieldType::Range2Dh: + case SceneFieldType::Complex: + case SceneFieldType::Degd: + case SceneFieldType::Radd: + return 8; + case SceneFieldType::Vector3: + case SceneFieldType::Vector3ui: + case SceneFieldType::Vector3i: + case SceneFieldType::Matrix2x3h: + case SceneFieldType::Matrix3x2h: + case SceneFieldType::Range3Dh: + return 12; + case SceneFieldType::Vector2d: + case SceneFieldType::Vector4: + case SceneFieldType::Vector4ui: + case SceneFieldType::Vector4i: + case SceneFieldType::Matrix2x2: + case SceneFieldType::Matrix2x4h: + case SceneFieldType::Matrix4x2h: + case SceneFieldType::Range1Dd: + case SceneFieldType::Range2D: + case SceneFieldType::Range2Di: + case SceneFieldType::Complexd: + case SceneFieldType::DualComplex: + case SceneFieldType::Quaternion: + return 16; + case SceneFieldType::Matrix3x3h: + return 18; + case SceneFieldType::Vector3d: + case SceneFieldType::Matrix2x3: + case SceneFieldType::Matrix3x4h: + case SceneFieldType::Matrix3x2: + case SceneFieldType::Matrix4x3h: + case SceneFieldType::Range3D: + case SceneFieldType::Range3Di: + return 24; + case SceneFieldType::Vector4d: + case SceneFieldType::Matrix2x2d: + case SceneFieldType::Matrix2x4: + case SceneFieldType::Matrix4x2: + case SceneFieldType::Matrix4x4h: + case SceneFieldType::Range2Dd: + case SceneFieldType::DualComplexd: + case SceneFieldType::Quaterniond: + case SceneFieldType::DualQuaternion: + return 32; + case SceneFieldType::Matrix3x3: + return 36; + case SceneFieldType::Matrix2x3d: + case SceneFieldType::Matrix3x4: + case SceneFieldType::Matrix3x2d: + case SceneFieldType::Matrix4x3: + case SceneFieldType::Range3Dd: + return 48; + case SceneFieldType::Matrix2x4d: + case SceneFieldType::Matrix4x2d: + case SceneFieldType::Matrix4x4: + case SceneFieldType::DualQuaterniond: + return 64; + case SceneFieldType::Matrix3x3d: + return 72; + case SceneFieldType::Matrix3x4d: + case SceneFieldType::Matrix4x3d: + return 96; + case SceneFieldType::Matrix4x4d: + return 128; + } + + CORRADE_ASSERT_UNREACHABLE("Trade::sceneFieldTypeSize(): invalid type" << type, {}); +} + +SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView2D& objectData, const SceneFieldType fieldType, const Containers::StridedArrayView2D& fieldData, const UnsignedShort fieldArraySize) noexcept: SceneFieldData{name, {}, Containers::StridedArrayView1D{{objectData.data(), ~std::size_t{}}, objectData.size()[0], objectData.stride()[0]}, fieldType, Containers::StridedArrayView1D{{fieldData.data(), ~std::size_t{}}, fieldData.size()[0], fieldData.stride()[0]}, fieldArraySize} { + /* Yes, this calls into a constexpr function defined in the header -- + because I feel that makes more sense than duplicating the full assert + logic */ + #ifndef CORRADE_NO_ASSERT + if(fieldArraySize) CORRADE_ASSERT(fieldData.empty()[0] || fieldData.size()[1] == sceneFieldTypeSize(fieldType)*fieldArraySize, + "Trade::SceneFieldData: second field view dimension size" << fieldData.size()[1] << "doesn't match" << fieldType << "and field array size" << fieldArraySize, ); + else CORRADE_ASSERT(fieldData.empty()[0] || fieldData.size()[1] == sceneFieldTypeSize(fieldType), + "Trade::SceneFieldData: second field view dimension size" << fieldData.size()[1] << "doesn't match" << fieldType, ); + #endif + + if(objectData.size()[1] == 8) _objectType = SceneObjectType::UnsignedLong; + else if(objectData.size()[1] == 4) _objectType = SceneObjectType::UnsignedInt; + else if(objectData.size()[1] == 2) _objectType = SceneObjectType::UnsignedShort; + else if(objectData.size()[1] == 1) _objectType = SceneObjectType::UnsignedByte; + else CORRADE_ASSERT_UNREACHABLE("Trade::SceneFieldData: expected second object view dimension size 1, 2, 4 or 8 but got" << objectData.size()[1], ); + + CORRADE_ASSERT(fieldData.isContiguous<1>(), "Trade::SceneFieldData: second field view dimension is not contiguous", ); + CORRADE_ASSERT(objectData.isContiguous<1>(), "Trade::SceneFieldData: second object view dimension is not contiguous", ); +} + +Containers::Array sceneFieldDataNonOwningArray(const Containers::ArrayView view) { + /* Ugly, eh? */ + return Containers::Array{const_cast(view.data()), view.size(), reinterpret_cast(Implementation::nonOwnedArrayDeleter)}; +} + +SceneData::SceneData(const SceneObjectType objectType, const UnsignedLong objectCount, Containers::Array&& data, Containers::Array&& fields, const void* const importerState) noexcept: _dataFlags{DataFlag::Owned|DataFlag::Mutable}, _objectType{objectType}, _objectCount{objectCount}, _importerState{importerState}, _fields{std::move(fields)}, _data{std::move(data)} { + /* Check that object type is large enough */ + CORRADE_ASSERT( + (objectType == SceneObjectType::UnsignedByte && objectCount <= 0xffull) || + (objectType == SceneObjectType::UnsignedShort && objectCount <= 0xffffull) || + (objectType == SceneObjectType::UnsignedInt && objectCount <= 0xffffffffull) || + objectType == SceneObjectType::UnsignedLong, + "Trade::SceneData:" << objectType << "is too small for" << objectCount << "objects", ); + + #ifndef CORRADE_NO_ASSERT + /* Check various assumptions about field data */ + Math::BoolVector<11> fieldsPresent; /** @todo some constant for this */ + const UnsignedInt objectTypeSize = sceneObjectTypeSize(_objectType); + UnsignedInt translationField = ~UnsignedInt{}; + UnsignedInt rotationField = ~UnsignedInt{}; + UnsignedInt scalingField = ~UnsignedInt{}; + UnsignedInt meshField = ~UnsignedInt{}; + UnsignedInt meshMaterialField = ~UnsignedInt{}; + for(std::size_t i = 0; i != _fields.size(); ++i) { + const SceneFieldData& field = _fields[i]; + + /* The object type has to be the same among all fields. Technically it + wouldn't need to be, but if there's 60k objects then using a 8bit + type for certain fields would mean only the first 256 objects can be + referenced, which makes no practical sense, and to improve the + situation there would need to be some additional per-field object + offset and ... it's simpler to just require the object type to be + large enough to reference all objects (checked outside of the loop + above) and that it's the same for all fields. This also makes it + more convenient for the user. */ + CORRADE_ASSERT(field._objectType == _objectType, + "Trade::SceneData: inconsistent object type, got" << field._objectType << "for field" << i << "but expected" << _objectType, ); + + /* We could check that object indices are in bounds, but that's rather + expensive. OTOH it's fine if field size is larger than object count, + as a single object can have multiple instances of the same field + attached, so checking that would be wrong. */ + + /* Check that there are only unique fields. To avoid a O(n^2) operation + always (or allocating a sorted field map), builtin fields are + checked against a map and only custom fields are checked in an + O(n^2) way with the assumption there isn't many of them (and that + they'll gradually become builtin). */ + if(!isSceneFieldCustom(_fields[i]._name)) { + CORRADE_INTERNAL_ASSERT(UnsignedInt(_fields[i]._name) < fieldsPresent.Size); + CORRADE_ASSERT(!fieldsPresent[UnsignedInt(_fields[i]._name)], + "Trade::SceneData: duplicate field" << _fields[i]._name, ); + fieldsPresent.set(UnsignedInt(_fields[i]._name), true); + } else for(std::size_t j = 0; j != i; ++j) { + CORRADE_ASSERT(_fields[j]._name != _fields[i]._name, + "Trade::SceneData: duplicate field" << _fields[i]._name, ); + } + + /* Check that both the object and field view fits into the provided + data array. If the field is empty, we don't check anything -- + accessing the memory would be invalid anyway and enforcing this + would lead to unnecessary friction with optional fields. */ + if(field._size) { + const UnsignedInt fieldTypeSize = sceneFieldTypeSize(field._fieldType)* + (field._fieldArraySize ? field._fieldArraySize : 1); + if(field._isOffsetOnly) { + const std::size_t objectSize = field._objectData.offset + (field._size - 1)*field._objectStride + objectTypeSize; + const std::size_t fieldSize = field._fieldData.offset + (field._size - 1)*field._fieldStride + fieldTypeSize; + CORRADE_ASSERT(objectSize <= _data.size(), + "Trade::SceneData: offset-only object data of field" << i << "span" << objectSize << "bytes but passed data array has only" << _data.size(), ); + CORRADE_ASSERT(fieldSize <= _data.size(), + "Trade::SceneData: offset-only field data of field" << i << "span" << fieldSize << "bytes but passed data array has only" << _data.size(), ); + } else { + const void* const objectBegin = field._objectData.pointer; + const void* const fieldBegin = field._fieldData.pointer; + const void* const objectEnd = static_cast(field._objectData.pointer) + (field._size - 1)*field._objectStride + objectTypeSize; + const void* const fieldEnd = static_cast(field._fieldData.pointer) + (field._size - 1)*field._fieldStride + fieldTypeSize; + CORRADE_ASSERT(objectBegin >= _data.begin() && objectEnd <= _data.end(), + "Trade::SceneData: object data [" << Debug::nospace << objectBegin << Debug::nospace << ":" << Debug::nospace << objectEnd << Debug::nospace << "] of field" << i << "are not contained in passed data array [" << Debug::nospace << static_cast(_data.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_data.end()) << Debug::nospace << "]", ); + CORRADE_ASSERT(fieldBegin >= _data.begin() && fieldEnd <= _data.end(), + "Trade::SceneData: field data [" << Debug::nospace << fieldBegin << Debug::nospace << ":" << Debug::nospace << fieldEnd << Debug::nospace << "] of field" << i << "are not contained in passed data array [" << Debug::nospace << static_cast(_data.begin()) << Debug::nospace << ":" << Debug::nospace << static_cast(_data.end()) << Debug::nospace << "]", ); + } + } + + /* Remember TRS and mesh/material fields to check their object mapping + consistency outside of the loop below */ + if(_fields[i]._name == SceneField::Translation) { + translationField = i; + } else if(_fields[i]._name == SceneField::Rotation) { + rotationField = i; + } else if(_fields[i]._name == SceneField::Scaling) { + scalingField = i; + } else if(_fields[i]._name == SceneField::Mesh) { + meshField = i; + } else if(_fields[i]._name == SceneField::MeshMaterial) { + meshMaterialField = i; + } + } + + /* Check that certain fields share the same object mapping. Printing as if + all would be pointers (and not offset-only), it's not worth the extra + effort just for an assert message. Also, compared to above, where + "begin" was always zero, here we're always comparing four values, so the + message for offset-only wouldn't be simpler either. */ + const auto checkFieldObjectDataMatch = [](const SceneFieldData& a, const SceneFieldData& b) { + const std::size_t objectTypeSize = sceneObjectTypeSize(a._objectType); + const void* const aBegin = a._objectData.pointer; + const void* const bBegin = b._objectData.pointer; + const void* const aEnd = static_cast(a._objectData.pointer) + a._size*objectTypeSize; + const void* const bEnd = static_cast(b._objectData.pointer) + b._size*objectTypeSize; + CORRADE_ASSERT(aBegin == bBegin && aEnd == bEnd, + "Trade::SceneData:" << b._name << "object data [" << Debug::nospace << bBegin << Debug::nospace << ":" << Debug::nospace << bEnd << Debug::nospace << "] is different from" << a._name << "object data [" << Debug::nospace << aBegin << Debug::nospace << ":" << Debug::nospace << aEnd << Debug::nospace << "]", ); + }; + + /* All present TRS fields should share the same object mapping */ + if(translationField != ~UnsignedInt{}) { + if(rotationField != ~UnsignedInt{}) + checkFieldObjectDataMatch(_fields[translationField], _fields[rotationField]); + if(scalingField != ~UnsignedInt{}) + checkFieldObjectDataMatch(_fields[translationField], _fields[scalingField]); + } + if(rotationField != ~UnsignedInt{} && scalingField != ~UnsignedInt{}) + checkFieldObjectDataMatch(_fields[rotationField], _fields[scalingField]); + + /* Mesh and materials also */ + if(meshField != ~UnsignedInt{} && meshMaterialField != ~UnsignedInt{}) + checkFieldObjectDataMatch(_fields[meshField], _fields[meshMaterialField]); + #endif +} + +SceneData::SceneData(const SceneObjectType objectType, const UnsignedLong objectCount, Containers::Array&& data, const std::initializer_list fields, const void* const importerState): SceneData{objectType, objectCount, std::move(data), Implementation::initializerListToArrayWithDefaultDeleter(fields), importerState} {} + +SceneData::SceneData(const SceneObjectType objectType, const UnsignedLong objectCount, const DataFlags dataFlags, const Containers::ArrayView data, Containers::Array&& fields, const void* const importerState) noexcept: SceneData{objectType, objectCount, Containers::Array{const_cast(static_cast(data.data())), data.size(), Implementation::nonOwnedArrayDeleter}, std::move(fields), importerState} { + CORRADE_ASSERT(!(dataFlags & DataFlag::Owned), + "Trade::SceneData: can't construct with non-owned data but" << dataFlags, ); + _dataFlags = dataFlags; +} + +SceneData::SceneData(const SceneObjectType objectType, const UnsignedLong objectCount, const DataFlags dataFlags, const Containers::ArrayView data, const std::initializer_list fields, const void* const importerState): SceneData{objectType, objectCount, dataFlags, data, Implementation::initializerListToArrayWithDefaultDeleter(fields), importerState} {} + +SceneData::SceneData(SceneData&&) noexcept = default; + +SceneData::~SceneData() = default; + +SceneData& SceneData::operator=(SceneData&&) noexcept = default; + +Containers::ArrayView SceneData::mutableData() & { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::SceneData::mutableData(): data not mutable", {}); + return _data; +} + +Containers::StridedArrayView1D SceneData::fieldDataObjectViewInternal(const SceneFieldData& field) const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + {field._isOffsetOnly ? _data.data() + field._objectData.offset : + field._objectData.pointer, ~std::size_t{}}, + field._size, field._objectStride}; +} + +Containers::StridedArrayView1D SceneData::fieldDataFieldViewInternal(const SceneFieldData& field) const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + {field._isOffsetOnly ? _data.data() + field._fieldData.offset : + field._fieldData.pointer, ~std::size_t{}}, + field._size, field._fieldStride}; +} + +SceneFieldData SceneData::fieldData(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldData(): index" << id << "out of range for" << _fields.size() << "fields", SceneFieldData{}); + const SceneFieldData& field = _fields[id]; + return SceneFieldData{field._name, field._objectType, fieldDataObjectViewInternal(field), field._fieldType, fieldDataFieldViewInternal(field), field._fieldArraySize}; +} + +SceneField SceneData::fieldName(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldName(): index" << id << "out of range for" << _fields.size() << "fields", {}); + return _fields[id]._name; +} + +SceneFieldType SceneData::fieldType(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldType(): index" << id << "out of range for" << _fields.size() << "fields", {}); + return _fields[id]._fieldType; +} + +std::size_t SceneData::fieldSize(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldSize(): index" << id << "out of range for" << _fields.size() << "fields", {}); + return _fields[id]._size; +} + +UnsignedShort SceneData::fieldArraySize(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::fieldArraySize(): index" << id << "out of range for" << _fields.size() << "fields", {}); + return _fields[id]._fieldArraySize; +} + +UnsignedInt SceneData::fieldFor(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{}; +} + +UnsignedInt SceneData::fieldId(const SceneField name) const { + const UnsignedInt fieldId = fieldFor(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldId(): field" << name << "not found", {}); + return fieldId; +} + +SceneFieldType SceneData::fieldType(const SceneField name) const { + const UnsignedInt fieldId = fieldFor(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); + 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); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::fieldArraySize(): field" << name << "not found", {}); + return _fields[fieldId]._fieldArraySize; +} + +Containers::StridedArrayView2D SceneData::objects(const UnsignedInt fieldId) const { + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::objects(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[fieldId]; + /* Build a 2D view using information about object type size */ + return Containers::arrayCast<2, const char>( + fieldDataObjectViewInternal(field), + sceneObjectTypeSize(field._objectType)); +} + +Containers::StridedArrayView2D SceneData::mutableObjects(const UnsignedInt fieldId) { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::SceneData::mutableObjects(): data not mutable", {}); + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::mutableObjects(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[fieldId]; + /* Build a 2D view using information about attribute type size */ + const auto out = Containers::arrayCast<2, const char>( + fieldDataObjectViewInternal(field), + sceneObjectTypeSize(field._objectType)); + /** @todo some arrayConstCast? UGH */ + return Containers::StridedArrayView2D{ + /* The view size is there only for a size assert, we're pretty sure the + view is valid */ + {static_cast(const_cast(out.data())), ~std::size_t{}}, + out.size(), out.stride()}; +} + +Containers::StridedArrayView2D SceneData::objects(const SceneField fieldName) const { + const UnsignedInt fieldId = fieldFor(fieldName); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::objects(): field" << fieldName << "not found", {}); + return objects(fieldId); +} + +Containers::StridedArrayView2D SceneData::mutableObjects(const SceneField fieldName) { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::SceneData::mutableObjects(): data not mutable", {}); + const UnsignedInt fieldId = fieldFor(fieldName); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, "Trade::SceneData::mutableObjects(): field" << fieldName << "not found", {}); + return mutableObjects(fieldId); +} + +Containers::StridedArrayView2D SceneData::field(const UnsignedInt id) const { + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::field(): index" << id << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[id]; + /* Build a 2D view using information about object type size */ + return Containers::arrayCast<2, const char>( + fieldDataFieldViewInternal(field), + sceneFieldTypeSize(field._fieldType)*(field._fieldArraySize ? field._fieldArraySize : 1)); +} + +Containers::StridedArrayView2D SceneData::mutableField(const UnsignedInt id) { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::SceneData::mutableField(): data not mutable", {}); + CORRADE_ASSERT(id < _fields.size(), + "Trade::SceneData::mutableField(): index" << id << "out of range for" << _fields.size() << "fields", {}); + const SceneFieldData& field = _fields[id]; + /* Build a 2D view using information about attribute type size */ + const auto out = Containers::arrayCast<2, const char>( + fieldDataFieldViewInternal(field), + sceneFieldTypeSize(field._fieldType)*(field._fieldArraySize ? field._fieldArraySize : 1)); + /** @todo some arrayConstCast? UGH */ + return Containers::StridedArrayView2D{ + /* The view size is there only for a size assert, we're pretty sure the + view is valid */ + {static_cast(const_cast(out.data())), ~std::size_t{}}, + out.size(), out.stride()}; +} + +Containers::StridedArrayView2D SceneData::field(const SceneField name) const { + const UnsignedInt fieldId = fieldFor(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::field(): field" << name << "not found", {}); + return field(fieldId); +} -SceneData::SceneData(SceneData&&) - #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 - noexcept +Containers::StridedArrayView2D SceneData::mutableField(const SceneField name) { + CORRADE_ASSERT(_dataFlags & DataFlag::Mutable, + "Trade::SceneData::mutableField(): data not mutable", {}); + const UnsignedInt fieldId = fieldFor(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::mutableField(): field" << name << "not found", {}); + return mutableField(fieldId); +} + +void SceneData::objectsInto(const UnsignedInt fieldId, const Containers::StridedArrayView1D& destination) const { + CORRADE_ASSERT(fieldId < _fields.size(), + "Trade::SceneData::objectsInto(): index" << fieldId << "out of range for" << _fields.size() << "fields", ); + const SceneFieldData& field = _fields[fieldId]; + CORRADE_ASSERT(destination.size() == field._size, + "Trade::SceneData::objectsInto(): expected a view with" << field._size << "elements but got" << destination.size(), ); + const Containers::StridedArrayView1D objectData = fieldDataObjectViewInternal(field); + const auto destination1ui = Containers::arrayCast<2, UnsignedInt>(destination); + + if(field._objectType == SceneObjectType::UnsignedInt) + Utility::copy(Containers::arrayCast(objectData), destination); + else if(field._objectType == SceneObjectType::UnsignedShort) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(objectData, 1), destination1ui); + else if(field._objectType == SceneObjectType::UnsignedByte) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(objectData, 1), destination1ui); + else if(field._objectType == SceneObjectType::UnsignedLong) { + CORRADE_ASSERT(_objectCount <= 0xffffffffull, "Trade::SceneData::objectsInto(): indices for up to" << _objectCount << "objects can't fit into a 32-bit type, access them directly via objects() instead", ); + Math::castInto(Containers::arrayCast<2, const UnsignedLong>(objectData, 1), destination1ui); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array SceneData::objectsAsArray(const UnsignedInt fieldId) const { + CORRADE_ASSERT(fieldId < _fields.size(), + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::objectsInto(): index" << fieldId << "out of range for" << _fields.size() << "fields", {}); + Containers::Array out{NoInit, std::size_t(_fields[fieldId]._size)}; + objectsInto(fieldId, out); + return out; +} + +void SceneData::objectsInto(const SceneField name, const Containers::StridedArrayView1D& destination) const { + const UnsignedInt fieldId = fieldFor(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::objectsInto(): field" << name << "not found", ); + objectsInto(fieldId, destination); +} + +Containers::Array SceneData::objectsAsArray(const SceneField name) const { + const UnsignedInt fieldId = fieldFor(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::objectsInto(): field" << name << "not found", {}); + return objectsAsArray(fieldId); +} + +void SceneData::parentsInto(const Containers::StridedArrayView1D& destination) const { + const UnsignedInt fieldId = fieldFor(SceneField::Parent); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + "Trade::SceneData::parentsInto(): field not found", ); + const SceneFieldData& field = _fields[fieldId]; + CORRADE_ASSERT(destination.size() == field._size, + "Trade::SceneData::parentsInto(): expected a view with" << field._size << "elements but got" << destination.size(), ); + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const auto destination1i = Containers::arrayCast<2, Int>(destination); + + if(field._fieldType == SceneFieldType::Int) + Utility::copy(Containers::arrayCast(fieldData), destination); + else if(field._fieldType == SceneFieldType::Short) + Math::castInto(Containers::arrayCast<2, const Short>(fieldData, 1), destination1i); + else if(field._fieldType == SceneFieldType::Byte) + Math::castInto(Containers::arrayCast<2, const Byte>(fieldData, 1), destination1i); + else if(field._fieldType == SceneFieldType::Long) { + CORRADE_ASSERT(field._size <= 0xffffffffull, "Trade::SceneData::parentsInto(): parent indices for up to" << field._size << "objects can't fit into a 32-bit type, access them directly via field() instead", ); + Math::castInto(Containers::arrayCast<2, const Long>(fieldData, 1), destination1i); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array SceneData::parentsAsArray() const { + const UnsignedInt fieldId = fieldFor(SceneField::Parent); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::parentsInto(): field not found", {}); + Containers::Array out{NoInit, std::size_t(_fields[fieldId]._size)}; + parentsInto(out); + return out; +} + +namespace { + +template void convertTransformation(const Containers::StridedArrayView1D& source, const Containers::StridedArrayView1D& destination) { + CORRADE_INTERNAL_ASSERT(source.size() == destination.size()); + const auto sourceT = Containers::arrayCast(source); + for(std::size_t i = 0; i != sourceT.size(); ++i) + destination[i] = Destination{sourceT[i].toMatrix()}; +} + +/** @todo these (or the float variants at least) should eventually be replaced + with optimized batched APIs (applyTranslationsInto() updating just the + last matrix column etc.) */ + +template void applyTranslation(const Containers::StridedArrayView1D& source, const Containers::StridedArrayView1D& destination) { + CORRADE_INTERNAL_ASSERT(source.size() == destination.size()); + const auto sourceT = Containers::arrayCast(source); + for(std::size_t i = 0; i != sourceT.size(); ++i) + destination[i] = Destination::translation(Math::Vector{sourceT[i]})*destination[i]; +} + +template void applyRotation(const Containers::StridedArrayView1D& source, const Containers::StridedArrayView1D& destination) { + CORRADE_INTERNAL_ASSERT(source.size() == destination.size()); + const auto sourceT = Containers::arrayCast(source); + for(std::size_t i = 0; i != sourceT.size(); ++i) + destination[i] = Destination{Math::Matrix{ sourceT[i].toMatrix()}}*destination[i]; +} + +template void applyScaling(const Containers::StridedArrayView1D& source, const Containers::StridedArrayView1D& destination) { + CORRADE_INTERNAL_ASSERT(source.size() == destination.size()); + const auto sourceT = Containers::arrayCast(source); + for(std::size_t i = 0; i != sourceT.size(); ++i) + destination[i] = Destination::scaling(Math::Vector{sourceT[i]})*destination[i]; +} + +} + +std::size_t SceneData::findTransformFields(UnsignedInt& transformationFieldId, UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId) const { + UnsignedInt fieldToCheckForSize = ~UnsignedInt{}; + transformationFieldId = ~UnsignedInt{}; + translationFieldId = ~UnsignedInt{}; + rotationFieldId = ~UnsignedInt{}; + scalingFieldId = ~UnsignedInt{}; + for(std::size_t i = 0; i != _fields.size(); ++i) { + /* If we find a transformation field, we don't need to look any + further */ + if(_fields[i]._name == SceneField::Transformation) { + fieldToCheckForSize = transformationFieldId = i; + break; + } else if(_fields[i]._name == SceneField::Translation) { + fieldToCheckForSize = translationFieldId = i; + } else if(_fields[i]._name == SceneField::Rotation) { + fieldToCheckForSize = rotationFieldId = i; + } else if(_fields[i]._name == SceneField::Scaling) { + fieldToCheckForSize = scalingFieldId = i; + } + } + + /* Assuming the caller fires an appropriate assertion */ + return fieldToCheckForSize == ~UnsignedInt{} ? + ~std::size_t{} : _fields[fieldToCheckForSize]._size; +} + +void SceneData::transformations2DIntoInternal(const UnsignedInt transformationFieldId, const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, const Containers::StridedArrayView1D& destination) const { + /** @todo apply scalings as well if dual complex? */ + + /* Prefer the transformation field, if present */ + if(transformationFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[transformationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const auto destination1f = Containers::arrayCast<2, Float>(destination); + + if(field._fieldType == SceneFieldType::Matrix3x3) { + Utility::copy(Containers::arrayCast(fieldData), destination); + } else if(field._fieldType == SceneFieldType::Matrix3x3d) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 9), destination1f); + } else if(field._fieldType == SceneFieldType::DualComplex) { + convertTransformation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::DualComplexd) { + convertTransformation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Matrix4x4 || + field._fieldType == SceneFieldType::Matrix4x4d || + field._fieldType == SceneFieldType::DualQuaternion || + field._fieldType == SceneFieldType::DualQuaterniond) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::transformations2DInto(): field has a 3D transformation type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + /* If not, combine from TRS components */ + } else if(translationFieldId != ~UnsignedInt{} || rotationFieldId != ~UnsignedInt{} || scalingFieldId != ~UnsignedInt{}) { + /* First fill the destination with identity matrices */ + const Matrix3 identity[1]{Matrix3{Math::IdentityInit}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(destination.size()), destination); + + /* Apply scaling first, if present */ + if(scalingFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[scalingFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + + if(field._fieldType == SceneFieldType::Vector2) { + applyScaling(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Vector2d) { + applyScaling(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Vector3 || + field._fieldType == SceneFieldType::Vector3d) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::transformations2DInto(): field has a 3D scaling type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Apply rotation second, if present */ + if(rotationFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[rotationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + + if(field._fieldType == SceneFieldType::Complex) { + applyRotation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Complexd) { + applyRotation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Quaternion || + field._fieldType == SceneFieldType::Quaterniond) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::transformations2DInto(): field has a 3D rotation type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Apply translation last, if present */ + if(translationFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[translationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + + if(field._fieldType == SceneFieldType::Vector2) { + applyTranslation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Vector2d) { + applyTranslation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Vector3 || + field._fieldType == SceneFieldType::Vector3d) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::transformations2DInto(): field has a 3D translation type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Checked in the caller */ + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +void SceneData::transformations2DInto(const Containers::StridedArrayView1D& destination) const { + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + #ifndef CORRADE_NO_ASSERT + const std::size_t expectedSize = #endif - = default; + findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(expectedSize != ~std::size_t{}, + "Trade::SceneData::transformations2DInto(): no transformation-related field found", ); + CORRADE_ASSERT(expectedSize == destination.size(), + "Trade::SceneData::transformations2DInto(): expected a view with" << expectedSize << "elements but got" << destination.size(), ); + transformations2DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, destination); +} + +Containers::Array SceneData::transformations2DAsArray() const { + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + const std::size_t expectedSize = findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(expectedSize != ~std::size_t{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::transformations2DInto(): no transformation-related field found", {}); + Containers::Array out{NoInit, expectedSize}; + transformations2DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, out); + return out; +} -SceneData& SceneData::operator=(SceneData&&) - #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 - noexcept +void SceneData::transformations3DIntoInternal(const UnsignedInt transformationFieldId, const UnsignedInt translationFieldId, const UnsignedInt rotationFieldId, const UnsignedInt scalingFieldId, const Containers::StridedArrayView1D& destination) const { + /** @todo apply scalings as well if dual quat? */ + + /* Prefer the transformation field, if present */ + if(transformationFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[transformationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const auto destination1f = Containers::arrayCast<2, Float>(destination); + + if(field._fieldType == SceneFieldType::Matrix4x4) { + Utility::copy(Containers::arrayCast(fieldData), destination); + } else if(field._fieldType == SceneFieldType::Matrix4x4d) { + Math::castInto(Containers::arrayCast<2, const Double>(fieldData, 16), destination1f); + } else if(field._fieldType == SceneFieldType::DualQuaternion) { + convertTransformation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::DualQuaterniond) { + convertTransformation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Matrix3x3 || + field._fieldType == SceneFieldType::Matrix3x3d || + field._fieldType == SceneFieldType::DualComplex || + field._fieldType == SceneFieldType::DualComplexd) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::transformations3DInto(): field has a 2D transformation type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + /* If not, combine from TRS components */ + } else if(translationFieldId != ~UnsignedInt{} || rotationFieldId != ~UnsignedInt{} || scalingFieldId != ~UnsignedInt{}) { + /* First fill the destination with identity matrices */ + const Matrix4 identity[1]{Matrix4{Math::IdentityInit}}; + Utility::copy(Containers::stridedArrayView(identity).broadcasted<0>(destination.size()), destination); + + /* Apply scaling first, if present */ + if(scalingFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[scalingFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + + if(field._fieldType == SceneFieldType::Vector3) { + applyScaling(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Vector3d) { + applyScaling(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Vector2 || + field._fieldType == SceneFieldType::Vector2d) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::transformations3DInto(): field has a 2D scaling type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Apply rotation second, if present */ + if(rotationFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[rotationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + + if(field._fieldType == SceneFieldType::Quaternion) { + applyRotation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Quaterniond) { + applyRotation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Complex || + field._fieldType == SceneFieldType::Complexd) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::transformations3DInto(): field has a 2D rotation type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Apply translation last, if present */ + if(translationFieldId != ~UnsignedInt{}) { + const SceneFieldData& field = _fields[translationFieldId]; + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + + if(field._fieldType == SceneFieldType::Vector3) { + applyTranslation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Vector3d) { + applyTranslation(fieldData, destination); + } else if(field._fieldType == SceneFieldType::Vector2 || + field._fieldType == SceneFieldType::Vector2d) { + CORRADE_ASSERT_UNREACHABLE("Trade::SceneData::transformations3DInto(): field has a 2D translation type" << field._fieldType, ); + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + /* Checked in the caller */ + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +void SceneData::transformations3DInto(const Containers::StridedArrayView1D& destination) const { + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + #ifndef CORRADE_NO_ASSERT + const std::size_t expectedSize = #endif - = default; + findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(expectedSize != ~std::size_t{}, + "Trade::SceneData::transformations3DInto(): no transformation-related field found", ); + CORRADE_ASSERT(expectedSize == destination.size(), + "Trade::SceneData::transformations3DInto(): expected a view with" << expectedSize << "elements but got" << destination.size(), ); + transformations3DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, destination); +} + +Containers::Array SceneData::transformations3DAsArray() const { + UnsignedInt transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId; + const std::size_t expectedSize = findTransformFields(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId); + CORRADE_ASSERT(expectedSize != ~std::size_t{}, + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::transformations3DInto(): no transformation-related field found", {}); + Containers::Array out{NoInit, expectedSize}; + transformations3DIntoInternal(transformationFieldId, translationFieldId, rotationFieldId, scalingFieldId, out); + return out; +} + +void SceneData::indexFieldIntoInternal( + #ifndef CORRADE_NO_ASSERT + const char* const prefix, + #endif + const SceneField name, const Containers::StridedArrayView1D& destination) const +{ + const UnsignedInt fieldId = fieldFor(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, + prefix << "field not found", ); + const SceneFieldData& field = _fields[fieldId]; + CORRADE_ASSERT(destination.size() == field._size, + prefix << "expected a view with" << field._size << "elements but got" << destination.size(), ); + const Containers::StridedArrayView1D fieldData = fieldDataFieldViewInternal(field); + const auto destination1ui = Containers::arrayCast<2, UnsignedInt>(destination); + + if(field._fieldType == SceneFieldType::UnsignedInt) + Utility::copy(Containers::arrayCast(fieldData), destination); + else if(field._fieldType == SceneFieldType::UnsignedShort) + Math::castInto(Containers::arrayCast<2, const UnsignedShort>(fieldData, 1), destination1ui); + else if(field._fieldType == SceneFieldType::UnsignedByte) + Math::castInto(Containers::arrayCast<2, const UnsignedByte>(fieldData, 1), destination1ui); + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Array SceneData::indexFieldAsArrayInternal( + #ifndef CORRADE_NO_ASSERT + const char* const prefix, + #endif + const SceneField name) const +{ + const UnsignedInt fieldId = fieldFor(name); + CORRADE_ASSERT(fieldId != ~UnsignedInt{}, prefix << "field not found", {}); + Containers::Array out{NoInit, std::size_t(_fields[fieldId]._size)}; + indexFieldIntoInternal( + #ifndef CORRADE_NO_ASSERT + prefix, + #endif + name, out); + return out; +} + +void SceneData::meshesInto(const Containers::StridedArrayView1D& destination) const { + indexFieldIntoInternal( + #ifndef CORRADE_NO_ASSERT + "Trade::SceneData::meshesInto():", + #endif + SceneField::Mesh, destination); +} + +Containers::Array SceneData::meshesAsArray() const { + return indexFieldAsArrayInternal( + #ifndef CORRADE_NO_ASSERT + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::meshesInto():", + #endif + SceneField::Mesh); +} + +void SceneData::meshMaterialsInto(const Containers::StridedArrayView1D& destination) const { + indexFieldIntoInternal( + #ifndef CORRADE_NO_ASSERT + "Trade::SceneData::meshMaterialsInto():", + #endif + SceneField::MeshMaterial, destination); +} + +Containers::Array SceneData::meshMaterialsAsArray() const { + return indexFieldAsArrayInternal( + #ifndef CORRADE_NO_ASSERT + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::meshMaterialsInto():", + #endif + SceneField::MeshMaterial); +} + +void SceneData::lightsInto(const Containers::StridedArrayView1D& destination) const { + indexFieldIntoInternal( + #ifndef CORRADE_NO_ASSERT + "Trade::SceneData::lightsInto():", + #endif + SceneField::Light, destination); +} + +Containers::Array SceneData::lightsAsArray() const { + return indexFieldAsArrayInternal( + #ifndef CORRADE_NO_ASSERT + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::lightsInto():", + #endif + SceneField::Light); +} + +void SceneData::camerasInto(const Containers::StridedArrayView1D& destination) const { + indexFieldIntoInternal( + #ifndef CORRADE_NO_ASSERT + "Trade::SceneData::camerasInto():", + #endif + SceneField::Camera, destination); +} + +Containers::Array SceneData::camerasAsArray() const { + return indexFieldAsArrayInternal( + #ifndef CORRADE_NO_ASSERT + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::camerasInto():", + #endif + SceneField::Camera); +} + +void SceneData::skinsInto(const Containers::StridedArrayView1D& destination) const { + indexFieldIntoInternal( + #ifndef CORRADE_NO_ASSERT + "Trade::SceneData::skinsInto():", + #endif + SceneField::Skin, destination); +} + +Containers::Array SceneData::skinsAsArray() const { + return indexFieldAsArrayInternal( + #ifndef CORRADE_NO_ASSERT + /* Using the same message as in Into() to avoid too many redundant + strings in the binary */ + "Trade::SceneData::skinsInto():", + #endif + SceneField::Skin); +} + +Containers::Array SceneData::releaseFieldData() { + Containers::Array out = std::move(_fields); + _fields = {}; + return out; +} + +Containers::Array SceneData::releaseData() { + _fields = {}; + Containers::Array out = std::move(_data); + _data = {}; + return out; +} }} diff --git a/src/Magnum/Trade/SceneData.h b/src/Magnum/Trade/SceneData.h index 8a10cfd8f..2eb7e2943 100644 --- a/src/Magnum/Trade/SceneData.h +++ b/src/Magnum/Trade/SceneData.h @@ -26,62 +26,1508 @@ */ /** @file - * @brief Class @ref Magnum::Trade::SceneData + * @brief Class @ref Magnum::Trade::SceneData, @ref Magnum::Trade::SceneFieldData, enum @ref Magnum::Trade::SceneObjectType, @ref Magnum::Trade::SceneField, @ref Magnum::Trade::SceneFieldType, function @ref Magnum::sceneObjectTypeSize(), @ref Magnum::sceneFieldTypeSize(), @ref Magnum::Trade::isSceneFieldCustom(), @ref Magnum::sceneFieldCustom() */ -#include +#include +#include -#include "Magnum/Types.h" +#include "Magnum/Trade/Data.h" +#include "Magnum/Trade/Trade.h" #include "Magnum/Trade/visibility.h" namespace Magnum { namespace Trade { +/** +@brief Scene object type +@m_since_latest + +Type used for mapping fields to corresponding objects. Unlike +@ref SceneFieldType that is different for different fields, the object type is +the same for all fields, and is guaranteed to be large enough to fit all +@ref SceneData::objectCount() objects. +@see @ref SceneData::objectType(), @ref sceneObjectTypeSize() +*/ +enum class SceneObjectType: UnsignedByte { + /* Zero used for an invalid value */ + + UnsignedByte = 1, /**< @relativeref{Magnum,UnsignedByte} */ + UnsignedShort, /**< @relativeref{Magnum,UnsignedShort} */ + UnsignedInt, /**< @relativeref{Magnum,UnsignedInt} */ + + /** + * @relativeref{Magnum,UnsignedLong}. Meant to be used only in rare cases + * for *really huge* scenes. If this type is used and + * @ref SceneData::objectCount() is larger than the max representable + * 32-bit value, the indices can't be retrieved using + * @ref SceneData::objectsAsArray(), but only using appropriately typed + * @ref SceneData::objects(). + */ + UnsignedLong +}; + +/** +@debugoperatorenum{SceneObjectType} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneObjectType value); + +/** +@brief Size of given scene object type +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT UnsignedInt sceneObjectTypeSize(SceneObjectType type); + +/** +@brief Scene field name +@m_since_latest + +See @ref SceneData for more information. +@see @ref SceneFieldData, @ref SceneFieldType +*/ +enum class SceneField: UnsignedInt { + /* Zero used for an invalid value */ + + /** + * Parent index. Type is usually @ref SceneFieldType::Int, but can be also + * any of @relativeref{SceneFieldType,Byte}, + * @relativeref{SceneFieldType,Short} or, rarely, a + * @relativeref{SceneFieldType,Long}. A value of @cpp -1 @ce means there's + * no parent. An object should have only one parent, altough this isn't + * enforced in any way, and which of the duplicate fields gets used is not + * defined. + * + * Note that the index points to the parent array itself and isn't the + * actual object index --- the object mapping is stored in the + * corresponding @ref SceneData::objects() array. + * @see @ref SceneData::parentsAsArray() + */ + Parent = 1, + + /** + * Transformation. Type is usually @ref SceneFieldType::Matrix3x3 for 2D + * and @ref SceneFieldType::Matrix4x4 for 3D, but can be also any of + * @relativeref{SceneFieldType,Matrix3x3d}, + * @relativeref{SceneFieldType,DualComplex} or + * @relativeref{SceneFieldType,DualComplexd} for 2D and + * @relativeref{SceneFieldType,Matrix4x4d}, + * @relativeref{SceneFieldType,DualQuaternion} or + * @relativeref{SceneFieldType,DualQuaterniond} for 3D. An object should + * have only one transformation, altough this isn't enforced in any way, + * and which of the duplicate fields gets used is not defined. + * + * The transformation can be also represented by separate + * @ref SceneField::Translation, @ref SceneField::Rotation and + * @ref SceneField::Scaling fields. If both @ref SceneField::Transformation + * and TRS fields are specified, it's expected that all objects that have + * TRS fields have a combined transformation field as well, and + * @ref SceneData::transformations2DAsArray() / + * @ref SceneData::transformations3DAsArray() then takes into account only + * the combined transformation field. TRS fields can however be specified + * only for a subset of transformed objects, useful for example when only + * certain objects have these properties animated. + * @see @ref SceneData::transformations2DAsArray(), + * @ref SceneData::transformations3DAsArray() + */ + Transformation, + + /** + * Translation. Type is usually @ref SceneFieldType::Vector2 for 2D and + * @ref SceneFieldType::Vector3 for 3D, but can be also any of + * @relativeref{SceneFieldType,Vector2d} for 2D and + * @relativeref{SceneFieldType,Vector3d} for 3D. An object should + * have only one translation, altough this isn't enforced in any way, + * and which of the duplicate fields gets used is not defined. + * + * The translation field usually is (but doesn't have to be) complemented + * by a @ref SceneField::Rotation and @ref SceneField::Scaling, which, if + * present, are expected to all share the same object mapping view. The TRS + * components can either completely replace @ref SceneField::Transformation + * or be provided just for a subset of it --- see its documentation for + * details. + * @see @ref SceneData::transformations2DAsArray(), + * @ref SceneData::transformations3DAsArray() + */ + Translation, + + /** + * Rotation. Type is usually @ref SceneFieldType::Complex for 2D and + * @ref SceneFieldType::Quaternion for 3D, but can be also any of + * @relativeref{SceneFieldType,Complexd} for 2D and + * @relativeref{SceneFieldType,Quaterniond} for 3D. An object should have + * only one rotation, altough this isn't enforced in any way, and which of + * the duplicate fields gets used is not defined. + * + * The rotation field usually is (but doesn't have to be) complemented by a + * @ref SceneField::Translation and @ref SceneField::Scaling, which, if + * present, are expected to all share the same object mapping view. The TRS + * components can either completely replace @ref SceneField::Transformation + * or be provided just for a subset of it --- see its documentation for + * details. + * @see @ref SceneData::transformations2DAsArray(), + * @ref SceneData::transformations3DAsArray() + */ + Rotation, + + /** + * Scaling. Type is usually @ref SceneFieldType::Vector2 for 2D and + * @ref SceneFieldType::Vector3 for 3D, but can be also any of + * @relativeref{SceneFieldType,Vector2d} for 2D and + * @relativeref{SceneFieldType,Vector3d} for 3D. An object should + * have only one scaling, altough this isn't enforced in any way, and which + * of the duplicate fields gets used is not defined. + * + * The scaling field usually is (but doesn't have to be) complemented by a + * @ref SceneField::Translation and @ref SceneField::Rotation, which, if + * present, are expected to all share the same object mapping view. The TRS + * components can either completely replace @ref SceneField::Transformation + * or be provided just for a subset of it --- see its documentation for + * details. + * @see @ref SceneData::transformations2DAsArray(), + * @ref SceneData::transformations3DAsArray() + */ + Scaling, + + /** + * ID of a mesh associated with this object, corresponding to the ID passed + * to @ref AbstractImporter::mesh(). Type is usually + * @ref SceneFieldType::UnsignedInt, but can be also any of + * @relativeref{SceneFieldType,UnsignedByte} or + * @relativeref{SceneFieldType,UnsignedShort}. An object can have multiple + * meshes associated. + * + * Usually complemented with a @ref SceneField::MeshMaterial, although not + * required. If present, both should share the same object mapping view. + * Objects with multiple meshes then have the Nth mesh associated with the + * Nth material. + * @see @ref SceneData::meshesAsArray() + */ + Mesh, + + /** + * ID of a material for a @ref SceneField::Mesh, corresponding to the ID + * passed to @ref AbstractImporter::material(). Type is usually + * @ref SceneFieldType::UnsignedInt, but can be also any of + * @relativeref{SceneFieldType,UnsignedByte} or + * @relativeref{SceneFieldType,UnsignedShort}. Expected to share the + * object mapping view with @ref SceneField::Mesh. + * @see @ref SceneData::meshMaterialsAsArray() + */ + MeshMaterial, + + /** + * ID of a light associated with this object, corresponding to the ID + * passed to @ref AbstractImporter::light(). Type is usually + * @ref SceneFieldType::UnsignedInt, but can be also any of + * @relativeref{SceneFieldType,UnsignedByte} or + * @relativeref{SceneFieldType,UnsignedShort}. An object can have multiple + * lights associated. + * @see @ref SceneData::lightsAsArray() + */ + Light, + + /** + * ID of a camera associated with this object, corresponding to the ID + * passed to @ref AbstractImporter::camera(). Type is usually + * @ref SceneFieldType::UnsignedInt, but can be also any of + * @relativeref{SceneFieldType,UnsignedByte} or + * @relativeref{SceneFieldType,UnsignedShort}. An object can have multiple + * cameras associated. + * @see @ref SceneData::camerasAsArray() + */ + Camera, + + /** + * ID of a skin associated with this object, corresponding to the ID + * passed to @ref AbstractImporter::skin(). Type is usually + * @ref SceneFieldType::UnsignedInt, but can be also any of + * @relativeref{SceneFieldType,UnsignedByte} or + * @relativeref{SceneFieldType,UnsignedShort}. An object can have multiple + * skins associated. + * @see @ref SceneData::skinsAsArray() + */ + Skin, + + /** + * This and all higher values are for importer-specific fields. Can be + * of any type. See documentation of a particular importer for details. + * + * While it's unlikely to have billions of custom fields, the enum + * intentionally reserves a full 31-bit range to avoid the need to remap + * field identifiers coming from 3rd party ECS frameworks, for example. + * @see @ref isSceneFieldCustom(), @ref sceneFieldCustom(SceneField), + * @ref sceneFieldCustom(UnsignedInt) + */ + Custom = 0x80000000u +}; + +/** +@debugoperatorenum{SceneField} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneField value); + +/** +@brief Whether a scene field is custom +@m_since_latest + +Returns @cpp true @ce if @p name has a value larger or equal to +@ref SceneField::Custom, @cpp false @ce otherwise. +@see @ref sceneFieldCustom(UnsignedInt), @ref sceneFieldCustom(SceneField) +*/ +constexpr bool isSceneFieldCustom(SceneField name) { + return UnsignedInt(name) >= UnsignedInt(SceneField::Custom); +} + +/** +@brief Create a custom scene field +@m_since_latest + +Returns a custom scene field with index @p id. The index is expected to be less +than the value of @ref SceneField::Custom. Use @ref sceneFieldCustom(SceneField) +to get the index back. +*/ +/* Constexpr so it's usable for creating compile-time SceneFieldData + instances */ +constexpr SceneField sceneFieldCustom(UnsignedInt id) { + return CORRADE_CONSTEXPR_ASSERT(id < UnsignedInt(SceneField::Custom), + "Trade::sceneFieldCustom(): index" << id << "too large"), + SceneField(UnsignedInt(SceneField::Custom) + id); +} + +/** +@brief Get index of a custom scene field +@m_since_latest + +Inverse to @ref sceneFieldCustom(UnsignedInt). Expects that the field is +custom. +@see @ref isSceneFieldCustom() +*/ +constexpr UnsignedInt sceneFieldCustom(SceneField name) { + return CORRADE_CONSTEXPR_ASSERT(isSceneFieldCustom(name), + "Trade::sceneFieldCustom():" << name << "is not custom"), + UnsignedInt(name) - UnsignedInt(SceneField::Custom); +} + +/** +@brief Scene field type +@m_since_latest + +A type in which a @ref SceneField is stored. See @ref SceneData for more +information. +@see @ref SceneFieldData, @ref sceneFieldTypeSize() +*/ +enum class SceneFieldType: UnsignedShort { + /* Zero used for an invalid value */ + + /* 1 reserved for Bool (Bit?), which needs [Strided]BitArray[View] first */ + + Float = 2, /**< @relativeref{Magnum,Float} */ + Half, /**< @relativeref{Magnum,Half} */ + Double, /**< @relativeref{Magnum,Double} */ + UnsignedByte, /**< @relativeref{Magnum,UnsignedByte} */ + Byte, /**< @relativeref{Magnum,Byte} */ + UnsignedShort, /**< @relativeref{Magnum,UnsignedShort} */ + Short, /**< @relativeref{Magnum,Short} */ + UnsignedInt, /**< @relativeref{Magnum,UnsignedInt} */ + Int, /**< @relativeref{Magnum,Int} */ + UnsignedLong, /**< @relativeref{Magnum,UnsignedLong} */ + Long, /**< @relativeref{Magnum,Long} */ + + Vector2, /**< @relativeref{Magnum,Vector2} */ + Vector2h, /**< @relativeref{Magnum,Vector2h} */ + Vector2d, /**< @relativeref{Magnum,Vector2d} */ + Vector2ub, /**< @relativeref{Magnum,Vector2ub} */ + Vector2b, /**< @relativeref{Magnum,Vector2b} */ + Vector2us, /**< @relativeref{Magnum,Vector2us} */ + Vector2s, /**< @relativeref{Magnum,Vector2s} */ + Vector2ui, /**< @relativeref{Magnum,Vector2ui} */ + Vector2i, /**< @relativeref{Magnum,Vector2i} */ + + Vector3, /**< @relativeref{Magnum,Vector3} */ + Vector3h, /**< @relativeref{Magnum,Vector3h} */ + Vector3d, /**< @relativeref{Magnum,Vector3d} */ + Vector3ub, /**< @relativeref{Magnum,Vector3ub} */ + Vector3b, /**< @relativeref{Magnum,Vector3b} */ + Vector3us, /**< @relativeref{Magnum,Vector3us} */ + Vector3s, /**< @relativeref{Magnum,Vector3s} */ + Vector3ui, /**< @relativeref{Magnum,Vector3ui} */ + Vector3i, /**< @relativeref{Magnum,Vector3i} */ + + Vector4, /**< @relativeref{Magnum,Vector4} */ + Vector4h, /**< @relativeref{Magnum,Vector4h} */ + Vector4d, /**< @relativeref{Magnum,Vector4d} */ + Vector4ub, /**< @relativeref{Magnum,Vector4ub} */ + Vector4b, /**< @relativeref{Magnum,Vector4b} */ + Vector4us, /**< @relativeref{Magnum,Vector4us} */ + Vector4s, /**< @relativeref{Magnum,Vector4s} */ + Vector4ui, /**< @relativeref{Magnum,Vector4ui} */ + Vector4i, /**< @relativeref{Magnum,Vector4i} */ + + Matrix2x2, /**< @relativeref{Magnum,Matrix2x2} */ + Matrix2x2h, /**< @relativeref{Magnum,Matrix2x2h} */ + Matrix2x2d, /**< @relativeref{Magnum,Matrix2x2d} */ + + Matrix2x3, /**< @relativeref{Magnum,Matrix2x3} */ + Matrix2x3h, /**< @relativeref{Magnum,Matrix2x3h} */ + Matrix2x3d, /**< @relativeref{Magnum,Matrix2x3d} */ + + Matrix2x4, /**< @relativeref{Magnum,Matrix2x4} */ + Matrix2x4h, /**< @relativeref{Magnum,Matrix2x4h} */ + Matrix2x4d, /**< @relativeref{Magnum,Matrix2x4d} */ + + Matrix3x2, /**< @relativeref{Magnum,Matrix3x2} */ + Matrix3x2h, /**< @relativeref{Magnum,Matrix3x2h} */ + Matrix3x2d, /**< @relativeref{Magnum,Matrix3x2d} */ + + Matrix3x3, /**< @relativeref{Magnum,Matrix3x3} */ + Matrix3x3h, /**< @relativeref{Magnum,Matrix3x3h} */ + Matrix3x3d, /**< @relativeref{Magnum,Matrix3x3d} */ + + Matrix3x4, /**< @relativeref{Magnum,Matrix3x4} */ + Matrix3x4h, /**< @relativeref{Magnum,Matrix3x4h} */ + Matrix3x4d, /**< @relativeref{Magnum,Matrix3x4d} */ + + Matrix4x2, /**< @relativeref{Magnum,Matrix4x2} */ + Matrix4x2h, /**< @relativeref{Magnum,Matrix4x2h} */ + Matrix4x2d, /**< @relativeref{Magnum,Matrix4x2d} */ + + Matrix4x3, /**< @relativeref{Magnum,Matrix4x3} */ + Matrix4x3h, /**< @relativeref{Magnum,Matrix4x3h} */ + Matrix4x3d, /**< @relativeref{Magnum,Matrix4x3d} */ + + Matrix4x4, /**< @relativeref{Magnum,Matrix4x4} */ + Matrix4x4h, /**< @relativeref{Magnum,Matrix4x4h} */ + Matrix4x4d, /**< @relativeref{Magnum,Matrix4x4d} */ + + Range1D, /**< @relativeref{Magnum,Range1D} */ + Range1Dh, /**< @relativeref{Magnum,Range1Dh} */ + Range1Dd, /**< @relativeref{Magnum,Range1Dd} */ + Range1Di, /**< @relativeref{Magnum,Range1Di} */ + + Range2D, /**< @relativeref{Magnum,Range2D} */ + Range2Dh, /**< @relativeref{Magnum,Range2Dh} */ + Range2Dd, /**< @relativeref{Magnum,Range2Dd} */ + Range2Di, /**< @relativeref{Magnum,Range2Di} */ + + Range3D, /**< @relativeref{Magnum,Range3D} */ + Range3Dh, /**< @relativeref{Magnum,Range3Dh} */ + Range3Dd, /**< @relativeref{Magnum,Range3Dd} */ + Range3Di, /**< @relativeref{Magnum,Range3Di} */ + + Complex, /**< @relativeref{Magnum,Complex} */ + Complexd, /**< @relativeref{Magnum,Complexd} */ + DualComplex, /**< @relativeref{Magnum,DualComplex} */ + DualComplexd, /**< @relativeref{Magnum,DualComplexd} */ + + Quaternion, /**< @relativeref{Magnum,Quaternion} */ + Quaterniond, /**< @relativeref{Magnum,Quaterniond} */ + DualQuaternion, /**< @relativeref{Magnum,DualQuaternion} */ + DualQuaterniond,/**< @relativeref{Magnum,DualQuaterniond} */ + + Deg, /**< @relativeref{Magnum,Deg} */ + Degh, /**< @relativeref{Magnum,Degh} */ + Degd, /**< @relativeref{Magnum,Degh} */ + Rad, /**< @relativeref{Magnum,Rad} */ + Radh, /**< @relativeref{Magnum,Radh} */ + Radd /**< @relativeref{Magnum,Radd} */ +}; + +/** +@debugoperatorenum{SceneFieldType} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneFieldType value); + +/** +@brief Size of given scene field type +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT UnsignedInt sceneFieldTypeSize(SceneFieldType type); + +/** +@brief Scene field data +@m_since_latest + +Convenience type for populating @ref SceneData, see its documentation for an +introduction. +*/ +class MAGNUM_TRADE_EXPORT SceneFieldData { + public: + /** + * @brief Default constructor + * + * Leaves contents at unspecified values. Provided as a convenience for + * initialization of the field array for @ref SceneData, expected to be + * replaced with concrete values later. + */ + constexpr explicit SceneFieldData() noexcept: _size{}, _name{}, _isOffsetOnly{}, _objectType{}, _objectStride{}, _objectData{}, _fieldType{}, _fieldStride{}, _fieldArraySize{}, _fieldData{} {} + + /** + * @brief Type-erased constructor + * @param name Field name + * @param objectType Object type + * @param objectData Object data + * @param fieldType Field type + * @param fieldData Field data + * @param fieldArraySize Field array size. Use @cpp 0 @ce for + * non-array fields. + * + * Expects that @p objectData and @p fieldData have the same size, + * @p fieldType corresponds to @p name and @p fieldArraySize is zero + * for builtin fields. + */ + constexpr explicit SceneFieldData(SceneField name, SceneObjectType objectType, const Containers::StridedArrayView1D& objectData, SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, UnsignedShort fieldArraySize = 0) noexcept; + + /** + * @brief Constructor + * @param name Field name + * @param objectData Object data + * @param fieldType Field type + * @param fieldData Field data + * @param fieldArraySize Field array size. Use @cpp 0 @ce for + * non-array fields. + * + * Expects that @p objectData and @p fieldData have the same size in + * the first dimension, that the second dimension of @p objectData is + * contiguous and its size is either 1, 2, 4 or 8, corresponding to one + * of the @ref SceneObjectType values, that the second dimension of + * @p fieldData is contiguous and its size matches @p fieldType and + * @p fieldArraySize and that @p fieldType corresponds to @p name and + * @p fieldArraySize is zero for builtin attributes. + */ + explicit SceneFieldData(SceneField name, const Containers::StridedArrayView2D& objectData, SceneFieldType fieldType, const Containers::StridedArrayView2D& fieldData, UnsignedShort fieldArraySize = 0) noexcept; + + /** + * @brief Constructor + * @param name Field name + * @param objectData Object data + * @param fieldData Field data + * + * Detects @ref SceneObjectType based on @p T and @ref SceneFieldType + * based on @p U and calls @ref SceneFieldData(SceneField, SceneObjectType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort). + * For all types known by Magnum, the detected @ref SceneFieldType is + * of the same name as the type (so e.g. @relativeref{Magnum,Vector3ui} + * gets recognized as @ref SceneFieldType::Vector3ui). + */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& objectData, const Containers::StridedArrayView1D& fieldData) noexcept; + + /** @overload */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& objectData, const Containers::ArrayView& fieldData) noexcept: SceneFieldData{name, objectData, Containers::stridedArrayView(fieldData)} {} + + /** @overload */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& objectData, const Containers::StridedArrayView1D& fieldData) noexcept: SceneFieldData{name, Containers::stridedArrayView(objectData), fieldData} {} + + /** @overload */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& objectData, const Containers::ArrayView& fieldData) noexcept: SceneFieldData{name, Containers::stridedArrayView(objectData), Containers::stridedArrayView(fieldData)} {} + + /** + * @brief Construct an array field + * @param name Field name + * @param objectData Object data + * @param fieldData Field data + * + * Detects @ref SceneObjectType based on @p T and @ref SceneFieldType + * based on @p U and calls @ref SceneFieldData(SceneField, SceneObjectType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort) + * with the @p fieldData second dimension size passed to + * @p fieldArraySize. Expects that the second dimension of @p fieldData + * is contiguous. At the moment only custom fields can be arrays, which + * means this function can't be used with a builtin @p name. See + * @ref SceneFieldData(SceneField, const Containers::StridedArrayView1D&, const Containers::StridedArrayView1D&) + * for details about @ref SceneObjectType and @ref SceneFieldType + * detection. + */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::StridedArrayView1D& objectData, const Containers::StridedArrayView2D& fieldData) noexcept; + + /** @overload */ + template constexpr explicit SceneFieldData(SceneField name, const Containers::ArrayView& objectData, const Containers::StridedArrayView2D& fieldData) noexcept: SceneFieldData{name, Containers::stridedArrayView(objectData), fieldData} {} + + /** + * @brief Construct an offset-only field + * @param name Field name + * @param size Number of entries + * @param objectType Object type + * @param objectOffset Object data offset + * @param objectStride Object data stride + * @param fieldType Field type + * @param fieldOffset Field data offset + * @param fieldStride Field data stride + * @param fieldArraySize Field array size. Use @cpp 0 @ce for + * non-array fields. + * + * Instances created this way refer to offsets in unspecified + * external scene data instead of containing the data views directly. + * Useful when the location of the scene data array is not known at + * field construction time. Expects that @p fieldType corresponds to + * @p name and @p fieldArraySize is zero for builtin attributes. + * + * Note that due to the @cpp constexpr @ce nature of this constructor, + * no @p objectType checks against @p objectStride or + * @p fieldType / @p fieldArraySize checks against @p fieldStride can + * be done. You're encouraged to use the @ref SceneFieldData(SceneField, SceneObjectType, const Containers::StridedArrayView1D&, SceneFieldType, const Containers::StridedArrayView1D&, UnsignedShort) + * constructor if you want additional safeguards. + * @see @ref isOffsetOnly(), @ref fieldArraySize(), + * @ref objectData(Containers::ArrayView) const, + * @ref fieldData(Containers::ArrayView) const + */ + explicit constexpr SceneFieldData(SceneField name, std::size_t size, SceneObjectType objectType, std::size_t objectOffset, std::ptrdiff_t objectStride, SceneFieldType fieldType, std::size_t fieldOffset, std::ptrdiff_t fieldStride, UnsignedShort fieldArraySize = 0) noexcept; + + /** + * @brief If the field is offset-only + * + * Returns @cpp true @ce if the field doesn't contain the data views + * directly, but instead refers to unspecified external data. + * @see @ref objectData(Containers::ArrayView) const, + * @ref fieldData(Containers::ArrayView) const, + * @ref SceneFieldData(SceneField, std::size_t, SceneObjectType, std::size_t, std::ptrdiff_t, SceneFieldType, std::size_t, std::ptrdiff_t, UnsignedShort) + */ + constexpr bool isOffsetOnly() const { return _isOffsetOnly; } + + /** @brief Field name */ + constexpr SceneField name() const { return _name; } + + /** @brief Number of entries */ + constexpr UnsignedLong size() const { return _size; } + + /** @brief Object type */ + constexpr SceneObjectType objectType() const { return _objectType; } + + /** + * @brief Type-erased object data + * + * Expects that the field is not offset-only, in that case use the + * @ref objectData(Containers::ArrayView) const overload + * instead. + * @see @ref isOffsetOnly() + */ + constexpr Containers::StridedArrayView1D objectData() const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + {_objectData.pointer, ~std::size_t{}}, _size, + (CORRADE_CONSTEXPR_ASSERT(!_isOffsetOnly, "Trade::SceneFieldData::objectData(): the field is offset-only, supply a data array"), _objectStride)}; + } + + /** + * @brief Type-erased object data for an offset-only attribute + * + * If the field is not offset-only, the @p data parameter is ignored. + * @see @ref isOffsetOnly(), @ref objectData() const + */ + Containers::StridedArrayView1D objectData(Containers::ArrayView data) const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + data, _isOffsetOnly ? reinterpret_cast(data.data()) + _objectData.offset : _objectData.pointer, _size, _objectStride}; + } + + /** @brief Field type */ + constexpr SceneFieldType fieldType() const { return _fieldType; } + + /** @brief Field array size */ + constexpr UnsignedShort fieldArraySize() const { return _fieldArraySize; } + + /** + * @brief Type-erased field data + * + * Expects that the field is not offset-only, in that case use the + * @ref fieldData(Containers::ArrayView) const overload + * instead. + * @see @ref isOffsetOnly() + */ + constexpr Containers::StridedArrayView1D fieldData() const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + {_fieldData.pointer, ~std::size_t{}}, _size, + (CORRADE_CONSTEXPR_ASSERT(!_isOffsetOnly, "Trade::SceneFieldData::fieldData(): the field is offset-only, supply a data array"), _fieldStride)}; + } + + /** + * @brief Type-erased field data for an offset-only attribute + * + * If the field is not offset-only, the @p data parameter is ignored. + * @see @ref isOffsetOnly(), @ref fieldData() const + */ + Containers::StridedArrayView1D fieldData(Containers::ArrayView data) const { + return Containers::StridedArrayView1D{ + /* We're *sure* the view is correct, so faking the view size */ + /** @todo better ideas for the StridedArrayView API? */ + data, _isOffsetOnly ? reinterpret_cast(data.data()) + _fieldData.offset : _fieldData.pointer, _size, _fieldStride}; + } + + private: + friend SceneData; + + union Data { + /* FFS C++ why this doesn't JUST WORK goddamit?! It's already past + the End Of Times AND YET this piece of complex shit can't do the + obvious! */ + constexpr Data(const void* pointer = nullptr): pointer{pointer} {} + constexpr Data(std::size_t offset): offset{offset} {} + + const void* pointer; + std::size_t offset; + }; + + UnsignedLong _size; + SceneField _name; + bool _isOffsetOnly; + SceneObjectType _objectType; + Short _objectStride; + Data _objectData; + + SceneFieldType _fieldType; + Short _fieldStride; + UnsignedShort _fieldArraySize; + /* 2 bytes free */ + Data _fieldData; +}; + +/** @relatesalso SceneFieldData +@brief Create a non-owning array of @ref SceneFieldData items +@m_since_latest + +Useful when you have the field definitions statically defined (for example when +the data themselves are already defined at compile time) and don't want to +allocate just to pass those to @ref SceneData. +*/ +Containers::Array MAGNUM_TRADE_EXPORT sceneFieldDataNonOwningArray(Containers::ArrayView view); + /** @brief Scene data +Contains scene hierarchy, object transformations and association of mesh, +material, camera, light and other resources with particular objects. + @see @ref AbstractImporter::scene() */ class MAGNUM_TRADE_EXPORT SceneData { public: /** - * @brief Constructor - * @param children2D Two-dimensional child objects - * @param children3D Three-dimensional child objects - * @param importerState Importer-specific state + * @brief Construct scene data + * @param objectType Object type + * @param objectCount Total count of all objects in the scene + * @param data Data for all fields and objects + * @param fields Description of all scene field data + * @param importerState Importer-specific state + * @m_since_latest + * + * The @p objectType is expected to be large enough to index all + * @p objectCount objects. The @p fields are expected to reference + * (sparse) sub-ranges of @p data, each having an unique + * @ref SceneField and @ref SceneObjectType equal to @p objectType. + * Particular fields can have additional restrictions, see + * documentation of @ref SceneField values for more information. + * + * The @ref dataFlags() are implicitly set to a combination of + * @ref DataFlag::Owned and @ref DataFlag::Mutable. For non-owned data + * use the @ref SceneData(SceneObjectType, UnsignedLong, DataFlags, Containers::ArrayView, Containers::Array&&, const void*) + * constructor or its variants instead. */ - explicit SceneData(std::vector children2D, std::vector children3D, const void* importerState = nullptr); + explicit SceneData(SceneObjectType objectType, UnsignedLong objectCount, Containers::Array&& data, Containers::Array&& fields, const void* importerState = nullptr) noexcept; + + /** + * @overload + * @m_since_latest + */ + /* Not noexcept because allocation happens inside */ + explicit SceneData(SceneObjectType objectType, UnsignedLong objectCount, Containers::Array&& data, std::initializer_list fields, const void* importerState = nullptr); + + /** + * @brief Construct non-owned scene data + * @param objectType Object type + * @param objectCount Total count of all objects in the scene + * @param dataFlags Data flags + * @param data View on data for all fields and objects + * @param fields Description of all scene field data + * @param importerState Importer-specific state + * @m_since_latest + * + * Compared to @ref SceneData(SceneObjectType, UnsignedLong, Containers::Array&&, Containers::Array&&, const void*) + * creates an instance that doesn't own the passed data. The + * @p dataFlags parameter can contain @ref DataFlag::Mutable to + * indicate the external data can be modified, and is expected to *not* + * have @ref DataFlag::Owned set. + */ + explicit SceneData(SceneObjectType objectType, UnsignedLong objectCount, DataFlags dataFlags, Containers::ArrayView data, Containers::Array&& fields, const void* importerState = nullptr) noexcept; + + /** + * @overload + * @m_since_latest + */ + /* Not noexcept because allocation happens inside */ + explicit SceneData(SceneObjectType objectType, UnsignedLong objectCount, DataFlags dataFlags, Containers::ArrayView data, std::initializer_list fields, const void* importerState = nullptr); /** @brief Copying is not allowed */ SceneData(const SceneData&) = delete; /** @brief Move constructor */ - SceneData(SceneData&&) - /* GCC 4.9.0 (the one from Android NDK) thinks this does not match - the implicit signature so it can't be defaulted. Works on 4.8, - 5.0 and everywhere else, so I don't bother. */ - #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 - noexcept - #endif - ; + SceneData(SceneData&&) noexcept; + + ~SceneData(); /** @brief Copying is not allowed */ SceneData& operator=(const SceneData&) = delete; /** @brief Move assignment */ - SceneData& operator=(SceneData&&) - /* GCC 4.9.0 (the one from Android NDK) thinks this does not match - the implicit signature so it can't be defaulted. Works on 4.8, - 5.0 and everywhere else, so I don't bother. */ - #if !defined(__GNUC__) || __GNUC__*100 + __GNUC_MINOR__ != 409 - noexcept - #endif - ; + SceneData& operator=(SceneData&&) noexcept; + + /** + * @brief Data flags + * @m_since_latest + * + * @see @ref releaseData(), @ref mutableData(), @ref mutableField(), + * @ref mutableObjects() + */ + DataFlags dataFlags() const { return _dataFlags; } + + /** + * @brief Raw data + * @m_since_latest + * + * Returns @cpp nullptr @ce if the scene has no data. + */ + Containers::ArrayView data() const & { return _data; } + + /** + * @brief Taking a view to a r-value instance is not allowed + * @m_since_latest + */ + Containers::ArrayView data() const && = delete; + + /** + * @brief Mutable raw data + * @m_since_latest + * + * Like @ref data(), but returns a non-const view. Expects that the + * scene is mutable. + * @see @ref dataFlags() + */ + Containers::ArrayView mutableData() &; + + /** + * @brief Taking a view to a r-value instance is not allowed + * @m_since_latest + */ + Containers::ArrayView mutableData() && = delete; + + /** + * @brief Type used for object mapping + * @m_since_latest + * + * Type returned from @ref objects() and @ref mutableObjects(). It's + * the same for all fields and is guaranteed to be large enough to fit + * all @ref objectCount() objects. + */ + SceneObjectType objectType() const { return _objectType; } + + /** + * @brief Total object count + * @m_since_latest + * + * Total number of objects contained in the scene. + * @see @ref fieldCount(), @ref fieldSize() + */ + UnsignedLong objectCount() const { return _objectCount; } + + /** + * @brief Field count + * @m_since_latest + * + * Count of different fields contained in the scene, or @cpp 0 @ce for + * a scene with no fields. Each @ref SceneField can be present only + * once, however an object can have a certain field associated with it + * multiple times with different values (for example an object having + * multiple meshes). See also @ref objectCount() which returns count of + * actual objects. + */ + UnsignedInt fieldCount() const { return _fields.size(); } + + /** + * @brief Raw field metadata + * @m_since_latest + * + * Returns the raw data that are used as a base for all `field*()` + * accessors, or @cpp nullptr @ce if the scene has no fields. In most + * cases you don't want to access those directly, but rather use the + * @ref objects(), @ref field(), @ref fieldName(), @ref fieldType(), + * @ref fieldSize() and @ref fieldArraySize() accessors. Compared to + * those and to @ref fieldData(UnsignedInt) const, the + * @ref SceneFieldData instances returned by this function may have + * different data pointers, and some of them might be offset-only --- + * use this function only if you *really* know what are you doing. + * @see @ref SceneFieldData::isOffsetOnly() + */ + Containers::ArrayView fieldData() const & { return _fields; } + + /** + * @brief Taking a view to a r-value instance is not allowed + * @m_since_latest + */ + Containers::ArrayView fieldData() const && = delete; + + /** + * @brief Raw field data + * @m_since_latest + * + * Returns the raw data that are used as a base for all `field*()` + * accessors. In most cases you don't want to access those directly, + * but rather use the @ref objects(), @ref field(), @ref fieldName(), + * @ref fieldType(), @ref fieldSize() and @ref fieldArraySize() + * accessors. This is also the reason why there's no overload taking a + * @ref SceneField, unlike the other accessors. + * + * Unlike with @ref fieldData() and @ref releaseFieldData(), returned + * instances are guaranteed to always have an absolute data pointer + * (i.e., @ref SceneFieldData::isOffsetOnly() always returning + * @cpp false @ce). The @p id is expected to be smaller than + * @ref fieldCount(). + */ + SceneFieldData fieldData(UnsignedInt id) const; + + /** + * @brief Field name + * @m_since_latest + * + * The @p id is expected to be smaller than @ref fieldCount(). + * @see @ref fieldType(), @ref isSceneFieldCustom(), + * @ref AbstractImporter::sceneFieldForName(), + * @ref AbstractImporter::sceneFieldName() + */ + SceneField fieldName(UnsignedInt id) const; + + /** + * @brief Field type + * @m_since_latest + * + * The @p id is expected to be smaller than @ref fieldCount(). You can + * also use @ref fieldType(SceneField) const to directly get a type of + * given named field. + * @see @ref fieldName(), @ref objectType() + */ + SceneFieldType fieldType(UnsignedInt id) const; + + /** + * @brief Size of given field + * @m_since_latest + * + * Size of the view returned by @ref field() / @ref mutableField() and + * also @ref objects() / @ref mutableObjects() for given @p id. Since + * an object can have multiple entries of the same field (for example + * multiple meshes associated with an object), the size doesn't + * necessarily match the number of objects having given field. + * + * The @p id is expected to be smaller than @ref fieldCount(). You can + * also use @ref fieldSize(SceneField) const to directly get a size of + * given named field. + */ + std::size_t fieldSize(UnsignedInt id) const; + + /** + * @brief Field array size + * @m_since_latest + * + * In case given field is an array (the euqivalent of e.g. + * @cpp int[30] @ce), returns array size, otherwise returns @cpp 0 @ce. + * At the moment only custom fields can be arrays, no builtin + * @ref SceneField is an array attribute. Note that this is different + * from the count of entries for given field, which is exposed through + * @ref fieldSize(). See @ref Trade-SceneData-populating-custom for an + * example. + * + * The @p id is expected to be smaller than @ref fieldCount(). You can + * also use @ref fieldArraySize(SceneField) const to directly get a + * type of given named field. + */ + UnsignedShort fieldArraySize(UnsignedInt id) const; + + /** + * @brief Whether the scene has given field + * @m_since_latest + */ + bool hasField(SceneField name) const; + + /** + * @brief Absolute ID of a named field + * @m_since_latest + * + * The @p name is expected to exist. + * @see @ref hasField(), @ref fieldName(UnsignedInt) const + */ + UnsignedInt fieldId(SceneField name) const; + + /** + * @brief Type of a named field + * @m_since_latest + * + * The @p name is expected to exist. + * @see @ref hasField(), @ref fieldType(UnsignedInt) const + */ + SceneFieldType fieldType(SceneField name) const; + + /** + * @brief Number of entries for given named field + * @m_since_latest + * + * The @p name is expected to exist. + * @see @ref hasField(), @ref fieldSize(UnsignedInt) const + */ + std::size_t fieldSize(SceneField name) const; + + /** + * @brief Array size of a named field + * @m_since_latest + * + * The @p name is expected to exist. + * @see @ref hasField(), @ref fieldArraySize(UnsignedInt) const + */ + UnsignedShort fieldArraySize(SceneField name) const; + + /** + * @brief Object mapping data for given field + * @m_since_latest + * + * The @p fieldId is expected to be smaller than @ref fieldCount(). The + * second dimension represents the actual data type (its size is equal + * to @ref SceneObjectType size) and is guaranteed to be contiguous. + * Use the templated overload below to get the objects in a concrete + * type. + * @see @ref mutableObjects(UnsignedInt), + * @ref Corrade::Containers::StridedArrayView::isContiguous(), + * @ref sceneObjectTypeSize() + */ + Containers::StridedArrayView2D objects(UnsignedInt fieldId) const; + + /** + * @brief Mutable object mapping data for given field + * @m_since_latest + * + * Like @ref objects(UnsignedInt) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + Containers::StridedArrayView2D mutableObjects(UnsignedInt fieldId); + + /** + * @brief Object mapping for given field + * @m_since_latest + * + * The @p fieldId is expected to be smaller than @ref fieldCount() and + * @p T is expected to correspond to @ref objectType(). You can also + * use the non-templated @ref objectsAsArray() accessor to get the + * object mapping converted to the usual type, but note that such + * operation involves extra allocation and data conversion. + * @see @ref mutableObjects(UnsignedInt) + */ + template Containers::StridedArrayView1D objects(UnsignedInt fieldId) const; + + /** + * @brief Mutable object mapping for given field + * @m_since_latest + * + * Like @ref objects(UnsignedInt) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + template Containers::StridedArrayView1D mutableObjects(UnsignedInt fieldId); + + /** + * @brief Object mapping data for given named field + * @m_since_latest + * + * The @p fieldName is expected to exist. The second dimension + * represents the actual data type (its size is equal to + * @ref SceneObjectType size) and is guaranteed to be contiguous. Use + * the templated overload below to get the objects in a concrete type. + * @see @ref hasField(), @ref objects(UnsignedInt) const, + * @ref mutableObjects(SceneField), + * @ref Corrade::Containers::StridedArrayView::isContiguous() + */ + Containers::StridedArrayView2D objects(SceneField fieldName) const; + + /** + * @brief Mutable object mapping data for given named field + * @m_since_latest + * + * Like @ref objects(SceneField) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + Containers::StridedArrayView2D mutableObjects(SceneField fieldName); + + /** + * @brief Object mapping for given named field + * @m_since_latest + * + * The @p fieldName is expected to exist and @p T is expected to + * correspond to @ref objectType(). You can also use the non-templated + * @ref objectsAsArray() accessor to get the object mapping converted + * to the usual type, but note that such operation involves extra + * allocation and data conversion. + * @see @ref hasField(), @ref objects(UnsignedInt) const, + * @ref mutableObjects(UnsignedInt) + */ + template Containers::StridedArrayView1D objects(SceneField fieldName) const; + + /** + * @brief Mutable object mapping for given named field + * @m_since_latest + * + * Like @ref objects(SceneField) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + template Containers::StridedArrayView1D mutableObjects(SceneField fieldName); + + /** + * @brief Data for given field + * @m_since_latest + * + * The @p id is expected to be smaller than @ref fieldCount(). The + * second dimension represents the actual data type (its size is equal + * to @ref SceneFieldType size, possibly multiplied by array size) and + * is guaranteed to be contiguous. Use the templated overload below to + * get the field in a concrete type. + * @see @ref Corrade::Containers::StridedArrayView::isContiguous(), + * @ref sceneFieldTypeSize(), @ref mutableField(UnsignedInt) + */ + Containers::StridedArrayView2D field(UnsignedInt id) const; + + /** + * @brief Mutable data for given field + * @m_since_latest + * + * Like @ref field(UnsignedInt) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + Containers::StridedArrayView2D mutableField(UnsignedInt id); + + /** + * @brief Data for given field in a concrete type + * @m_since_latest + * + * The @p id is expected to be smaller than @ref fieldCount() and @p T + * is expected to correspond to @ref fieldType(UnsignedInt) const. The + * field is also expected to not be an array, in that case you need to + * use the overload below by using @cpp T[] @ce instead of @cpp T @ce. + * You can also use the non-templated @ref parentsAsArray(), + * @ref transformations2DAsArray(), @ref transformations3DAsArray(), + * @ref meshesAsArray(), @ref meshMaterialsAsArray(), + * @ref lightsAsArray(), @ref camerasAsArray(), @ref skinsAsArray() + * accessors to get common fields converted to usual types, but note + * that these operations involve extra allocation and data conversion. + * @see @ref field(SceneField) const, @ref mutableField(UnsignedInt), + * @ref fieldArraySize() + */ + template::value>::type> Containers::StridedArrayView1D field(UnsignedInt id) const; + + /** + * @brief Data for given array field in a concrete type + * @m_since_latest + * + * Same as above, except that it works with array fields instead --- + * you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref fieldArraySize(UnsignedInt) const for given field. + */ + template::value>::type> Containers::StridedArrayView2D::type> field(UnsignedInt id) const; + + /** + * @brief Mutable data for given field in a concrete type + * @m_since_latest + * + * Like @ref field(UnsignedInt) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + template::value>::type> Containers::StridedArrayView1D mutableField(UnsignedInt id); + + /** + * @brief Mutable data for given array field in a concrete type + * @m_since_latest + * + * Same as above, except that it works with array fields instead --- + * you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref fieldArraySize(UnsignedInt) const for given field. + */ + template::value>::type> Containers::StridedArrayView2D::type> mutableField(UnsignedInt id); + + /** + * @brief Data for given named field + * @m_since_latest + * + * The @p name is expected to exist. The second dimension represents + * the actual data type (its size is equal to @ref SceneFieldType size, + * possibly multiplied by array size) and is guaranteed to be + * contiguous. Use the templated overload below to get the field in a + * concrete type. + * @see @ref hasField(), @ref field(UnsignedInt) const, + * @ref mutableField(SceneField), + * @ref Corrade::Containers::StridedArrayView::isContiguous() + */ + Containers::StridedArrayView2D field(SceneField name) const; + + /** + * @brief Mutable data for given named field + * @m_since_latest + * + * Like @ref field(SceneField) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + Containers::StridedArrayView2D mutableField(SceneField name); + + /** + * @brief Data for given named field in a concrete type + * @m_since_latest + * + * The @p name is expected to exist and @p T is expected to correspond + * to @ref fieldType(SceneField) const. The field is also expected to + * not be an array, in that case you need to use the overload below by + * using @cpp T[] @ce instead of @cpp T @ce. You can also use the + * non-templated @ref parentsAsArray(), @ref transformations2DAsArray(), + * @ref transformations3DAsArray(), @ref meshesAsArray(), + * @ref meshMaterialsAsArray(), @ref lightsAsArray(), + * @ref camerasAsArray(), @ref skinsAsArray() accessors to get common + * fields converted to usual types, but note that these operations + * involve extra allocation and data conversion. + * @see @ref field(UnsignedInt) const, @ref mutableField(SceneField) + */ + template::value>::type> Containers::StridedArrayView1D field(SceneField name) const; + + /** + * @brief Data for given named array field in a concrete type + * @m_since_latest + * + * Same as above, except that it works with array fields instead --- + * you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref fieldArraySize(SceneField) const for given field. + */ + template::value>::type> Containers::StridedArrayView2D::type> field(SceneField name) const; + + /** + * @brief Mutable data for given named field + * @m_since_latest + * + * Like @ref field(SceneField) const, but returns a mutable view. + * Expects that the scene is mutable. + * @see @ref dataFlags() + */ + template::value>::type> Containers::StridedArrayView1D mutableField(SceneField name); + + /** + * @brief Mutable data for given named array field in a concrete type + * @m_since_latest + * + * Same as above, except that it works with array fields instead --- + * you're expected to select this overload by passing @cpp T[] @ce + * instead of @cpp T @ce. The second dimension is guaranteed to be + * contiguous and have the same size as reported by + * @ref fieldArraySize(SceneField) const for given field. + */ + template::value>::type> Containers::StridedArrayView2D::type> mutableField(SceneField name); + + /** + * @brief Object mapping for given field as 32-bit integers + * @m_since_latest + * + * Convenience alternative to the templated + * @ref objects(UnsignedInt) const that converts the field from an + * arbitrary underlying type and returns it in a newly-allocated array. + * The @p fieldId is expected to be smaller than @ref fieldCount(). + * @attention In the rare case when @ref objectType() is + * @ref SceneObjectType::UnsignedLong and @ref objectCount() is + * larger than the max representable 32-bit value, this function + * can't be used, only an appropriately typed + * @ref objects(UnsignedInt) const. + * @see @ref objectsInto(UnsignedInt, const Containers::StridedArrayView1D&) const + */ + Containers::Array objectsAsArray(UnsignedInt fieldId) const; + + /** + * @brief Object mapping for given field as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref objectsAsArray(UnsignedInt) const, but puts the result + * into @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref fieldSize(UnsignedInt) const + */ + void objectsInto(UnsignedInt fieldId, const Containers::StridedArrayView1D& destination) const; + + /** + * @brief Object mapping for given named field as 32-bit integers + * @m_since_latest + * + * Convenience alternative to the templated + * @ref objects(SceneField) const that converts the field from an + * arbitrary underlying type and returns it in a newly-allocated array. + * The @p fieldName is expected to exist. + * @attention In the rare case when @ref objectType() is + * @ref SceneObjectType::UnsignedLong and @ref objectCount() is + * larger than the max representable 32-bit value, this function + * can't be used, only an appropriately typed + * @ref objects(SceneField) const. + * @see @ref objectsInto(SceneField, const Containers::StridedArrayView1D&) const, + * @ref hasField() + */ + Containers::Array objectsAsArray(SceneField fieldName) const; + + /** + * @brief Object mapping for given named field as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref objectsAsArray(SceneField) const, but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref fieldSize(SceneField) const + */ + void objectsInto(SceneField fieldName, const Containers::StridedArrayView1D& destination) const; + + /** + * @brief Parent indices as 32-bit integers + * @m_since_latest + * + * Convenience alternative to @ref field(SceneField) const with + * @ref SceneField::Parent as the argument that converts the field from + * an arbitrary underlying type and returns it in a newly-allocated + * array. The field is expected to exist. + * @attention In the rare case when @ref fieldType(SceneField) const is + * @ref SceneFieldType::Long and @ref fieldSize(SceneField) const + * is larger than the max representable 32-bit value, this + * function can't be used, only an appropriately typed + * @ref field(SceneField) const. + * @see @ref parentsInto(), @ref hasField() + */ + Containers::Array parentsAsArray() const; - /** @brief Two-dimensional child objects */ - const std::vector& children2D() const { return _children2D; } + /** + * @brief Parent indices as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref parentsAsArray(), but puts the result into @p destination + * instead of allocating a new array. Expects that @p destination is + * sized to contain exactly all data. + * @see @ref fieldSize(SceneField) const + */ + void parentsInto(const Containers::StridedArrayView1D& destination) const; + + /** + * @brief 2D transformations as 3x3 float matrices + * @m_since_latest + * + * Convenience alternative to @ref field(SceneField) const with + * @ref SceneField::Transformation as the argument, or, if not present, + * to a matrix created out of a subset of the + * @ref SceneField::Translation, @ref SceneField::Rotation and + * @ref SceneField::Scaling fields that's present. The transformation + * is converted and composed from an arbitrary underlying type and + * returned in a newly-allocated array. At least one of the fields is + * expected to exist and they are expected to have a type corresponding + * to 2D, otherwise you're supposed to use + * @ref transformations3DAsArray(). + * @see @ref transformations2DInto(), @ref hasField(), + * @ref fieldType(SceneField) const + */ + Containers::Array transformations2DAsArray() const; - /** @brief Three-dimensional child objects */ - const std::vector& children3D() const { return _children3D; } + /** + * @brief 2D transformations as 3x3 float matrices into a pre-allocated view + * @m_since_latest + * + * Like @ref transformations2DAsArray(), but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref fieldSize(SceneField) const + */ + void transformations2DInto(const Containers::StridedArrayView1D& destination) const; + + /** + * @brief 3D transformations as 4x4 float matrices + * @m_since_latest + * + * Convenience alternative to @ref field(SceneField) const with + * @ref SceneField::Transformation as the argument, or, if not present, + * to a matrix created out of a subset of the + * @ref SceneField::Translation, @ref SceneField::Rotation and + * @ref SceneField::Scaling fields that's present. The transformation + * is converted and composed from an arbitrary underlying type and + * returned in a newly-allocated array. At least one of the fields is + * expected to exist and they are expected to have a type corresponding + * to 3D, otherwise you're supposed to use + * @ref transformations2DAsArray(). + * @see @ref transformations3DInto(), @ref hasField(), + * @ref fieldType(SceneField) const + */ + Containers::Array transformations3DAsArray() const; + + /** + * @brief 3D transformations as 4x4 float matrices into a pre-allocated view + * @m_since_latest + * + * Like @ref transformations3DAsArray(), but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref fieldSize(SceneField) const + */ + void transformations3DInto(const Containers::StridedArrayView1D& destination) const; + + /** + * @brief Mesh IDs as 32-bit integers + * @m_since_latest + * + * Convenience alternative to @ref field(SceneField) const with + * @ref SceneField::Mesh as the argument that converts the field from + * an arbitrary underlying type and returns it in a newly-allocated + * array. The field is expected to exist. + * @see @ref meshesInto(), @ref hasField() + */ + Containers::Array meshesAsArray() const; + + /** + * @brief Mesh IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref meshesAsArray(), but puts the result into @p destination + * instead of allocating a new array. Expects that @p destination is + * sized to contain exactly all data. + * @see @ref fieldSize(SceneField) const + */ + void meshesInto(const Containers::StridedArrayView1D& destination) const; + + /** + * @brief Mesh material IDs as 32-bit integers + * @m_since_latest + * + * Convenience alternative to @ref field(SceneField) const with + * @ref SceneField::MeshMaterial as the argument that converts the + * field from an arbitrary underlying type and returns it in a + * newly-allocated array. The field is expected to exist. + * @see @ref meshMaterialsInto(), @ref hasField() + */ + Containers::Array meshMaterialsAsArray() const; + + /** + * @brief Mesh material IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref meshMaterialsAsArray(), but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref fieldSize(SceneField) const + */ + void meshMaterialsInto(const Containers::StridedArrayView1D& destination) const; + + /** + * @brief Light IDs as 32-bit integers + * @m_since_latest + * + * Convenience alternative to @ref field(SceneField) const with + * @ref SceneField::Light as the argument that converts the field from + * an arbitrary underlying type and returns it in a newly-allocated + * array. The field is expected to exist. + * @see @ref lightsInto(), @ref hasField() + */ + Containers::Array lightsAsArray() const; + + /** + * @brief Light IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref lightsAsArray(), but puts the result into @p destination + * instead of allocating a new array. Expects that @p destination is + * sized to contain exactly all data. + * @see @ref fieldSize(SceneField) const + */ + void lightsInto(const Containers::StridedArrayView1D& destination) const; + + /** + * @brief Camera IDs as 32-bit integers + * @m_since_latest + * + * Convenience alternative to @ref field(SceneField) const with + * @ref SceneField::Camera as the argument that converts the field from + * an arbitrary underlying type and returns it in a newly-allocated + * array. The field is expected to exist. + * @see @ref camerasInto(), @ref hasField() + */ + Containers::Array camerasAsArray() const; + + /** + * @brief Camera IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref camerasAsArray(), but puts the result into + * @p destination instead of allocating a new array. Expects that + * @p destination is sized to contain exactly all data. + * @see @ref fieldSize(SceneField) const + */ + void camerasInto(const Containers::StridedArrayView1D& destination) const; + + /** + * @brief Skin IDs as 32-bit integers + * @m_since_latest + * + * Convenience alternative to @ref field(SceneField) const with + * @ref SceneField::Skin as the argument that converts the field from + * an arbitrary underlying type and returns it in a newly-allocated + * array. The field is expected to exist. + * @see @ref skinsInto(), @ref hasField() + */ + Containers::Array skinsAsArray() const; + + /** + * @brief Skin IDs as 32-bit integers into a pre-allocated view + * @m_since_latest + * + * Like @ref skinsAsArray(), but puts the result into @p destination + * instead of allocating a new array. Expects that @p destination is + * sized to contain exactly all data. + * @see @ref fieldSize(SceneField) const + */ + void skinsInto(const Containers::StridedArrayView1D& destination) const; + + /** + * @brief Release field data storage + * @m_since_latest + * + * Releases the ownership of the field data array and resets internal + * field-related state to default. The scene then behaves like if it + * has no fields (but it can still have non-empty data). Note that the + * returned array has a custom no-op deleter when the data are not + * owned by the scene, and while the returned array type is mutable, + * the actual memory might be not. Additionally, the returned + * @ref SceneFieldData instances may have different data pointers and + * sizes than what's returned by the @ref field() and + * @ref fieldData(UnsignedInt) const accessors as some of them might be + * offset-only --- use this function only if you *really* know what are + * you doing. + * @see @ref fieldData(), @ref SceneFieldData::isOffsetOnly() + */ + Containers::Array releaseFieldData(); + + /** + * @brief Release data storage + * @m_since_latest + * + * Releases the ownership of the data array and resets internal + * field-related state to default. The scene then behaves like it has + * no fields and no data. If you want to release field data as well, + * first call @ref releaseFieldData() and then this function. + * + * Note that the returned array has a custom no-op deleter when the + * data are not owned by the scene, and while the returned array type + * is mutable, the actual memory might be not. + * @see @ref data(), @ref dataFlags() + */ + Containers::Array releaseData(); /** * @brief Importer-specific state @@ -91,11 +1537,397 @@ class MAGNUM_TRADE_EXPORT SceneData { const void* importerState() const { return _importerState; } private: - std::vector _children2D, - _children3D; + /* Internal helper that doesn't assert, unlike fieldId() */ + UnsignedInt fieldFor(SceneField name) const; + + /* Like objects() / field(), but returning just a 1D view */ + MAGNUM_TRADE_LOCAL Containers::StridedArrayView1D fieldDataObjectViewInternal(const SceneFieldData& field) const; + MAGNUM_TRADE_LOCAL Containers::StridedArrayView1D fieldDataFieldViewInternal(const SceneFieldData& field) const; + + #ifndef CORRADE_NO_ASSERT + template bool checkFieldTypeCompatibility(const SceneFieldData& attribute, const char* prefix) const; + #endif + + MAGNUM_TRADE_LOCAL std::size_t findTransformFields(UnsignedInt& transformationFieldId, UnsignedInt& translationFieldId, UnsignedInt& rotationFieldId, UnsignedInt& scalingFieldId) const; + MAGNUM_TRADE_LOCAL void transformations2DIntoInternal(UnsignedInt transformationFieldId, UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL void transformations3DIntoInternal(UnsignedInt transformationFieldId, UnsignedInt translationFieldId, UnsignedInt rotationFieldId, UnsignedInt scalingFieldId, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL void indexFieldIntoInternal( + #ifndef CORRADE_NO_ASSERT + const char* const prefix, + #endif + const SceneField name, const Containers::StridedArrayView1D& destination) const; + MAGNUM_TRADE_LOCAL Containers::Array indexFieldAsArrayInternal( + #ifndef CORRADE_NO_ASSERT + const char* const prefix, + #endif + const SceneField name) const; + + DataFlags _dataFlags; + SceneObjectType _objectType; + /* 2/6 bytes free */ + UnsignedLong _objectCount; const void* _importerState; + Containers::Array _fields; + Containers::Array _data; }; +namespace Implementation { + /* Making this a struct because there can't be partial specializations for + a function (which we may need for pointers, matrix/vector subclasses + etc) */ + template struct SceneFieldTypeFor { + static_assert(sizeof(T) == 0, "unsupported field type"); + }; + #ifndef DOXYGEN_GENERATING_OUTPUT + #define _c(type_) template<> struct SceneFieldTypeFor { \ + constexpr static SceneFieldType type() { \ + return SceneFieldType::type_; \ + } \ + }; + /* Bool needs a special type */ + _c(Float) + _c(Half) + _c(Double) + _c(UnsignedByte) + _c(Byte) + _c(UnsignedShort) + _c(Short) + _c(UnsignedInt) + _c(Int) + _c(UnsignedLong) + _c(Long) + _c(Vector2) + _c(Vector2h) + _c(Vector2d) + _c(Vector2ub) + _c(Vector2b) + _c(Vector2us) + _c(Vector2s) + _c(Vector2ui) + _c(Vector2i) + _c(Vector3) + _c(Vector3h) + _c(Vector3d) + _c(Vector3ub) + _c(Vector3b) + _c(Vector3us) + _c(Vector3s) + _c(Vector3ui) + _c(Vector3i) + _c(Vector4) + _c(Vector4h) + _c(Vector4d) + _c(Vector4ub) + _c(Vector4b) + _c(Vector4us) + _c(Vector4s) + _c(Vector4ui) + _c(Vector4i) + _c(Matrix2x2) + _c(Matrix2x2h) + _c(Matrix2x2d) + _c(Matrix2x3) + _c(Matrix2x3h) + _c(Matrix2x3d) + _c(Matrix2x4) + _c(Matrix2x4h) + _c(Matrix2x4d) + _c(Matrix3x2) + _c(Matrix3x2h) + _c(Matrix3x2d) + _c(Matrix3x3) + _c(Matrix3x3h) + _c(Matrix3x3d) + _c(Matrix3x4) + _c(Matrix3x4h) + _c(Matrix3x4d) + _c(Matrix4x2) + _c(Matrix4x2h) + _c(Matrix4x2d) + _c(Matrix4x3) + _c(Matrix4x3h) + _c(Matrix4x3d) + _c(Matrix4x4) + _c(Matrix4x4h) + _c(Matrix4x4d) + _c(Range1D) + _c(Range1Dh) + _c(Range1Dd) + _c(Range1Di) + _c(Range2D) + _c(Range2Dh) + _c(Range2Dd) + _c(Range2Di) + _c(Range3D) + _c(Range3Dh) + _c(Range3Dd) + _c(Range3Di) + _c(Complex) + _c(Complexd) + _c(DualComplex) + _c(DualComplexd) + _c(Quaternion) + _c(Quaterniond) + _c(DualQuaternion) + _c(DualQuaterniond) + _c(Deg) + _c(Degh) + _c(Degd) + _c(Rad) + _c(Radh) + _c(Radd) + #undef _c + #endif + /** @todo this doesn't handle RectangleMatrix and Vector at the moment, do we need those? */ + template struct SceneFieldTypeFor>: SceneFieldTypeFor> {}; + template struct SceneFieldTypeFor>: SceneFieldTypeFor> {}; + template struct SceneFieldTypeFor>: SceneFieldTypeFor> {}; + template struct SceneFieldTypeFor>: SceneFieldTypeFor> {}; + + template constexpr SceneObjectType sceneObjectTypeFor() { + static_assert(sizeof(T) == 0, "unsupported object type"); + return {}; + } + #ifndef DOXYGEN_GENERATING_OUTPUT + #define _c(type) \ + template<> constexpr SceneObjectType sceneObjectTypeFor() { return SceneObjectType::type; } + _c(UnsignedByte) + _c(UnsignedShort) + _c(UnsignedInt) + _c(UnsignedLong) + #undef _c + #endif + + constexpr bool isSceneFieldTypeCompatibleWithField(SceneField name, SceneFieldType type) { + return + /* Named fields are restricted so we can decode them */ + (name == SceneField::Parent && + (type == SceneFieldType::Byte || + type == SceneFieldType::Short || + type == SceneFieldType::Int || + type == SceneFieldType::Long)) || + (name == SceneField::Transformation && + (type == SceneFieldType::Matrix3x3 || + type == SceneFieldType::Matrix3x3d || + type == SceneFieldType::Matrix4x4 || + type == SceneFieldType::Matrix4x4d || + type == SceneFieldType::DualComplex || + type == SceneFieldType::DualComplexd || + type == SceneFieldType::DualQuaternion || + type == SceneFieldType::DualQuaterniond)) || + ((name == SceneField::Translation || + name == SceneField::Scaling) && + (type == SceneFieldType::Vector2 || + type == SceneFieldType::Vector2d || + type == SceneFieldType::Vector3 || + type == SceneFieldType::Vector3d)) || + (name == SceneField::Rotation && + (type == SceneFieldType::Complex || + type == SceneFieldType::Complexd || + type == SceneFieldType::Quaternion || + type == SceneFieldType::Quaterniond)) || + ((name == SceneField::Mesh || + name == SceneField::MeshMaterial || + name == SceneField::Light || + name == SceneField::Camera || + name == SceneField::Skin) && + (type == SceneFieldType::UnsignedByte || + type == SceneFieldType::UnsignedShort || + type == SceneFieldType::UnsignedInt)) || + /* Custom fields can be anything */ + isSceneFieldCustom(name); + } + + constexpr bool isSceneFieldArrayAllowed(SceneField name) { + return isSceneFieldCustom(name); + } +} + +constexpr SceneFieldData::SceneFieldData(const SceneField name, const SceneObjectType objectType, const Containers::StridedArrayView1D& objectData, const SceneFieldType fieldType, const Containers::StridedArrayView1D& fieldData, const UnsignedShort fieldArraySize) noexcept: + _size{(CORRADE_CONSTEXPR_ASSERT(objectData.size() == fieldData.size(), + "Trade::SceneFieldData: expected object and field view to have the same size but got" << objectData.size() << "and" << fieldData.size()), objectData.size())}, + _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeCompatibleWithField(name, fieldType), + "Trade::SceneFieldData:" << fieldType << "is not a valid type for" << name), name)}, + _isOffsetOnly{false}, + _objectType{objectType}, + _objectStride{(CORRADE_CONSTEXPR_ASSERT(objectData.stride() >= -32768 && objectData.stride() <= 32767, + "Trade::SceneFieldData: expected object view stride to fit into 16 bits, but got" << objectData.stride()), Short(objectData.stride()))}, + _objectData{objectData.data()}, + _fieldType{fieldType}, + _fieldStride{(CORRADE_CONSTEXPR_ASSERT(fieldData.stride() >= -32768 && fieldData.stride() <= 32767, + "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got" << fieldData.stride()), Short(fieldData.stride()))}, + _fieldArraySize{(CORRADE_CONSTEXPR_ASSERT(!fieldArraySize || Implementation::isSceneFieldArrayAllowed(name), + "Trade::SceneFieldData:" << name << "can't be an array field"), fieldArraySize)}, + _fieldData{fieldData.data()} {} + +template constexpr SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView1D& objectData, const Containers::StridedArrayView1D& fieldData) noexcept: SceneFieldData{name, Implementation::sceneObjectTypeFor::type>(), objectData, Implementation::SceneFieldTypeFor::type>::type(), fieldData, 0} {} + +template constexpr SceneFieldData::SceneFieldData(const SceneField name, const Containers::StridedArrayView1D& objectData, const Containers::StridedArrayView2D& fieldData) noexcept: SceneFieldData{ + name, + Implementation::sceneObjectTypeFor::type>(), + objectData, + Implementation::SceneFieldTypeFor::type>::type(), + Containers::StridedArrayView1D{{fieldData.data(), ~std::size_t{}}, fieldData.size()[0], fieldData.stride()[0]}, + /* Not using isContiguous<1>() as that's not constexpr */ + (CORRADE_CONSTEXPR_ASSERT(fieldData.stride()[1] == sizeof(U), "Trade::SceneFieldData: second field view dimension is not contiguous"), UnsignedShort(fieldData.size()[1])) +} {} + +constexpr SceneFieldData::SceneFieldData(const SceneField name, const std::size_t size, const SceneObjectType objectType, const std::size_t objectOffset, const std::ptrdiff_t objectStride, const SceneFieldType fieldType, const std::size_t fieldOffset, const std::ptrdiff_t fieldStride, const UnsignedShort fieldArraySize) noexcept: + _size{size}, + _name{(CORRADE_CONSTEXPR_ASSERT(Implementation::isSceneFieldTypeCompatibleWithField(name, fieldType), + "Trade::SceneFieldData:" << fieldType << "is not a valid type for" << name), name)}, + _isOffsetOnly{true}, + _objectType{objectType}, + _objectStride{(CORRADE_CONSTEXPR_ASSERT(objectStride >= -32768 && objectStride <= 32767, + "Trade::SceneFieldData: expected object view stride to fit into 16 bits, but got" << objectStride), Short(objectStride))}, + _objectData{objectOffset}, + _fieldType{fieldType}, + _fieldStride{(CORRADE_CONSTEXPR_ASSERT(fieldStride >= -32768 && fieldStride <= 32767, + "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got" << fieldStride), Short(fieldStride))}, + _fieldArraySize{(CORRADE_CONSTEXPR_ASSERT(!fieldArraySize || Implementation::isSceneFieldArrayAllowed(name), + "Trade::SceneFieldData:" << name << "can't be an array field"), fieldArraySize)}, + _fieldData{fieldOffset} {} + +template Containers::StridedArrayView1D SceneData::objects(const UnsignedInt fieldId) const { + Containers::StridedArrayView2D data = objects(fieldId); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + CORRADE_ASSERT(Implementation::sceneObjectTypeFor() == _objectType, + "Trade::SceneData::objects(): objects are" << _objectType << "but requested" << Implementation::sceneObjectTypeFor(), {}); + return Containers::arrayCast<1, const T>(data); +} + +template Containers::StridedArrayView1D SceneData::mutableObjects(const UnsignedInt fieldId) { + Containers::StridedArrayView2D data = mutableObjects(fieldId); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + CORRADE_ASSERT(Implementation::sceneObjectTypeFor() == _objectType, + "Trade::SceneData::mutableObjects(): objects are" << _objectType << "but requested" << Implementation::sceneObjectTypeFor(), {}); + return Containers::arrayCast<1, T>(data); +} + +template Containers::StridedArrayView1D SceneData::objects(const SceneField fieldName) const { + Containers::StridedArrayView2D data = objects(fieldName); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + CORRADE_ASSERT(Implementation::sceneObjectTypeFor() == _objectType, + "Trade::SceneData::objects(): objects are" << _objectType << "but requested" << Implementation::sceneObjectTypeFor(), {}); + return Containers::arrayCast<1, const T>(data); +} + +template Containers::StridedArrayView1D SceneData::mutableObjects(const SceneField fieldName) { + Containers::StridedArrayView2D data = mutableObjects(fieldName); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + CORRADE_ASSERT(Implementation::sceneObjectTypeFor() == _objectType, + "Trade::SceneData::mutableObjects(): objects are" << _objectType << "but requested" << Implementation::sceneObjectTypeFor(), {}); + return Containers::arrayCast<1, T>(data); +} + +#ifndef CORRADE_NO_ASSERT +template bool SceneData::checkFieldTypeCompatibility(const SceneFieldData& field, const char* const prefix) const { + CORRADE_ASSERT(Implementation::SceneFieldTypeFor::type>::type() == field._fieldType, + prefix << field._name << "is" << field._fieldType << "but requested a type equivalent to" << Implementation::SceneFieldTypeFor::type>::type(), false); + if(field._fieldArraySize) CORRADE_ASSERT(std::is_array::value, + prefix << field._name << "is an array field, use T[] to access it", false); + else CORRADE_ASSERT(!std::is_array::value, + prefix << field._name << "is not an array field, can't use T[] to access it", false); + return true; +} +#endif + +template Containers::StridedArrayView1D SceneData::field(const UnsignedInt id) const { + Containers::StridedArrayView2D data = field(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[id], "Trade::SceneData::field():")) return {}; + #endif + return Containers::arrayCast<1, const T>(data); +} + +template Containers::StridedArrayView2D::type> SceneData::field(const UnsignedInt id) const { + Containers::StridedArrayView2D data = field(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[id], "Trade::SceneData::field():")) return {}; + #endif + return Containers::arrayCast<2, const typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D SceneData::mutableField(const UnsignedInt id) { + Containers::StridedArrayView2D data = mutableField(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[id], "Trade::SceneData::mutableField():")) return {}; + #endif + return Containers::arrayCast<1, T>(data); +} + +template Containers::StridedArrayView2D::type> SceneData::mutableField(const UnsignedInt id) { + Containers::StridedArrayView2D data = mutableField(id); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[id], "Trade::SceneData::mutableField():")) return {}; + #endif + return Containers::arrayCast<2, typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D SceneData::field(const SceneField name) const { + Containers::StridedArrayView2D data = field(name); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[fieldFor(name)], "Trade::SceneData::field():")) return {}; + #endif + return Containers::arrayCast<1, const T>(data); +} + +template Containers::StridedArrayView2D::type> SceneData::field(const SceneField name) const { + Containers::StridedArrayView2D data = field(name); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[fieldFor(name)], "Trade::SceneData::field():")) return {}; + #endif + return Containers::arrayCast<2, const typename std::remove_extent::type>(data); +} + +template Containers::StridedArrayView1D SceneData::mutableField(const SceneField name) { + Containers::StridedArrayView2D data = mutableField(name); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[fieldFor(name)], "Trade::SceneData::mutableField():")) return {}; + #endif + return Containers::arrayCast<1, T>(data); +} + +template Containers::StridedArrayView2D::type> SceneData::mutableField(const SceneField name) { + Containers::StridedArrayView2D data = mutableField(name); + #ifdef CORRADE_GRACEFUL_ASSERT /* Sigh. Brittle. Better idea? */ + if(!data.stride()[1]) return {}; + #endif + #ifndef CORRADE_NO_ASSERT + if(!checkFieldTypeCompatibility(_fields[fieldFor(name)], "Trade::SceneData::mutableField():")) return {}; + #endif + return Containers::arrayCast<2, typename std::remove_extent::type>(data); +} + }} #endif diff --git a/src/Magnum/Trade/Test/AbstractImporterTest.cpp b/src/Magnum/Trade/Test/AbstractImporterTest.cpp index 671aec909..6f4f43f28 100644 --- a/src/Magnum/Trade/Test/AbstractImporterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractImporterTest.cpp @@ -1644,8 +1644,9 @@ void AbstractImporterTest::scene() { return {}; } Containers::Optional doScene(UnsignedInt id) override { - if(id == 7) return SceneData{{}, {}, &state}; - return SceneData{{}, {}}; + if(id == 7) + return SceneData{SceneObjectType::UnsignedByte, 0, nullptr, {}, &state}; + return SceneData{SceneObjectType::UnsignedByte, 0, nullptr, {}}; } } importer; diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index f90dba8b2..75103d461 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -60,7 +60,7 @@ corrade_add_test(TradePbrClearCoatMaterialDataTest PbrClearCoatMaterialDataTest. corrade_add_test(TradePbrMetallicRoughnessMate___Test PbrMetallicRoughnessMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradePbrSpecularGlossinessMat___Test PbrSpecularGlossinessMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradePhongMaterialDataTest PhongMaterialDataTest.cpp LIBRARIES MagnumTradeTestLib) -corrade_add_test(TradeSceneDataTest SceneDataTest.cpp LIBRARIES MagnumTrade) +corrade_add_test(TradeSceneDataTest SceneDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeSkinDataTest SkinDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeTextureDataTest TextureDataTest.cpp LIBRARIES MagnumTrade) @@ -68,6 +68,7 @@ set_property(TARGET TradeAnimationDataTest TradeMaterialDataTest TradeMeshDataTest + TradeSceneDataTest APPEND PROPERTY COMPILE_DEFINITIONS "CORRADE_GRACEFUL_ASSERT") set_target_properties( diff --git a/src/Magnum/Trade/Test/SceneDataTest.cpp b/src/Magnum/Trade/Test/SceneDataTest.cpp index 26e81c9b7..a96702135 100644 --- a/src/Magnum/Trade/Test/SceneDataTest.cpp +++ b/src/Magnum/Trade/Test/SceneDataTest.cpp @@ -23,9 +23,18 @@ DEALINGS IN THE SOFTWARE. */ +#include +#include #include +#include +#include +#include #include "Magnum/Magnum.h" +#include "Magnum/Math/Half.h" +#include "Magnum/Math/DualComplex.h" +#include "Magnum/Math/DualQuaternion.h" +#include "Magnum/Math/Range.h" #include "Magnum/Trade/SceneData.h" namespace Magnum { namespace Trade { namespace Test { namespace { @@ -33,24 +42,1526 @@ namespace Magnum { namespace Trade { namespace Test { namespace { struct SceneDataTest: TestSuite::Tester { explicit SceneDataTest(); + void objectTypeSize(); + void objectTypeSizeInvalid(); + void debugObjectType(); + + void customFieldName(); + void customFieldNameTooLarge(); + void customFieldNameNotCustom(); + void debugFieldName(); + + void fieldTypeSize(); + void fieldTypeSizeInvalid(); + void debugFieldType(); + + void constructField(); + void constructFieldDefault(); + void constructFieldCustom(); + void constructField2D(); + void constructFieldTypeErased(); + void constructFieldNonOwningArray(); + void constructFieldOffsetOnly(); + void constructFieldArray(); + void constructFieldArray2D(); + void constructFieldArrayTypeErased(); + void constructFieldArrayOffsetOnly(); + + void constructFieldWrongType(); + void constructFieldInconsistentViewSize(); + void constructFieldTooLargeObjectStride(); + void constructFieldTooLargeFieldStride(); + void constructFieldWrongDataAccess(); + void constructField2DWrongSize(); + void constructField2DNonContiguous(); + void constructFieldArrayNonContiguous(); + void constructFieldArrayNotAllowed(); + void constructFieldArray2DWrongSize(); + void constructFieldArray2DNonContiguous(); + void construct(); + void constructZeroFields(); + void constructZeroObjects(); + + void constructNotOwned(); + + void constructDuplicateField(); + void constructDuplicateCustomField(); + void constructInconsistentObjectType(); + void constructObjectDataNotContained(); + void constructFieldDataNotContained(); + void constructObjectTypeTooSmall(); + void constructNotOwnedFlagOwned(); + void constructMismatchedTRSViews(); + void constructMismatchedMeshMaterialView(); + void constructCopy(); void constructMove(); + + template void objectsAsArrayByIndex(); + template void objectsAsArrayByName(); + void objectsAsArrayLongType(); + void objectsIntoArrayInvalidSize(); + template void parentsAsArray(); + #ifndef CORRADE_TARGET_32BIT + void parentsAsArrayLongType(); + #endif + void parentsIntoArrayInvalidSize(); + template void transformations2DAsArray(); + template void transformations2DAsArrayTRS(); + template void transformations2DAsArrayBut3DType(); + template void transformations2DAsArrayBut3DTypeTRS(); + void transformations2DIntoArrayInvalidSize(); + template void transformations3DAsArray(); + template void transformations3DAsArrayTRS(); + template void transformations3DAsArrayBut2DType(); + template void transformations3DAsArrayBut2DTypeTRS(); + void transformations3DIntoArrayInvalidSize(); + template void meshesAsArray(); + void meshesIntoArrayInvalidSize(); + template void meshMaterialsAsArray(); + void meshMaterialsIntoArrayInvalidSize(); + template void lightsAsArray(); + void lightsIntoArrayInvalidSize(); + template void camerasAsArray(); + void camerasIntoArrayInvalidSize(); + template void skinsAsArray(); + void skinsIntoArrayInvalidSize(); + + void mutableAccessNotAllowed(); + + void objectsNotFound(); + void objectsWrongType(); + + void fieldNotFound(); + void fieldWrongType(); + void fieldWrongArrayAccess(); + + void releaseFieldData(); + void releaseData(); +}; + +const struct { + const char* name; + DataFlags dataFlags; +} NotOwnedData[]{ + {"", {}}, + {"mutable", DataFlag::Mutable} }; SceneDataTest::SceneDataTest() { - addTests({&SceneDataTest::construct, + addTests({&SceneDataTest::objectTypeSize, + &SceneDataTest::objectTypeSizeInvalid, + &SceneDataTest::debugObjectType, + + &SceneDataTest::customFieldName, + &SceneDataTest::customFieldNameTooLarge, + &SceneDataTest::customFieldNameNotCustom, + &SceneDataTest::debugFieldName, + + &SceneDataTest::fieldTypeSize, + &SceneDataTest::fieldTypeSizeInvalid, + &SceneDataTest::debugFieldType, + + &SceneDataTest::constructField, + &SceneDataTest::constructFieldDefault, + &SceneDataTest::constructFieldCustom, + &SceneDataTest::constructField2D, + &SceneDataTest::constructFieldTypeErased, + &SceneDataTest::constructFieldNonOwningArray, + &SceneDataTest::constructFieldOffsetOnly, + &SceneDataTest::constructFieldArray, + &SceneDataTest::constructFieldArray2D, + &SceneDataTest::constructFieldArrayTypeErased, + &SceneDataTest::constructFieldArrayOffsetOnly, + + &SceneDataTest::constructFieldWrongType, + &SceneDataTest::constructFieldInconsistentViewSize, + &SceneDataTest::constructFieldTooLargeObjectStride, + &SceneDataTest::constructFieldTooLargeFieldStride, + &SceneDataTest::constructFieldWrongDataAccess, + &SceneDataTest::constructField2DWrongSize, + &SceneDataTest::constructField2DNonContiguous, + &SceneDataTest::constructFieldArrayNonContiguous, + &SceneDataTest::constructFieldArrayNotAllowed, + &SceneDataTest::constructFieldArray2DWrongSize, + &SceneDataTest::constructFieldArray2DNonContiguous, + + &SceneDataTest::construct, + &SceneDataTest::constructZeroFields, + &SceneDataTest::constructZeroObjects}); + + addInstancedTests({&SceneDataTest::constructNotOwned}, + Containers::arraySize(NotOwnedData)); + + addTests({&SceneDataTest::constructDuplicateField, + &SceneDataTest::constructDuplicateCustomField, + &SceneDataTest::constructInconsistentObjectType, + &SceneDataTest::constructObjectDataNotContained, + &SceneDataTest::constructFieldDataNotContained, + &SceneDataTest::constructObjectTypeTooSmall, + &SceneDataTest::constructNotOwnedFlagOwned, + &SceneDataTest::constructMismatchedTRSViews, + &SceneDataTest::constructMismatchedMeshMaterialView, + &SceneDataTest::constructCopy, - &SceneDataTest::constructMove}); + &SceneDataTest::constructMove, + + &SceneDataTest::objectsAsArrayByIndex, + &SceneDataTest::objectsAsArrayByIndex, + &SceneDataTest::objectsAsArrayByIndex, + &SceneDataTest::objectsAsArrayByIndex, + &SceneDataTest::objectsAsArrayByName, + &SceneDataTest::objectsAsArrayByName, + &SceneDataTest::objectsAsArrayByName, + &SceneDataTest::objectsAsArrayByName, + &SceneDataTest::objectsAsArrayLongType, + &SceneDataTest::objectsIntoArrayInvalidSize, + &SceneDataTest::parentsAsArray, + &SceneDataTest::parentsAsArray, + &SceneDataTest::parentsAsArray, + &SceneDataTest::parentsAsArray, + #ifndef CORRADE_TARGET_32BIT + &SceneDataTest::parentsAsArrayLongType, + #endif + &SceneDataTest::parentsIntoArrayInvalidSize, + &SceneDataTest::transformations2DAsArray, + &SceneDataTest::transformations2DAsArray, + &SceneDataTest::transformations2DAsArray, + &SceneDataTest::transformations2DAsArray, + &SceneDataTest::transformations2DAsArrayTRS, + &SceneDataTest::transformations2DAsArrayTRS, + &SceneDataTest::transformations2DAsArrayBut3DType, + &SceneDataTest::transformations2DAsArrayBut3DType, + &SceneDataTest::transformations2DAsArrayBut3DType, + &SceneDataTest::transformations2DAsArrayBut3DType, + &SceneDataTest::transformations2DAsArrayBut3DTypeTRS, + &SceneDataTest::transformations2DAsArrayBut3DTypeTRS, + &SceneDataTest::transformations2DIntoArrayInvalidSize, + &SceneDataTest::transformations3DAsArray, + &SceneDataTest::transformations3DAsArray, + &SceneDataTest::transformations3DAsArray, + &SceneDataTest::transformations3DAsArray, + &SceneDataTest::transformations3DAsArrayTRS, + &SceneDataTest::transformations3DAsArrayTRS, + &SceneDataTest::transformations3DAsArrayBut2DType, + &SceneDataTest::transformations3DAsArrayBut2DType, + &SceneDataTest::transformations3DAsArrayBut2DType, + &SceneDataTest::transformations3DAsArrayBut2DType, + &SceneDataTest::transformations3DAsArrayBut2DTypeTRS, + &SceneDataTest::transformations3DAsArrayBut2DTypeTRS, + &SceneDataTest::transformations3DIntoArrayInvalidSize, + &SceneDataTest::meshesAsArray, + &SceneDataTest::meshesAsArray, + &SceneDataTest::meshesAsArray, + &SceneDataTest::meshesIntoArrayInvalidSize, + &SceneDataTest::meshMaterialsAsArray, + &SceneDataTest::meshMaterialsAsArray, + &SceneDataTest::meshMaterialsAsArray, + &SceneDataTest::meshMaterialsIntoArrayInvalidSize, + &SceneDataTest::lightsAsArray, + &SceneDataTest::lightsAsArray, + &SceneDataTest::lightsAsArray, + &SceneDataTest::lightsIntoArrayInvalidSize, + &SceneDataTest::camerasAsArray, + &SceneDataTest::camerasAsArray, + &SceneDataTest::camerasAsArray, + &SceneDataTest::camerasIntoArrayInvalidSize, + &SceneDataTest::skinsAsArray, + &SceneDataTest::skinsAsArray, + &SceneDataTest::skinsAsArray, + &SceneDataTest::skinsIntoArrayInvalidSize, + + &SceneDataTest::mutableAccessNotAllowed, + + &SceneDataTest::objectsNotFound, + &SceneDataTest::objectsWrongType, + + &SceneDataTest::fieldNotFound, + &SceneDataTest::fieldWrongType, + &SceneDataTest::fieldWrongArrayAccess, + + &SceneDataTest::releaseFieldData, + &SceneDataTest::releaseData}); +} + +using namespace Math::Literals; + +void SceneDataTest::objectTypeSize() { + CORRADE_COMPARE(sceneObjectTypeSize(SceneObjectType::UnsignedByte), 1); + CORRADE_COMPARE(sceneObjectTypeSize(SceneObjectType::UnsignedShort), 2); + CORRADE_COMPARE(sceneObjectTypeSize(SceneObjectType::UnsignedInt), 4); + CORRADE_COMPARE(sceneObjectTypeSize(SceneObjectType::UnsignedLong), 8); +} + +void SceneDataTest::objectTypeSizeInvalid() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + sceneObjectTypeSize(SceneObjectType{}); + sceneObjectTypeSize(SceneObjectType(0x73)); + + CORRADE_COMPARE(out.str(), + "Trade::sceneObjectTypeSize(): invalid type Trade::SceneObjectType(0x0)\n" + "Trade::sceneObjectTypeSize(): invalid type Trade::SceneObjectType(0x73)\n"); +} + +void SceneDataTest::debugObjectType() { + std::ostringstream out; + Debug{&out} << SceneObjectType::UnsignedLong << SceneObjectType(0x73); + CORRADE_COMPARE(out.str(), "Trade::SceneObjectType::UnsignedLong Trade::SceneObjectType(0x73)\n"); +} + +void SceneDataTest::customFieldName() { + CORRADE_VERIFY(!isSceneFieldCustom(SceneField::Rotation)); + CORRADE_VERIFY(!isSceneFieldCustom(SceneField(0x0fffffffu))); + CORRADE_VERIFY(isSceneFieldCustom(SceneField::Custom)); + CORRADE_VERIFY(isSceneFieldCustom(SceneField(0x80000000u))); + + CORRADE_COMPARE(UnsignedInt(sceneFieldCustom(0)), 0x80000000u); + CORRADE_COMPARE(UnsignedInt(sceneFieldCustom(0xabcd)), 0x8000abcdu); + CORRADE_COMPARE(UnsignedInt(sceneFieldCustom(0x7fffffff)), 0xffffffffu); + + CORRADE_COMPARE(sceneFieldCustom(SceneField::Custom), 0); + CORRADE_COMPARE(sceneFieldCustom(SceneField(0x8000abcdu)), 0xabcd); + CORRADE_COMPARE(sceneFieldCustom(SceneField(0xffffffffu)), 0x7fffffffu); + + constexpr bool is = isSceneFieldCustom(SceneField(0x8000abcdu)); + CORRADE_VERIFY(is); + constexpr SceneField a = sceneFieldCustom(0xabcd); + CORRADE_COMPARE(UnsignedInt(a), 0x8000abcdu); + constexpr UnsignedInt b = sceneFieldCustom(a); + CORRADE_COMPARE(b, 0xabcd); +} + +void SceneDataTest::customFieldNameTooLarge() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + sceneFieldCustom(1u << 31); + CORRADE_COMPARE(out.str(), "Trade::sceneFieldCustom(): index 2147483648 too large\n"); +} + +void SceneDataTest::customFieldNameNotCustom() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + sceneFieldCustom(SceneField::Transformation); + CORRADE_COMPARE(out.str(), "Trade::sceneFieldCustom(): Trade::SceneField::Transformation is not custom\n"); +} + +void SceneDataTest::debugFieldName() { + std::ostringstream out; + Debug{&out} << SceneField::Transformation << sceneFieldCustom(73) << SceneField(0xdeadda7); + CORRADE_COMPARE(out.str(), "Trade::SceneField::Transformation Trade::SceneField::Custom(73) Trade::SceneField(0xdeadda7)\n"); +} + +void SceneDataTest::fieldTypeSize() { + /* Test at least one of every size */ + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Byte), sizeof(Byte)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Degh), sizeof(Degh)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Vector3ub), sizeof(Vector3ub)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Range1Dh), sizeof(Range1Dh)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Vector3s), sizeof(Vector3s)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Long), sizeof(Long)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix3x2h), sizeof(Matrix3x2h)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix4x2h), sizeof(Matrix4x2h)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix3x3h), sizeof(Matrix3x3h)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Range3Di), sizeof(Range3Di)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::DualQuaternion), sizeof(DualQuaternion)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix3x3), sizeof(Matrix3x3)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix3x2d), sizeof(Matrix3x2d)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::DualQuaterniond), sizeof(DualQuaterniond)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix3x3d), sizeof(Matrix3x3d)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix3x4d), sizeof(Matrix3x4d)); + CORRADE_COMPARE(sceneFieldTypeSize(SceneFieldType::Matrix4x4d), sizeof(Matrix4x4d)); +} + +void SceneDataTest::fieldTypeSizeInvalid() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + std::ostringstream out; + Error redirectError{&out}; + + sceneFieldTypeSize(SceneFieldType{}); + sceneFieldTypeSize(SceneFieldType(0xdead)); + + CORRADE_COMPARE(out.str(), + "Trade::sceneFieldTypeSize(): invalid type Trade::SceneFieldType(0x0)\n" + "Trade::sceneFieldTypeSize(): invalid type Trade::SceneFieldType(0xdead)\n"); +} + +void SceneDataTest::debugFieldType() { + std::ostringstream out; + Debug{&out} << SceneFieldType::Matrix3x4h << SceneFieldType(0xdead); + CORRADE_COMPARE(out.str(), "Trade::SceneFieldType::Matrix3x4h Trade::SceneFieldType(0xdead)\n"); +} + +constexpr Complexd Rotations2D[3] { + Complexd{Constantsd::sqrtHalf(), Constantsd::sqrtHalf()}, /* 45° */ + Complexd{1.0, 0.0}, /* 0° */ + Complexd{0.0, 1.0}, /* 90° */ +}; +const UnsignedShort RotationObjects2D[3] { + 17, + 35, + 98 +}; + +void SceneDataTest::constructField() { + const UnsignedShort rotationObjectData[3]{}; + const Complexd rotationFieldData[3]; + + SceneFieldData rotations{SceneField::Rotation, Containers::arrayView(rotationObjectData), Containers::arrayView(rotationFieldData)}; + CORRADE_VERIFY(!rotations.isOffsetOnly()); + CORRADE_COMPARE(rotations.name(), SceneField::Rotation); + CORRADE_COMPARE(rotations.size(), 3); + CORRADE_COMPARE(rotations.objectType(), SceneObjectType::UnsignedShort); + CORRADE_COMPARE(rotations.objectData().size(), 3); + CORRADE_COMPARE(rotations.objectData().stride(), sizeof(UnsignedShort)); + CORRADE_VERIFY(rotations.objectData().data() == rotationObjectData); + CORRADE_COMPARE(rotations.fieldType(), SceneFieldType::Complexd); + CORRADE_COMPARE(rotations.fieldArraySize(), 0); + CORRADE_COMPARE(rotations.fieldData().size(), 3); + CORRADE_COMPARE(rotations.fieldData().stride(), sizeof(Complexd)); + CORRADE_VERIFY(rotations.fieldData().data() == rotationFieldData); + + /* This is allowed too for simplicity, the parameter has to be large enough + tho */ + char someArray[3*sizeof(Complexd)]; + CORRADE_COMPARE(rotations.fieldData(someArray).size(), 3); + CORRADE_COMPARE(rotations.fieldData(someArray).stride(), sizeof(Complexd)); + CORRADE_VERIFY(rotations.fieldData(someArray).data() == rotationFieldData); + CORRADE_COMPARE(rotations.objectData(someArray).size(), 3); + CORRADE_COMPARE(rotations.objectData(someArray).stride(), sizeof(UnsignedShort)); + CORRADE_VERIFY(rotations.objectData(someArray).data() == rotationObjectData); + + constexpr SceneFieldData crotations{SceneField::Rotation, Containers::arrayView(RotationObjects2D), Containers::arrayView(Rotations2D)}; + constexpr bool isOffsetOnly = crotations.isOffsetOnly(); + constexpr SceneField name = crotations.name(); + constexpr SceneObjectType objectType = crotations.objectType(); + constexpr Containers::StridedArrayView1D objectData = crotations.objectData(); + constexpr SceneFieldType fieldType = crotations.fieldType(); + constexpr UnsignedShort fieldArraySize = crotations.fieldArraySize(); + constexpr Containers::StridedArrayView1D fieldData = crotations.fieldData(); + CORRADE_VERIFY(!isOffsetOnly); + CORRADE_COMPARE(name, SceneField::Rotation); + CORRADE_COMPARE(objectType, SceneObjectType::UnsignedShort); + CORRADE_COMPARE(objectData.size(), 3); + CORRADE_COMPARE(objectData.stride(), sizeof(UnsignedShort)); + CORRADE_COMPARE(objectData.data(), RotationObjects2D); + CORRADE_COMPARE(fieldType, SceneFieldType::Complexd); + CORRADE_COMPARE(fieldArraySize, 0); + CORRADE_COMPARE(fieldData.size(), 3); + CORRADE_COMPARE(fieldData.stride(), sizeof(Complexd)); + CORRADE_COMPARE(fieldData.data(), Rotations2D); +} + +void SceneDataTest::constructFieldDefault() { + SceneFieldData data; + CORRADE_COMPARE(data.name(), SceneField{}); + CORRADE_COMPARE(data.fieldType(), SceneFieldType{}); + CORRADE_COMPARE(data.objectType(), SceneObjectType{}); + + constexpr SceneFieldData cdata; + CORRADE_COMPARE(cdata.name(), SceneField{}); + CORRADE_COMPARE(cdata.fieldType(), SceneFieldType{}); + CORRADE_COMPARE(cdata.objectType(), SceneObjectType{}); +} + +void SceneDataTest::constructFieldCustom() { + /* Verifying it doesn't hit any assertion about disallowed type for given + attribute */ + + const UnsignedByte rangeObjectData[3]{}; + const Range2Dh rangeFieldData[3]; + SceneFieldData ranges{sceneFieldCustom(13), Containers::arrayView(rangeObjectData), Containers::arrayView(rangeFieldData)}; + CORRADE_COMPARE(ranges.name(), sceneFieldCustom(13)); + CORRADE_COMPARE(ranges.objectType(), SceneObjectType::UnsignedByte); + CORRADE_VERIFY(ranges.objectData().data() == rangeObjectData); + CORRADE_COMPARE(ranges.fieldType(), SceneFieldType::Range2Dh); + CORRADE_VERIFY(ranges.fieldData().data() == rangeFieldData); +} + +void SceneDataTest::constructField2D() { + char rotationObjectData[6*sizeof(UnsignedShort)]; + char rotationFieldData[6*sizeof(Complexd)]; + auto rotationObjectView = Containers::StridedArrayView2D{rotationObjectData, {6, sizeof(UnsignedShort)}}.every(2); + auto rotationFieldView = Containers::StridedArrayView2D{rotationFieldData, {6, sizeof(Complexd)}}.every(2); + + SceneFieldData rotations{SceneField::Rotation, rotationObjectView, SceneFieldType::Complexd, rotationFieldView}; + CORRADE_VERIFY(!rotations.isOffsetOnly()); + CORRADE_COMPARE(rotations.name(), SceneField::Rotation); + CORRADE_COMPARE(rotations.size(), 3); + CORRADE_COMPARE(rotations.objectType(), SceneObjectType::UnsignedShort); + CORRADE_COMPARE(rotations.objectData().size(), 3); + CORRADE_COMPARE(rotations.objectData().stride(), 2*sizeof(UnsignedShort)); + CORRADE_COMPARE(rotations.objectData().data(), rotationObjectView.data()); + CORRADE_COMPARE(rotations.fieldType(), SceneFieldType::Complexd); + CORRADE_COMPARE(rotations.fieldArraySize(), 0); + CORRADE_COMPARE(rotations.fieldData().size(), 3); + CORRADE_COMPARE(rotations.fieldData().stride(), 2*sizeof(Complexd)); + CORRADE_COMPARE(rotations.fieldData().data(), rotationFieldView.data()); +} + +void SceneDataTest::constructFieldTypeErased() { + const UnsignedLong scalingObjectData[3]{}; + const Vector3 scalingFieldData[3]; + SceneFieldData scalings{SceneField::Scaling, SceneObjectType::UnsignedLong, Containers::arrayCast(Containers::stridedArrayView(scalingObjectData)), SceneFieldType::Vector3, Containers::arrayCast(Containers::stridedArrayView(scalingFieldData))}; + CORRADE_VERIFY(!scalings.isOffsetOnly()); + CORRADE_COMPARE(scalings.name(), SceneField::Scaling); + CORRADE_COMPARE(scalings.size(), 3); + CORRADE_COMPARE(scalings.objectType(), SceneObjectType::UnsignedLong); + CORRADE_COMPARE(scalings.objectData().size(), 3); + CORRADE_COMPARE(scalings.objectData().stride(), sizeof(UnsignedLong)); + CORRADE_COMPARE(scalings.objectData().data(), scalingObjectData); + CORRADE_COMPARE(scalings.fieldType(), SceneFieldType::Vector3); + CORRADE_COMPARE(scalings.fieldArraySize(), 0); + CORRADE_COMPARE(scalings.fieldData().size(), 3); + CORRADE_COMPARE(scalings.fieldData().stride(), sizeof(Vector3)); + CORRADE_COMPARE(scalings.fieldData().data(), scalingFieldData); +} + +void SceneDataTest::constructFieldNonOwningArray() { + const SceneFieldData data[3]; + Containers::Array array = sceneFieldDataNonOwningArray(data); + CORRADE_COMPARE(array.size(), 3); + CORRADE_COMPARE(static_cast(array.data()), data); +} + +void SceneDataTest::constructFieldOffsetOnly() { + struct Data { + Byte parent; + UnsignedShort object; + Vector2 translation; + } data[] { + {0, 2, {2.0f, 3.0f}}, + {0, 15, {67.0f, -1.1f}} + }; + + SceneFieldData a{SceneField::Translation, 2, SceneObjectType::UnsignedShort, offsetof(Data, object), sizeof(Data), SceneFieldType::Vector2, offsetof(Data, translation), sizeof(Data)}; + CORRADE_VERIFY(a.isOffsetOnly()); + CORRADE_COMPARE(a.name(), SceneField::Translation); + CORRADE_COMPARE(a.size(), 2); + CORRADE_COMPARE(a.objectType(), SceneObjectType::UnsignedShort); + CORRADE_COMPARE(a.objectData(data).size(), 2); + CORRADE_COMPARE(a.objectData(data).stride(), sizeof(Data)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.objectData(data)), + Containers::arrayView({2, 15}), + TestSuite::Compare::Container); + CORRADE_COMPARE(a.fieldType(), SceneFieldType::Vector2); + CORRADE_COMPARE(a.fieldArraySize(), 0); + CORRADE_COMPARE(a.fieldData(data).size(), 2); + CORRADE_COMPARE(a.fieldData(data).stride(), sizeof(Data)); + CORRADE_COMPARE_AS(Containers::arrayCast(a.fieldData(data)), + Containers::arrayView({{2.0f, 3.0f}, {67.0f, -1.1f}}), + TestSuite::Compare::Container); +} + +constexpr UnsignedByte ArrayOffsetObjectData[3]{}; +constexpr Int ArrayOffsetFieldData[3*4]{}; + +void SceneDataTest::constructFieldArray() { + UnsignedByte offsetObjectData[3]; + Int offsetFieldData[3*4]; + SceneFieldData data{sceneFieldCustom(34), Containers::arrayView(offsetObjectData), Containers::StridedArrayView2D{offsetFieldData, {3, 4}}}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), sceneFieldCustom(34)); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.objectType(), SceneObjectType::UnsignedByte); + CORRADE_COMPARE(data.objectData().size(), 3); + CORRADE_COMPARE(data.objectData().stride(), sizeof(UnsignedByte)); + CORRADE_VERIFY(data.objectData().data() == offsetObjectData); + CORRADE_COMPARE(data.fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(data.fieldArraySize(), 4); + CORRADE_COMPARE(data.fieldData().size(), 3); + CORRADE_COMPARE(data.fieldData().stride(), 4*sizeof(Int)); + CORRADE_VERIFY(data.fieldData().data() == offsetFieldData); + + constexpr SceneFieldData cdata{sceneFieldCustom(34), Containers::arrayView(ArrayOffsetObjectData), Containers::StridedArrayView2D{ArrayOffsetFieldData, {3, 4}}}; + CORRADE_VERIFY(!cdata.isOffsetOnly()); + CORRADE_COMPARE(cdata.name(), sceneFieldCustom(34)); + CORRADE_COMPARE(cdata.size(), 3); + CORRADE_COMPARE(cdata.objectType(), SceneObjectType::UnsignedByte); + CORRADE_COMPARE(cdata.objectData().size(), 3); + CORRADE_COMPARE(cdata.objectData().stride(), sizeof(UnsignedByte)); + CORRADE_VERIFY(cdata.objectData().data() == ArrayOffsetObjectData); + CORRADE_COMPARE(cdata.fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(cdata.fieldArraySize(), 4); + CORRADE_COMPARE(cdata.fieldData().size(), 3); + CORRADE_COMPARE(cdata.fieldData().stride(), 4*sizeof(Int)); + CORRADE_VERIFY(cdata.fieldData().data() == ArrayOffsetFieldData); +} + +void SceneDataTest::constructFieldArray2D() { + char offsetObjectData[3*sizeof(UnsignedByte)]; + char offsetFieldData[3*4*sizeof(Int)]; + SceneFieldData data{sceneFieldCustom(34), Containers::StridedArrayView2D{offsetObjectData, {3, sizeof(UnsignedByte)}}, SceneFieldType::Int, Containers::StridedArrayView2D{offsetFieldData, {3, 4*sizeof(Int)}}, 4}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), sceneFieldCustom(34)); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.objectType(), SceneObjectType::UnsignedByte); + CORRADE_COMPARE(data.objectData().size(), 3); + CORRADE_COMPARE(data.objectData().stride(), sizeof(UnsignedByte)); + CORRADE_VERIFY(data.objectData().data() == offsetObjectData); + CORRADE_COMPARE(data.fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(data.fieldArraySize(), 4); + CORRADE_COMPARE(data.fieldData().size(), 3); + CORRADE_COMPARE(data.fieldData().stride(), 4*sizeof(Int)); + CORRADE_VERIFY(data.fieldData().data() == offsetFieldData); +} + +void SceneDataTest::constructFieldArrayTypeErased() { + Int offsetData[3*4]; + Containers::StridedArrayView1D offset{offsetData, 3, 4*sizeof(Int)}; + UnsignedByte offsetObjectData[3]; + SceneFieldData data{sceneFieldCustom(34), SceneObjectType::UnsignedByte, Containers::arrayCast(Containers::stridedArrayView(offsetObjectData)), SceneFieldType::Int, Containers::arrayCast(offset), 4}; + CORRADE_VERIFY(!data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), sceneFieldCustom(34)); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(data.objectType(), SceneObjectType::UnsignedByte); + CORRADE_COMPARE(data.objectData().size(), 3); + CORRADE_COMPARE(data.objectData().stride(), sizeof(UnsignedByte)); + CORRADE_VERIFY(data.objectData().data() == offsetObjectData); + CORRADE_COMPARE(data.fieldArraySize(), 4); + CORRADE_COMPARE(data.fieldData().size(), 3); + CORRADE_COMPARE(data.fieldData().stride(), 4*sizeof(Int)); + CORRADE_VERIFY(data.fieldData().data() == offsetData); +} + +void SceneDataTest::constructFieldArrayOffsetOnly() { + struct Data { + Byte parent; + UnsignedByte object; + Int offset[4]; + }; + + SceneFieldData data{sceneFieldCustom(34), 3, SceneObjectType::UnsignedByte, offsetof(Data, object), sizeof(Data), SceneFieldType::Int, offsetof(Data, offset), sizeof(Data), 4}; + CORRADE_VERIFY(data.isOffsetOnly()); + CORRADE_COMPARE(data.name(), sceneFieldCustom(34)); + CORRADE_COMPARE(data.size(), 3); + CORRADE_COMPARE(data.objectType(), SceneObjectType::UnsignedByte); + CORRADE_COMPARE(data.fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(data.fieldArraySize(), 4); + + Data actual[3]; + CORRADE_COMPARE(data.fieldData(actual).size(), 3); + CORRADE_COMPARE(data.fieldData(actual).stride(), sizeof(Data)); + CORRADE_VERIFY(data.fieldData(actual).data() == &actual[0].offset); + CORRADE_COMPARE(data.objectData(actual).size(), 3); + CORRADE_COMPARE(data.objectData(actual).stride(), sizeof(Data)); + CORRADE_VERIFY(data.objectData(actual).data() == &actual[0].object); + + constexpr SceneFieldData cdata{sceneFieldCustom(34), 3, SceneObjectType::UnsignedByte, offsetof(Data, object), sizeof(Data), SceneFieldType::Int, offsetof(Data, offset), sizeof(Data), 4}; + CORRADE_VERIFY(cdata.isOffsetOnly()); + CORRADE_COMPARE(cdata.name(), sceneFieldCustom(34)); + CORRADE_COMPARE(cdata.size(), 3); + CORRADE_COMPARE(cdata.objectType(), SceneObjectType::UnsignedByte); + CORRADE_COMPARE(cdata.fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(cdata.fieldArraySize(), 4); +} + +void SceneDataTest::constructFieldInconsistentViewSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + const UnsignedShort rotationObjectData[3]{}; + const Complexd rotationFieldData[2]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Rotation, Containers::arrayView(rotationObjectData), Containers::arrayView(rotationFieldData)}; + CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: expected object and field view to have the same size but got 3 and 2\n"); +} + +void SceneDataTest::constructFieldWrongType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + const UnsignedShort rotationObjectData[3]{}; + const Quaternion rotationFieldData[3]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Transformation, Containers::arrayView(rotationObjectData), Containers::arrayView(rotationFieldData)}; + SceneFieldData{SceneField::Transformation, 3, SceneObjectType::UnsignedShort, 0, sizeof(UnsignedShort), SceneFieldType::Quaternion, 0, sizeof(Quaternion)}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: Trade::SceneFieldType::Quaternion is not a valid type for Trade::SceneField::Transformation\n" + "Trade::SceneFieldData: Trade::SceneFieldType::Quaternion is not a valid type for Trade::SceneField::Transformation\n"); +} + +void SceneDataTest::constructFieldTooLargeObjectStride() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + UnsignedInt enough[2]; + char toomuch[2*(32768 + sizeof(UnsignedInt))]; + + /* These should be fine */ + SceneFieldData{SceneField::Mesh, SceneObjectType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32767}, SceneFieldType::UnsignedInt, enough}; + SceneFieldData{SceneField::Mesh, SceneObjectType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}.flipped<0>(), SceneFieldType::UnsignedInt, enough}; + SceneFieldData{SceneField::Mesh, 2, SceneObjectType::UnsignedInt, 0, 32767, SceneFieldType::UnsignedInt, 0, 4}; + SceneFieldData{SceneField::Mesh, 2, SceneObjectType::UnsignedInt, 65536, -32768, SceneFieldType::UnsignedInt, 0, 4}; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Mesh, SceneObjectType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}, SceneFieldType::UnsignedInt, enough}; + SceneFieldData{SceneField::Mesh, SceneObjectType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32769}.flipped<0>(), SceneFieldType::UnsignedInt, enough}; + SceneFieldData{SceneField::Mesh, 2, SceneObjectType::UnsignedInt, 0, 32768, SceneFieldType::UnsignedInt, 0, 4}; + SceneFieldData{SceneField::Mesh, 2, SceneObjectType::UnsignedInt, 65538, -32769, SceneFieldType::UnsignedInt, 0, 4}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: expected object view stride to fit into 16 bits, but got 32768\n" + "Trade::SceneFieldData: expected object view stride to fit into 16 bits, but got -32769\n" + "Trade::SceneFieldData: expected object view stride to fit into 16 bits, but got 32768\n" + "Trade::SceneFieldData: expected object view stride to fit into 16 bits, but got -32769\n"); +} + +void SceneDataTest::constructFieldTooLargeFieldStride() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + UnsignedInt enough[2]; + char toomuch[2*(32768 + sizeof(UnsignedInt))]; + + /* These should be fine */ + SceneFieldData{SceneField::Mesh, SceneObjectType::UnsignedInt, enough, SceneFieldType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32767}}; + SceneFieldData{SceneField::Mesh, SceneObjectType::UnsignedInt, enough, SceneFieldType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}.flipped<0>()}; + SceneFieldData{SceneField::Mesh, 2, SceneObjectType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 0, 32767}; + SceneFieldData{SceneField::Mesh, 2, SceneObjectType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 65536, -32768}; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Mesh, SceneObjectType::UnsignedInt, enough, SceneFieldType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32768}}; + SceneFieldData{SceneField::Mesh, SceneObjectType::UnsignedInt, enough, SceneFieldType::UnsignedInt, Containers::StridedArrayView1D{Containers::arrayCast(toomuch), 2, 32769}.flipped<0>()}; + SceneFieldData{SceneField::Mesh, 2, SceneObjectType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 0, 32768}; + SceneFieldData{SceneField::Mesh, 2, SceneObjectType::UnsignedInt, 0, 4, SceneFieldType::UnsignedInt, 65538, -32769}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got 32768\n" + "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got -32769\n" + "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got 32768\n" + "Trade::SceneFieldData: expected field view stride to fit into 16 bits, but got -32769\n"); +} + +void SceneDataTest::constructFieldWrongDataAccess() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + const UnsignedShort rotationObjectData[3]{}; + const Quaternion rotationFieldData[3]; + SceneFieldData a{SceneField::Rotation, Containers::arrayView(rotationObjectData), Containers::arrayView(rotationFieldData)}; + SceneFieldData b{SceneField::Rotation, 3, SceneObjectType::UnsignedShort, 0, sizeof(UnsignedShort), SceneFieldType::Quaternion, 0, sizeof(Quaternion)}; + CORRADE_VERIFY(!a.isOffsetOnly()); + CORRADE_VERIFY(b.isOffsetOnly()); + + a.objectData(rotationObjectData); /* This is fine, no asserts */ + a.fieldData(rotationFieldData); + + std::ostringstream out; + Error redirectError{&out}; + b.objectData(); + b.fieldData(); + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData::objectData(): the field is offset-only, supply a data array\n" + "Trade::SceneFieldData::fieldData(): the field is offset-only, supply a data array\n"); +} + +void SceneDataTest::constructField2DWrongSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + char rotationFieldData[5*sizeof(Complex)]; + char rotationObjectData[5*sizeof(UnsignedInt)]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Rotation, + Containers::StridedArrayView2D{rotationObjectData, {4, 5}}.every(2), + SceneFieldType::Complex, + Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}.every(2)}; + SceneFieldData{SceneField::Translation, + Containers::StridedArrayView2D{rotationObjectData, {4, sizeof(UnsignedInt)}}.every(2), + SceneFieldType::Vector3, + Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}.every(2)}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: expected second object view dimension size 1, 2, 4 or 8 but got 5\n" + "Trade::SceneFieldData: second field view dimension size 8 doesn't match Trade::SceneFieldType::Vector3\n"); +} + +void SceneDataTest::constructField2DNonContiguous() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + char rotationObjectData[8*sizeof(UnsignedInt)]; + char rotationFieldData[8*sizeof(Complex)]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Rotation, + Containers::StridedArrayView2D{rotationObjectData, {4, 2*sizeof(UnsignedInt)}}.every({1, 2}), + SceneFieldType::Complex, + Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}}; + SceneFieldData{SceneField::Rotation, + Containers::StridedArrayView2D{rotationObjectData, {4, sizeof(UnsignedInt)}}, + SceneFieldType::Complex, + Containers::StridedArrayView2D{rotationFieldData, {4, 2*sizeof(Complex)}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: second object view dimension is not contiguous\n" + "Trade::SceneFieldData: second field view dimension is not contiguous\n"); +} + +void SceneDataTest::constructFieldArrayNonContiguous() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + UnsignedByte offsetObjectData[3]; + Int offsetFieldData[3*4]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData data{sceneFieldCustom(34), Containers::arrayView(offsetObjectData), Containers::StridedArrayView2D{offsetFieldData, {3, 4}}.every({1, 2})}; + CORRADE_COMPARE(out.str(), "Trade::SceneFieldData: second field view dimension is not contiguous\n"); +} + +void SceneDataTest::constructFieldArrayNotAllowed() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + UnsignedShort rotationObjectData[3]{}; + Quaternion rotationFieldData[3]; + Containers::ArrayView rotationObjects = rotationObjectData; + Containers::ArrayView rotationFields = rotationFieldData; + Containers::StridedArrayView2D rotationFields2D{rotationFieldData, {3, 3}, {0, sizeof(Quaternion)}}; + auto rotationFields2DChar = Containers::arrayCast<2, const char>(rotationFields2D); + auto rotationObjectsChar = Containers::arrayCast<2, const char>(rotationObjects); + + /* This is all fine */ + SceneFieldData{SceneField::Rotation, + SceneObjectType::UnsignedShort, rotationObjects, + SceneFieldType::Quaternion, rotationFields, 0}; + SceneFieldData{SceneField::Rotation, 3, + SceneObjectType::UnsignedShort, 0, sizeof(UnsignedShort), + SceneFieldType::Quaternion, 0, sizeof(Quaternion), 0}; + SceneFieldData{sceneFieldCustom(37), + rotationObjects, + rotationFields2D}; + SceneFieldData{sceneFieldCustom(37), + rotationObjectsChar, + SceneFieldType::Quaternion, rotationFields2DChar, 3}; + SceneFieldData{sceneFieldCustom(37), 3, + SceneObjectType::UnsignedShort, 0, sizeof(UnsignedShort), + SceneFieldType::Quaternion, 0, sizeof(Quaternion), 3}; + + /* This is not */ + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{SceneField::Rotation, + SceneObjectType::UnsignedShort, rotationObjects, + SceneFieldType::Quaternion, rotationFields, 3}; + SceneFieldData{SceneField::Rotation, 3, + SceneObjectType::UnsignedShort, 0, sizeof(UnsignedShort), + SceneFieldType::Quaternion, 0, sizeof(Quaternion), 3}; + SceneFieldData{SceneField::Rotation, + rotationObjects, + rotationFields2D}; + SceneFieldData{SceneField::Rotation, + rotationObjectsChar, + SceneFieldType::Quaternion, rotationFields2DChar, 3}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: Trade::SceneField::Rotation can't be an array field\n" + "Trade::SceneFieldData: Trade::SceneField::Rotation can't be an array field\n" + "Trade::SceneFieldData: Trade::SceneField::Rotation can't be an array field\n" + "Trade::SceneFieldData: Trade::SceneField::Rotation can't be an array field\n"); +} + +void SceneDataTest::constructFieldArray2DWrongSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + char rotationObjectData[4*sizeof(UnsignedInt)]; + char rotationFieldData[4*sizeof(Complex)]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{sceneFieldCustom(37), + Containers::StridedArrayView2D{rotationObjectData, {4, sizeof(UnsignedInt)}}.every(2), + SceneFieldType::Int, + Containers::StridedArrayView2D{rotationFieldData, {4, sizeof(Complex)}}.every(2), 3}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: second field view dimension size 8 doesn't match Trade::SceneFieldType::Int and field array size 3\n"); +} + +void SceneDataTest::constructFieldArray2DNonContiguous() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + char offsetObjectData[18*sizeof(UnsignedInt)]; + char offsetFieldData[18*sizeof(Int)]; + + std::ostringstream out; + Error redirectError{&out}; + SceneFieldData{sceneFieldCustom(37), + Containers::StridedArrayView2D{offsetObjectData, {3, 2*sizeof(UnsignedInt)}}.every({1, 2}), + SceneFieldType::Int, + Containers::StridedArrayView2D{offsetFieldData, {3, 3*sizeof(Int)}}, 3}; + SceneFieldData{sceneFieldCustom(37), + Containers::StridedArrayView2D{offsetObjectData, {3, sizeof(UnsignedInt)}}, + SceneFieldType::Int, + Containers::StridedArrayView2D{offsetFieldData, {3, 6*sizeof(Int)}}.every({1, 2}), 3}; + CORRADE_COMPARE(out.str(), + "Trade::SceneFieldData: second object view dimension is not contiguous\n" + "Trade::SceneFieldData: second field view dimension is not contiguous\n"); } void SceneDataTest::construct() { - const int a{}; - const SceneData data{{0, 1, 4}, {2, 5}, &a}; + struct TransformParent { + UnsignedShort object; + Matrix4 transformation; + Int parent; + }; + + Containers::StridedArrayView1D transformsParentFieldObjectData; + Containers::StridedArrayView1D meshFieldData; + Containers::StridedArrayView1D radiusFieldData; + Containers::StridedArrayView1D materialMeshRadiusObjectData; + Containers::ArrayTuple data{ + {NoInit, 5, transformsParentFieldObjectData}, + {NoInit, 2, meshFieldData}, + {NoInit, 2, radiusFieldData}, + {NoInit, 2, materialMeshRadiusObjectData}, + }; + transformsParentFieldObjectData[0].object = 4; + transformsParentFieldObjectData[0].transformation = Matrix4::translation(Vector3::xAxis(5.0f)); + transformsParentFieldObjectData[0].parent = -1; + + transformsParentFieldObjectData[1].object = 2; + transformsParentFieldObjectData[1].transformation = Matrix4::translation(Vector3::yAxis(5.0f)); + transformsParentFieldObjectData[1].parent = 0; + + transformsParentFieldObjectData[2].object = 3; + transformsParentFieldObjectData[2].transformation = Matrix4::translation(Vector3::zAxis(5.0f)); + transformsParentFieldObjectData[2].parent = 2; + + transformsParentFieldObjectData[3].object = 0; + transformsParentFieldObjectData[3].transformation = Matrix4::translation(Vector3::yScale(5.0f)); + transformsParentFieldObjectData[3].parent = 1; + + transformsParentFieldObjectData[4].object = 1; + transformsParentFieldObjectData[4].transformation = Matrix4::translation(Vector3::zScale(5.0f)); + transformsParentFieldObjectData[4].parent = -1; + + meshFieldData[0] = 5; + radiusFieldData[0] = {37.5f, 1.5f}; + materialMeshRadiusObjectData[0] = 2; + + meshFieldData[1] = 7; + radiusFieldData[1] = {22.5f, 0.5f}; + materialMeshRadiusObjectData[1] = 6; + + int importerState; + SceneFieldData transformations{SceneField::Transformation, + transformsParentFieldObjectData.slice(&TransformParent::object), + transformsParentFieldObjectData.slice(&TransformParent::transformation)}; + /* Offset-only */ + SceneFieldData parents{SceneField::Parent, 5, + SceneObjectType::UnsignedShort, offsetof(TransformParent, object), sizeof(TransformParent), + SceneFieldType::Int, offsetof(TransformParent, parent), sizeof(TransformParent)}; + SceneFieldData meshes{SceneField::Mesh, + materialMeshRadiusObjectData, + meshFieldData}; + /* Custom & array */ + SceneFieldData radiuses{sceneFieldCustom(37), + materialMeshRadiusObjectData, + Containers::arrayCast<2, Float>(radiusFieldData)}; + SceneData scene{SceneObjectType::UnsignedShort, 8, std::move(data), { + transformations, parents, meshes, radiuses + }, &importerState}; + + /* Basics */ + CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_VERIFY(!scene.fieldData().empty()); + CORRADE_COMPARE(static_cast(scene.data()), transformsParentFieldObjectData.data()); + CORRADE_COMPARE(static_cast(scene.mutableData()), transformsParentFieldObjectData.data()); + CORRADE_COMPARE(scene.objectCount(), 8); + CORRADE_COMPARE(scene.objectType(), SceneObjectType::UnsignedShort); + CORRADE_COMPARE(scene.fieldCount(), 4); + CORRADE_COMPARE(scene.importerState(), &importerState); + + /* Field property access by ID */ + CORRADE_COMPARE(scene.fieldName(0), SceneField::Transformation); + CORRADE_COMPARE(scene.fieldName(1), SceneField::Parent); + CORRADE_COMPARE(scene.fieldName(2), SceneField::Mesh); + CORRADE_COMPARE(scene.fieldName(3), sceneFieldCustom(37)); + CORRADE_COMPARE(scene.fieldType(0), SceneFieldType::Matrix4x4); + CORRADE_COMPARE(scene.fieldType(1), SceneFieldType::Int); + CORRADE_COMPARE(scene.fieldType(2), SceneFieldType::UnsignedByte); + CORRADE_COMPARE(scene.fieldType(3), SceneFieldType::Float); + CORRADE_COMPARE(scene.fieldSize(0), 5); + CORRADE_COMPARE(scene.fieldSize(1), 5); + CORRADE_COMPARE(scene.fieldSize(2), 2); + CORRADE_COMPARE(scene.fieldSize(3), 2); + CORRADE_COMPARE(scene.fieldArraySize(0), 0); + CORRADE_COMPARE(scene.fieldArraySize(1), 0); + CORRADE_COMPARE(scene.fieldArraySize(2), 0); + CORRADE_COMPARE(scene.fieldArraySize(3), 2); + + /* Raw field data access by ID */ + CORRADE_COMPARE(scene.fieldData(2).name(), SceneField::Mesh); + CORRADE_COMPARE(scene.fieldData(2).size(), 2); + CORRADE_COMPARE(scene.fieldData(2).objectType(), SceneObjectType::UnsignedShort); + CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(2).objectData())[1], 6); + CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(2).fieldData())[1], 7); + CORRADE_VERIFY(!scene.fieldData(2).isOffsetOnly()); + CORRADE_COMPARE(scene.fieldData(2).fieldType(), SceneFieldType::UnsignedByte); + CORRADE_COMPARE(scene.fieldData(2).fieldArraySize(), 0); + /* Offset-only */ + CORRADE_COMPARE(scene.fieldData(1).name(), SceneField::Parent); + CORRADE_COMPARE(scene.fieldData(1).size(), 5); + CORRADE_COMPARE(scene.fieldData(1).objectType(), SceneObjectType::UnsignedShort); + CORRADE_VERIFY(!scene.fieldData(1).isOffsetOnly()); + CORRADE_COMPARE(scene.fieldData(1).fieldType(), SceneFieldType::Int); + CORRADE_COMPARE(scene.fieldData(1).fieldArraySize(), 0); + CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(1).objectData())[4], 1); + CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(1).fieldData())[4], -1); + /* Array */ + CORRADE_COMPARE(scene.fieldData(3).name(), sceneFieldCustom(37)); + CORRADE_COMPARE(scene.fieldData(3).size(), 2); + CORRADE_COMPARE(scene.fieldData(3).objectType(), SceneObjectType::UnsignedShort); + CORRADE_VERIFY(!scene.fieldData(3).isOffsetOnly()); + CORRADE_COMPARE(scene.fieldData(3).fieldType(), SceneFieldType::Float); + CORRADE_COMPARE(scene.fieldData(3).fieldArraySize(), 2); + CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(3).objectData())[0], 2); + CORRADE_COMPARE(Containers::arrayCast(scene.fieldData(3).fieldData())[0], (Vector2{37.5f, 1.5f})); + + /* Typeless object access by ID with a cast later */ + CORRADE_COMPARE(scene.objects(0).size()[0], 5); + CORRADE_COMPARE(scene.objects(1).size()[0], 5); + CORRADE_COMPARE(scene.objects(2).size()[0], 2); + CORRADE_COMPARE(scene.objects(3).size()[0], 2); + CORRADE_COMPARE(scene.mutableObjects(0).size()[0], 5); + CORRADE_COMPARE(scene.mutableObjects(1).size()[0], 5); + CORRADE_COMPARE(scene.mutableObjects(2).size()[0], 2); + CORRADE_COMPARE(scene.mutableObjects(3).size()[0], 2); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.objects(0))[2]), 3); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.objects(1))[4]), 1); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.objects(2))[1]), 6); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.objects(3))[0]), 2); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableObjects(0))[2]), 3); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableObjects(1))[4]), 1); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableObjects(2))[1]), 6); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableObjects(3))[0]), 2); + + /* Typeless field access by ID with a cast later */ + CORRADE_COMPARE(scene.field(0).size()[0], 5); + CORRADE_COMPARE(scene.field(1).size()[0], 5); + CORRADE_COMPARE(scene.field(2).size()[0], 2); + CORRADE_COMPARE(scene.field(3).size()[0], 2); + CORRADE_COMPARE(scene.mutableField(0).size()[0], 5); + CORRADE_COMPARE(scene.mutableField(1).size()[0], 5); + CORRADE_COMPARE(scene.mutableField(2).size()[0], 2); + CORRADE_COMPARE(scene.mutableField(3).size()[0], 2); + CORRADE_COMPARE((Containers::arrayCast<1, const Matrix4>(scene.field(0))[2]), Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE((Containers::arrayCast<1, const Int>(scene.field(1))[4]), -1); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedByte>(scene.field(2))[1]), 7); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>(scene.field(3))[0]), (Vector2{37.5f, 1.5f})); + CORRADE_COMPARE((Containers::arrayCast<1, Matrix4>(scene.mutableField(0))[2]), Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE((Containers::arrayCast<1, Int>(scene.mutableField(1))[4]), -1); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedByte>(scene.mutableField(2))[1]), 7); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>(scene.mutableField(3))[0]), (Vector2{37.5f, 1.5f})); + + /* Typed object access by ID */ + CORRADE_COMPARE(scene.objects(0).size(), 5); + CORRADE_COMPARE(scene.objects(1).size(), 5); + CORRADE_COMPARE(scene.objects(2).size(), 2); + CORRADE_COMPARE(scene.objects(3).size(), 2); + CORRADE_COMPARE(scene.mutableObjects(0).size(), 5); + CORRADE_COMPARE(scene.mutableObjects(1).size(), 5); + CORRADE_COMPARE(scene.mutableObjects(2).size(), 2); + CORRADE_COMPARE(scene.mutableObjects(3).size(), 2); + CORRADE_COMPARE(scene.objects(0)[2], 3); + CORRADE_COMPARE(scene.objects(1)[4], 1); + CORRADE_COMPARE(scene.objects(2)[1], 6); + CORRADE_COMPARE(scene.objects(3)[0], 2); + CORRADE_COMPARE(scene.mutableObjects(0)[2], 3); + CORRADE_COMPARE(scene.mutableObjects(1)[4], 1); + CORRADE_COMPARE(scene.mutableObjects(2)[1], 6); + CORRADE_COMPARE(scene.mutableObjects(3)[0], 2); + + /* Typed field access by ID */ + CORRADE_COMPARE(scene.field(0).size(), 5); + CORRADE_COMPARE(scene.field(1).size(), 5); + CORRADE_COMPARE(scene.field(2).size(), 2); + CORRADE_COMPARE(scene.field(3).size()[0], 2); + CORRADE_COMPARE(scene.field(3).size()[1], 2); + CORRADE_COMPARE(scene.mutableField(0).size(), 5); + CORRADE_COMPARE(scene.mutableField(1).size(), 5); + CORRADE_COMPARE(scene.mutableField(2).size(), 2); + CORRADE_COMPARE(scene.mutableField(3).size()[0], 2); + CORRADE_COMPARE(scene.mutableField(3).size()[1], 2); + CORRADE_COMPARE(scene.field(0)[2], Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE(scene.field(1)[4], -1); + CORRADE_COMPARE(scene.field(2)[1], 7); + CORRADE_COMPARE(scene.field(3)[0][0], 37.5f); + CORRADE_COMPARE(scene.field(3)[0][1], 1.5f); + CORRADE_COMPARE(scene.mutableField(0)[2], Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE(scene.mutableField(1)[4], -1); + CORRADE_COMPARE(scene.mutableField(2)[1], 7); + CORRADE_COMPARE(scene.mutableField(3)[0][0], 37.5f); + 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); + CORRADE_COMPARE(scene.fieldType(sceneFieldCustom(37)), SceneFieldType::Float); + CORRADE_COMPARE(scene.fieldSize(SceneField::Transformation), 5); + CORRADE_COMPARE(scene.fieldSize(SceneField::Parent), 5); + CORRADE_COMPARE(scene.fieldSize(SceneField::Mesh), 2); + CORRADE_COMPARE(scene.fieldSize(sceneFieldCustom(37)), 2); + CORRADE_COMPARE(scene.fieldArraySize(SceneField::Transformation), 0); + CORRADE_COMPARE(scene.fieldArraySize(SceneField::Parent), 0); + CORRADE_COMPARE(scene.fieldArraySize(SceneField::Mesh), 0); + CORRADE_COMPARE(scene.fieldArraySize(sceneFieldCustom(37)), 2); + + /* Typeless object access by name with a cast later */ + CORRADE_COMPARE(scene.objects(SceneField::Transformation).size()[0], 5); + CORRADE_COMPARE(scene.objects(SceneField::Parent).size()[0], 5); + CORRADE_COMPARE(scene.objects(2).size()[0], 2); + CORRADE_COMPARE(scene.objects(3).size()[0], 2); + CORRADE_COMPARE(scene.mutableObjects(SceneField::Transformation).size()[0], 5); + CORRADE_COMPARE(scene.mutableObjects(SceneField::Parent).size()[0], 5); + CORRADE_COMPARE(scene.mutableObjects(2).size()[0], 2); + CORRADE_COMPARE(scene.mutableObjects(3).size()[0], 2); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.objects(SceneField::Transformation))[2]), 3); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.objects(SceneField::Parent))[4]), 1); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.objects(2))[1]), 6); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedShort>(scene.objects(3))[0]), 2); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableObjects(SceneField::Transformation))[2]), 3); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableObjects(SceneField::Parent))[4]), 1); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableObjects(2))[1]), 6); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedShort>(scene.mutableObjects(3))[0]), 2); + + /* Typeless field access by name with a cast later */ + CORRADE_COMPARE(scene.field(SceneField::Transformation).size()[0], 5); + CORRADE_COMPARE(scene.field(SceneField::Parent).size()[0], 5); + CORRADE_COMPARE(scene.field(SceneField::Mesh).size()[0], 2); + CORRADE_COMPARE(scene.field(sceneFieldCustom(37)).size()[0], 2); + CORRADE_COMPARE(scene.mutableField(SceneField::Transformation).size()[0], 5); + CORRADE_COMPARE(scene.mutableField(SceneField::Parent).size()[0], 5); + CORRADE_COMPARE(scene.mutableField(SceneField::Mesh).size()[0], 2); + CORRADE_COMPARE(scene.mutableField(sceneFieldCustom(37)).size()[0], 2); + CORRADE_COMPARE((Containers::arrayCast<1, const Matrix4>(scene.field(SceneField::Transformation))[2]), Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE((Containers::arrayCast<1, const Int>(scene.field(SceneField::Parent))[4]), -1); + CORRADE_COMPARE((Containers::arrayCast<1, const UnsignedByte>(scene.field(SceneField::Mesh))[1]), 7); + CORRADE_COMPARE((Containers::arrayCast<1, const Vector2>(scene.field(sceneFieldCustom(37)))[0]), (Vector2{37.5f, 1.5f})); + CORRADE_COMPARE((Containers::arrayCast<1, Matrix4>(scene.mutableField(SceneField::Transformation))[2]), Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE((Containers::arrayCast<1, Int>(scene.mutableField(SceneField::Parent))[4]), -1); + CORRADE_COMPARE((Containers::arrayCast<1, UnsignedByte>(scene.mutableField(SceneField::Mesh))[1]), 7); + CORRADE_COMPARE((Containers::arrayCast<1, Vector2>(scene.mutableField(sceneFieldCustom(37)))[0]), (Vector2{37.5f, 1.5f})); + + /* Typed object access by name */ + CORRADE_COMPARE(scene.objects(SceneField::Transformation).size(), 5); + CORRADE_COMPARE(scene.objects(SceneField::Parent).size(), 5); + CORRADE_COMPARE(scene.objects(SceneField::Mesh).size(), 2); + CORRADE_COMPARE(scene.objects(sceneFieldCustom(37)).size(), 2); + CORRADE_COMPARE(scene.mutableObjects(SceneField::Transformation).size(), 5); + CORRADE_COMPARE(scene.mutableObjects(SceneField::Parent).size(), 5); + CORRADE_COMPARE(scene.mutableObjects(SceneField::Mesh).size(), 2); + CORRADE_COMPARE(scene.mutableObjects(sceneFieldCustom(37)).size(), 2); + CORRADE_COMPARE(scene.objects(SceneField::Transformation)[2], 3); + CORRADE_COMPARE(scene.objects(SceneField::Parent)[4], 1); + CORRADE_COMPARE(scene.objects(SceneField::Mesh)[1], 6); + CORRADE_COMPARE(scene.objects(sceneFieldCustom(37))[0], 2); + CORRADE_COMPARE(scene.mutableObjects(SceneField::Transformation)[2], 3); + CORRADE_COMPARE(scene.mutableObjects(SceneField::Parent)[4], 1); + CORRADE_COMPARE(scene.mutableObjects(SceneField::Mesh)[1], 6); + CORRADE_COMPARE(scene.mutableObjects(sceneFieldCustom(37))[0], 2); + + /* Typed field access by name */ + CORRADE_COMPARE(scene.field(SceneField::Transformation).size(), 5); + CORRADE_COMPARE(scene.field(SceneField::Parent).size(), 5); + CORRADE_COMPARE(scene.field(SceneField::Mesh).size(), 2); + CORRADE_COMPARE(scene.field(sceneFieldCustom(37)).size()[0], 2); + CORRADE_COMPARE(scene.field(sceneFieldCustom(37)).size()[1], 2); + CORRADE_COMPARE(scene.mutableField(SceneField::Transformation).size(), 5); + CORRADE_COMPARE(scene.mutableField(SceneField::Parent).size(), 5); + CORRADE_COMPARE(scene.mutableField(SceneField::Mesh).size(), 2); + CORRADE_COMPARE(scene.mutableField(sceneFieldCustom(37)).size()[0], 2); + CORRADE_COMPARE(scene.mutableField(sceneFieldCustom(37)).size()[1], 2); + CORRADE_COMPARE(scene.field(SceneField::Transformation)[2], Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE(scene.field(SceneField::Parent)[4], -1); + CORRADE_COMPARE(scene.field(SceneField::Mesh)[1], 7); + CORRADE_COMPARE(scene.field(sceneFieldCustom(37))[0][0], 37.5f); + CORRADE_COMPARE(scene.field(sceneFieldCustom(37))[0][1], 1.5f); + CORRADE_COMPARE(scene.mutableField(SceneField::Transformation)[2], Matrix4::translation(Vector3::zAxis(5.0f))); + CORRADE_COMPARE(scene.mutableField(SceneField::Parent)[4], -1); + CORRADE_COMPARE(scene.mutableField(SceneField::Mesh)[1], 7); + CORRADE_COMPARE(scene.mutableField(sceneFieldCustom(37))[0][0], 37.5f); + CORRADE_COMPARE(scene.mutableField(sceneFieldCustom(37))[0][1], 1.5f); +} + +void SceneDataTest::constructZeroFields() { + int importerState; + SceneData scene{SceneObjectType::UnsignedShort, 37563, nullptr, {}, &importerState}; + CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_VERIFY(scene.fieldData().empty()); + CORRADE_COMPARE(static_cast(scene.data()), nullptr); + CORRADE_COMPARE(static_cast(scene.mutableData()), nullptr); + CORRADE_COMPARE(scene.importerState(), &importerState); + CORRADE_COMPARE(scene.objectCount(), 37563); + CORRADE_COMPARE(scene.objectType(), SceneObjectType::UnsignedShort); + CORRADE_COMPARE(scene.fieldCount(), 0); +} - CORRADE_COMPARE(data.children2D(), (std::vector{0, 1, 4})); - CORRADE_COMPARE(data.children3D(), (std::vector{2, 5})); - CORRADE_COMPARE(data.importerState(), &a); +void SceneDataTest::constructZeroObjects() { + int importerState; + SceneFieldData meshes{SceneField::Mesh, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::UnsignedShort, nullptr}; + SceneFieldData materials{SceneField::MeshMaterial, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::UnsignedInt, nullptr}; + SceneData scene{SceneObjectType::UnsignedInt, 0, nullptr, {meshes, materials}, &importerState}; + CORRADE_COMPARE(scene.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_VERIFY(!scene.fieldData().empty()); + CORRADE_COMPARE(static_cast(scene.data()), nullptr); + CORRADE_COMPARE(static_cast(scene.mutableData()), nullptr); + CORRADE_COMPARE(scene.importerState(), &importerState); + CORRADE_COMPARE(scene.objectCount(), 0); + CORRADE_COMPARE(scene.objectType(), SceneObjectType::UnsignedInt); + CORRADE_COMPARE(scene.fieldCount(), 2); + + /* Field property access by name */ + CORRADE_COMPARE(scene.fieldType(SceneField::Mesh), SceneFieldType::UnsignedShort); + CORRADE_COMPARE(scene.fieldType(SceneField::MeshMaterial), SceneFieldType::UnsignedInt); + CORRADE_COMPARE(scene.fieldSize(SceneField::Mesh), 0); + CORRADE_COMPARE(scene.fieldSize(SceneField::MeshMaterial), 0); + CORRADE_COMPARE(scene.objects(SceneField::Mesh).data(), nullptr); + CORRADE_COMPARE(scene.objects(SceneField::MeshMaterial).data(), nullptr); +} + +void SceneDataTest::constructNotOwned() { + auto&& instanceData = NotOwnedData[testCaseInstanceId()]; + setTestCaseDescription(instanceData.name); + + struct Data { + UnsignedShort object; + UnsignedByte mesh; + } data[]{ + {0, 2}, + {1, 1}, + {2, 0} + }; + + int importerState; + SceneFieldData mesh{SceneField::Mesh, + Containers::stridedArrayView(data).slice(&Data::object), + Containers::stridedArrayView(data).slice(&Data::mesh)}; + SceneData scene{SceneObjectType::UnsignedShort, 7, instanceData.dataFlags, Containers::arrayView(data), {mesh}, &importerState}; + + CORRADE_COMPARE(scene.dataFlags(), instanceData.dataFlags); + CORRADE_COMPARE(static_cast(scene.data()), +data); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(static_cast(scene.mutableData()), +data); + CORRADE_COMPARE(scene.objectCount(), 7); + CORRADE_COMPARE(scene.objectType(), SceneObjectType::UnsignedShort); + CORRADE_COMPARE(scene.fieldCount(), 1); + CORRADE_COMPARE(scene.importerState(), &importerState); + + CORRADE_COMPARE(scene.objects(0).size(), 3); + CORRADE_COMPARE(scene.objects(0)[2], 2); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(scene.mutableObjects(0)[2], 2); + + CORRADE_COMPARE(scene.field(0).size(), 3); + CORRADE_COMPARE(scene.field(0)[2], 0); + if(instanceData.dataFlags & DataFlag::Mutable) + CORRADE_COMPARE(scene.mutableField(0)[2], 0); +} + +void SceneDataTest::constructDuplicateField() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + /* Builtin fields are checked using a bitfield, as they have monotonic + numbering */ + SceneFieldData meshes{SceneField::Mesh, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::UnsignedShort, nullptr}; + SceneFieldData materials{SceneField::MeshMaterial, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::UnsignedInt, nullptr}; + SceneFieldData meshesAgain{SceneField::Mesh, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::UnsignedInt, nullptr}; + + std::ostringstream out; + Error redirectError{&out}; + SceneData scene{SceneObjectType::UnsignedInt, 0, nullptr, {meshes, materials, meshesAgain}}; + CORRADE_COMPARE(out.str(), "Trade::SceneData: duplicate field Trade::SceneField::Mesh\n"); +} + +void SceneDataTest::constructDuplicateCustomField() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + /* These are checked in an O(n^2) way, separately from builtin fields. + Can't use a bitfield since the field index can be anything. */ + SceneFieldData customA{sceneFieldCustom(37), SceneObjectType::UnsignedInt, nullptr, SceneFieldType::UnsignedShort, nullptr}; + SceneFieldData customB{sceneFieldCustom(1038576154), SceneObjectType::UnsignedInt, nullptr, SceneFieldType::UnsignedInt, nullptr}; + SceneFieldData customAAgain{sceneFieldCustom(37), SceneObjectType::UnsignedInt, nullptr, SceneFieldType::UnsignedInt, nullptr}; + + std::ostringstream out; + Error redirectError{&out}; + SceneData scene{SceneObjectType::UnsignedInt, 0, nullptr, {customA, customB, customAAgain}}; + CORRADE_COMPARE(out.str(), "Trade::SceneData: duplicate field Trade::SceneField::Custom(37)\n"); +} + +void SceneDataTest::constructInconsistentObjectType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneFieldData meshes{SceneField::Mesh, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::UnsignedShort, nullptr}; + SceneFieldData materials{SceneField::MeshMaterial, SceneObjectType::UnsignedShort, nullptr, SceneFieldType::UnsignedInt, nullptr}; + + std::ostringstream out; + Error redirectError{&out}; + SceneData scene{SceneObjectType::UnsignedInt, 0, nullptr, {meshes, materials}}; + CORRADE_COMPARE(out.str(), "Trade::SceneData: inconsistent object type, got Trade::SceneObjectType::UnsignedShort for field 1 but expected Trade::SceneObjectType::UnsignedInt\n"); +} + +void SceneDataTest::constructObjectDataNotContained() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Containers::Array data{reinterpret_cast(0xbadda9), 10, [](char*, std::size_t){}}; + Containers::ArrayView dataIn{reinterpret_cast(0xbadda9), 5}; + Containers::ArrayView dataSlightlyOut{reinterpret_cast(0xbaddaa), 5}; + Containers::ArrayView dataOut{reinterpret_cast(0xdead), 5}; + + std::ostringstream out; + Error redirectError{&out}; + /* First a "slightly off" view that exceeds the original by one byte */ + SceneData{SceneObjectType::UnsignedShort, 5, {}, data, { + SceneFieldData{SceneField::Mesh, dataSlightlyOut, dataIn} + }}; + /* Second a view that's in a completely different location */ + SceneData{SceneObjectType::UnsignedShort, 5, {}, data, { + SceneFieldData{SceneField::MeshMaterial, dataIn, dataIn}, + SceneFieldData{SceneField::Mesh, dataOut, dataIn} + }}; + /* Verify the owning constructor does the checks as well */ + SceneData{SceneObjectType::UnsignedShort, 5, std::move(data), { + SceneFieldData{SceneField::MeshMaterial, dataIn, dataIn}, + SceneFieldData{SceneField::Mesh, dataOut, dataIn} + }}; + /* And if we have no data at all, it doesn't try to dereference them but + still checks properly */ + SceneData{SceneObjectType::UnsignedShort, 5, nullptr, { + SceneFieldData{SceneField::Mesh, dataOut, dataIn} + }}; + /* Finally, offset-only fields with a different message */ + SceneData{SceneObjectType::UnsignedByte, 6, Containers::Array{24}, { + SceneFieldData{SceneField::Mesh, 6, SceneObjectType::UnsignedByte, 4, 4, SceneFieldType::UnsignedByte, 0, 4} + }}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: object data [0xbaddaa:0xbaddb4] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: object data [0xdead:0xdeb7] of field 1 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: object data [0xdead:0xdeb7] of field 1 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: object data [0xdead:0xdeb7] of field 0 are not contained in passed data array [0x0:0x0]\n" + + "Trade::SceneData: offset-only object data of field 0 span 25 bytes but passed data array has only 24\n"); +} + +void SceneDataTest::constructFieldDataNotContained() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + /* Mostly the same as constructObjectDataNotContained() with object and + field views swapped, and added checks for array fields */ + + Containers::Array data{reinterpret_cast(0xbadda9), 10, [](char*, std::size_t){}}; + Containers::ArrayView dataIn{reinterpret_cast(0xbadda9), 5}; + Containers::ArrayView dataSlightlyOut{reinterpret_cast(0xbaddaa), 5}; + Containers::ArrayView dataOut{reinterpret_cast(0xdead), 5}; + + std::ostringstream out; + Error redirectError{&out}; + /* First a "slightly off" view that exceeds the original by one byte */ + SceneData{SceneObjectType::UnsignedShort, 5, {}, data, { + SceneFieldData{SceneField::Mesh, dataIn, dataSlightlyOut} + }}; + /* Second a view that's in a completely different location */ + SceneData{SceneObjectType::UnsignedShort, 5, {}, data, { + SceneFieldData{SceneField::MeshMaterial, dataIn, dataIn}, + SceneFieldData{SceneField::Mesh, dataIn, dataOut} + }}; + /* Verify array size is taken into account as well. If not, the data would + span only 7 bytes out of 10 (instead of 12), which is fine. */ + SceneData{SceneObjectType::UnsignedShort, 5, {}, data, { + SceneFieldData{sceneFieldCustom(37), dataIn.prefix(2), Containers::StridedArrayView2D{Containers::ArrayView{reinterpret_cast(0xbadda9), 12}, {2, 6}}} + }}; + /* Verify the owning constructor does the checks as well */ + SceneData{SceneObjectType::UnsignedShort, 5, std::move(data), { + SceneFieldData{SceneField::MeshMaterial, dataIn, dataIn}, + SceneFieldData{SceneField::Mesh, dataIn, dataOut} + }}; + /* Not checking for nullptr data, since that got checked for object view + already and there's no way to trigger it for fields */ + /* Finally, offset-only fields with a different message */ + SceneData{SceneObjectType::UnsignedShort, 6, Containers::Array{24}, { + SceneFieldData{SceneField::Mesh, 6, SceneObjectType::UnsignedShort, 0, 4, SceneFieldType::UnsignedByte, 4, 4} + }}; + /* This again spans 21 bytes if array size isn't taken into account, and 25 + if it is */ + SceneData{SceneObjectType::UnsignedShort, 5, Containers::Array{24}, { + SceneFieldData{sceneFieldCustom(37), 5, SceneObjectType::UnsignedShort, 0, 5, SceneFieldType::UnsignedByte, 0, 5, 5} + }}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: field data [0xbaddaa:0xbaddb4] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: field data [0xdead:0xdeb7] of field 1 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: field data [0xbadda9:0xbaddb5] of field 0 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + "Trade::SceneData: field data [0xdead:0xdeb7] of field 1 are not contained in passed data array [0xbadda9:0xbaddb3]\n" + + "Trade::SceneData: offset-only field data of field 0 span 25 bytes but passed data array has only 24\n" + "Trade::SceneData: offset-only field data of field 0 span 25 bytes but passed data array has only 24\n"); +} + +void SceneDataTest::constructObjectTypeTooSmall() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + /* This is fine */ + SceneData{SceneObjectType::UnsignedByte, 0xff, nullptr, {}}; + SceneData{SceneObjectType::UnsignedShort, 0xffff, nullptr, {}}; + SceneData{SceneObjectType::UnsignedInt, 0xffffffffu, nullptr, {}}; + + std::ostringstream out; + Error redirectError{&out}; + SceneData{SceneObjectType::UnsignedByte, 0x100, nullptr, {}}; + SceneData{SceneObjectType::UnsignedShort, 0x10000, nullptr, {}}; + SceneData{SceneObjectType::UnsignedInt, 0x100000000ull, nullptr, {}}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: Trade::SceneObjectType::UnsignedByte is too small for 256 objects\n" + "Trade::SceneData: Trade::SceneObjectType::UnsignedShort is too small for 65536 objects\n" + "Trade::SceneData: Trade::SceneObjectType::UnsignedInt is too small for 4294967296 objects\n"); +} + +void SceneDataTest::constructNotOwnedFlagOwned() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + const char data[32]{}; + + std::ostringstream out; + Error redirectError{&out}; + SceneData{SceneObjectType::UnsignedByte, 5, DataFlag::Owned, Containers::arrayView(data), {}}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: can't construct with non-owned data but Trade::DataFlag::Owned\n"); +} + +void SceneDataTest::constructMismatchedTRSViews() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Containers::ArrayView data{reinterpret_cast(0xcafe0000), + /* Three entries, each having a 2D TRS and 3 object IDs */ + 3*(24 + 12)}; + Containers::ArrayView translationObjectData{ + reinterpret_cast(data.data()), 3}; + Containers::ArrayView translationFieldData{ + reinterpret_cast(data.data() + 0x0c), 3}; + Containers::ArrayView rotationObjectData{ + reinterpret_cast(data.data() + 0x24), 3}; + Containers::ArrayView rotationFieldData{ + reinterpret_cast(data.data() + 0x30), 3}; + Containers::ArrayView scalingObjectData{ + reinterpret_cast(data.data() + 0x48), 3}; + Containers::ArrayView scalingFieldData{ + reinterpret_cast(data.data() + 0x54), 3}; + + SceneFieldData translations{SceneField::Translation, translationObjectData, translationFieldData}; + SceneFieldData rotationsDifferent{SceneField::Rotation, rotationObjectData, rotationFieldData}; + SceneFieldData scalingsDifferent{SceneField::Scaling, scalingObjectData, scalingFieldData}; + SceneFieldData rotationsSameButLess{SceneField::Rotation, translationObjectData.except(1), rotationFieldData.except(1)}; + SceneFieldData scalingsSameButLess{SceneField::Scaling, translationObjectData.except(2), scalingFieldData.except(2)}; + + /* Test that all pairs get checked */ + std::ostringstream out; + Error redirectError{&out}; + SceneData{SceneObjectType::UnsignedInt, 3, {}, data, {translations, rotationsDifferent}}; + SceneData{SceneObjectType::UnsignedInt, 3, {}, data, {translations, scalingsDifferent}}; + SceneData{SceneObjectType::UnsignedInt, 3, {}, data, {rotationsDifferent, scalingsDifferent}}; + SceneData{SceneObjectType::UnsignedInt, 3, {}, data, {translations, rotationsSameButLess}}; + SceneData{SceneObjectType::UnsignedInt, 3, {}, data, {translations, scalingsSameButLess}}; + SceneData{SceneObjectType::UnsignedInt, 3, {}, data, {rotationsSameButLess, scalingsSameButLess}}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: Trade::SceneField::Rotation object data [0xcafe0024:0xcafe0030] is different from Trade::SceneField::Translation object data [0xcafe0000:0xcafe000c]\n" + "Trade::SceneData: Trade::SceneField::Scaling object data [0xcafe0048:0xcafe0054] is different from Trade::SceneField::Translation object data [0xcafe0000:0xcafe000c]\n" + "Trade::SceneData: Trade::SceneField::Scaling object data [0xcafe0048:0xcafe0054] is different from Trade::SceneField::Rotation object data [0xcafe0024:0xcafe0030]\n" + "Trade::SceneData: Trade::SceneField::Rotation object data [0xcafe0000:0xcafe0008] is different from Trade::SceneField::Translation object data [0xcafe0000:0xcafe000c]\n" + "Trade::SceneData: Trade::SceneField::Scaling object data [0xcafe0000:0xcafe0004] is different from Trade::SceneField::Translation object data [0xcafe0000:0xcafe000c]\n" + "Trade::SceneData: Trade::SceneField::Scaling object data [0xcafe0000:0xcafe0004] is different from Trade::SceneField::Rotation object data [0xcafe0000:0xcafe0008]\n"); +} + +void SceneDataTest::constructMismatchedMeshMaterialView() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + Containers::ArrayView data{reinterpret_cast(0xcafe0000), + /* Three entries, each having mesh/material ID and 2 object IDs */ + 3*(8 + 8)}; + Containers::ArrayView meshObjectData{ + reinterpret_cast(data.data()), 3}; + Containers::ArrayView meshFieldData{ + reinterpret_cast(data.data() + 0x0c), 3}; + Containers::ArrayView meshMaterialObjectData{ + reinterpret_cast(data.data() + 0x18), 3}; + Containers::ArrayView meshMaterialFieldData{ + reinterpret_cast(data.data() + 0x24), 3}; + + SceneFieldData meshes{SceneField::Mesh, meshObjectData, meshFieldData}; + SceneFieldData meshMaterialsDifferent{SceneField::MeshMaterial, meshMaterialObjectData, meshMaterialFieldData}; + SceneFieldData meshMaterialsSameButLess{SceneField::MeshMaterial, meshObjectData.except(1), meshMaterialFieldData.except(1)}; + + std::ostringstream out; + Error redirectError{&out}; + SceneData{SceneObjectType::UnsignedInt, 3, {}, data, {meshes, meshMaterialsDifferent}}; + SceneData{SceneObjectType::UnsignedInt, 3, {}, data, {meshes, meshMaterialsSameButLess}}; + CORRADE_COMPARE(out.str(), + "Trade::SceneData: Trade::SceneField::MeshMaterial object data [0xcafe0018:0xcafe0024] is different from Trade::SceneField::Mesh object data [0xcafe0000:0xcafe000c]\n" + "Trade::SceneData: Trade::SceneField::MeshMaterial object data [0xcafe0000:0xcafe0008] is different from Trade::SceneField::Mesh object data [0xcafe0000:0xcafe000c]\n"); } void SceneDataTest::constructCopy() { @@ -59,27 +1570,1477 @@ void SceneDataTest::constructCopy() { } void SceneDataTest::constructMove() { - const int a{}; - SceneData data{{0, 1, 4}, {2, 5}, &a}; + struct Mesh { + UnsignedShort object; + UnsignedInt mesh; + }; - SceneData b{std::move(data)}; + Containers::Array data{NoInit, 3*sizeof(Mesh)}; + auto meshData = Containers::arrayCast(data); + meshData[0] = {0, 2}; + meshData[1] = {73, 1}; + meshData[2] = {122, 2}; - CORRADE_COMPARE(b.children2D(), (std::vector{0, 1, 4})); - CORRADE_COMPARE(b.children3D(), (std::vector{2, 5})); - CORRADE_COMPARE(b.importerState(), &a); + int importerState; + SceneFieldData meshes{SceneField::Mesh, stridedArrayView(meshData).slice(&Mesh::object), stridedArrayView(meshData).slice(&Mesh::mesh)}; + SceneData a{SceneObjectType::UnsignedShort, 15, std::move(data), {meshes}, &importerState}; - const int c{}; - SceneData d{{1, 3}, {1, 4, 5}, &c}; - d = std::move(b); + SceneData b{std::move(a)}; + CORRADE_COMPARE(b.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(b.objectCount(), 15); + CORRADE_COMPARE(b.objectType(), SceneObjectType::UnsignedShort); + CORRADE_COMPARE(b.fieldCount(), 1); + CORRADE_COMPARE(b.importerState(), &importerState); + CORRADE_COMPARE(static_cast(b.data()), meshData.data()); + CORRADE_COMPARE(b.fieldName(0), SceneField::Mesh); + CORRADE_COMPARE(b.fieldType(0), SceneFieldType::UnsignedInt); + CORRADE_COMPARE(b.fieldSize(0), 3); + CORRADE_COMPARE(b.fieldArraySize(0), 0); + CORRADE_COMPARE(b.objects(0)[2], 122); + CORRADE_COMPARE(b.field(0)[2], 2); - CORRADE_COMPARE(d.children2D(), (std::vector{0, 1, 4})); - CORRADE_COMPARE(d.children3D(), (std::vector{2, 5})); - CORRADE_COMPARE(d.importerState(), &a); + SceneData c{SceneObjectType::UnsignedByte, 76, nullptr, {}}; + c = std::move(b); + CORRADE_COMPARE(c.dataFlags(), DataFlag::Owned|DataFlag::Mutable); + CORRADE_COMPARE(c.objectCount(), 15); + CORRADE_COMPARE(c.objectType(), SceneObjectType::UnsignedShort); + CORRADE_COMPARE(c.fieldCount(), 1); + CORRADE_COMPARE(c.importerState(), &importerState); + CORRADE_COMPARE(static_cast(c.data()), meshData.data()); + CORRADE_COMPARE(c.fieldName(0), SceneField::Mesh); + CORRADE_COMPARE(c.fieldType(0), SceneFieldType::UnsignedInt); + CORRADE_COMPARE(c.fieldSize(0), 3); + CORRADE_COMPARE(c.fieldArraySize(0), 0); + CORRADE_COMPARE(c.objects(0)[2], 122); + CORRADE_COMPARE(c.field(0)[2], 2); CORRADE_VERIFY(std::is_nothrow_move_constructible::value); CORRADE_VERIFY(std::is_nothrow_move_assignable::value); } +template struct NameTraits; +#define _c(format) template<> struct NameTraits { \ + static const char* name() { return #format; } \ + }; +_c(UnsignedByte) +_c(Byte) +_c(UnsignedShort) +_c(Short) +_c(UnsignedInt) +_c(Int) +_c(UnsignedLong) +_c(Long) +_c(Float) +_c(Double) +_c(Vector2) +_c(Vector2d) +_c(Vector3) +_c(Vector3d) +_c(Matrix3) +_c(Matrix3x3) +_c(Matrix3d) +_c(Matrix3x3d) +_c(Matrix4) +_c(Matrix4x4) +_c(Matrix4d) +_c(Matrix4x4d) +_c(Complex) +_c(Complexd) +_c(Quaternion) +_c(Quaterniond) +_c(DualComplex) +_c(DualComplexd) +_c(DualQuaternion) +_c(DualQuaterniond) +#undef _c + +template void SceneDataTest::objectsAsArrayByIndex() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + T object; + UnsignedByte mesh; + } fields[]{ + {T(15), 0}, + {T(37), 1}, + {T(44), 15} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{Implementation::sceneObjectTypeFor(), 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, Implementation::sceneObjectTypeFor(), nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + CORRADE_COMPARE_AS(scene.objectsAsArray(1), + Containers::arrayView({15, 37, 44}), + TestSuite::Compare::Container); +} + +template void SceneDataTest::objectsAsArrayByName() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + T object; + UnsignedByte mesh; + } fields[]{ + {T(15), 0}, + {T(37), 1}, + {T(44), 15} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{Implementation::sceneObjectTypeFor(), 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, Implementation::sceneObjectTypeFor(), nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + UnsignedInt expected[]{15, 37, 44}; + CORRADE_COMPARE_AS(arrayView(scene.objectsAsArray(SceneField::Mesh)), + Containers::arrayView(expected), + TestSuite::Compare::Container); + + /* Test Into() as well as it only shares a common helper with AsArray() but + has different top-level code paths */ + UnsignedInt out[3]; + scene.objectsInto(SceneField::Mesh, out); + CORRADE_COMPARE_AS(Containers::arrayView(out), + Containers::arrayView(expected), + TestSuite::Compare::Container); +} + +void SceneDataTest::objectsAsArrayLongType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedLong object; + UnsignedByte mesh; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedLong, 0x100000000ull, {}, fields, { + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + /* AsArray calls into IntoArray, which then has the assert, so this tests + both */ + std::ostringstream out; + Error redirectError{&out}; + scene.objectsAsArray(0); + scene.objectsAsArray(SceneField::Mesh); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::objectsInto(): indices for up to 4294967296 objects can't fit into a 32-bit type, access them directly via objects() instead\n" + "Trade::SceneData::objectsInto(): indices for up to 4294967296 objects can't fit into a 32-bit type, access them directly via objects() instead\n"); +} + +void SceneDataTest::objectsIntoArrayInvalidSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedByte mesh; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt destination[2]; + scene.objectsInto(0, destination); + scene.objectsInto(SceneField::Mesh, destination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::objectsInto(): expected a view with 3 elements but got 2\n" + "Trade::SceneData::objectsInto(): expected a view with 3 elements but got 2\n"); +} + +template void SceneDataTest::parentsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + UnsignedByte object; + T parent; + } fields[]{ + {0, T(15)}, + {1, T(-1)}, + {15, T(44)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedByte, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Mesh, SceneObjectType::UnsignedByte, nullptr, SceneFieldType::UnsignedInt, nullptr}, + SceneFieldData{SceneField::Parent, view.slice(&Field::object), view.slice(&Field::parent)} + }}; + CORRADE_COMPARE_AS(scene.parentsAsArray(), + Containers::arrayView({15, -1, 44}), + TestSuite::Compare::Container); +} + +#ifndef CORRADE_TARGET_32BIT +void SceneDataTest::parentsAsArrayLongType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedLong object; + Long parent; + }; + + Containers::Array data{nullptr, 0x100000000ull*sizeof(Field), [](char*, std::size_t) {}}; + Containers::StridedArrayView1D view = Containers::arrayCast(data); + + SceneData scene{SceneObjectType::UnsignedLong, 0x100000000ull, std::move(data), { + SceneFieldData{SceneField::Parent, view.slice(&Field::object), view.slice(&Field::parent)} + }}; + + /* AsArray calls into IntoArray, which then has the assert, so this tests + both */ + std::ostringstream out; + Error redirectError{&out}; + scene.parentsAsArray(); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::parentsInto(): parent indices for up to 4294967296 objects can't fit into a 32-bit type, access them directly via field() instead\n"); +} +#endif + +void SceneDataTest::parentsIntoArrayInvalidSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + Int parent; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Parent, view.slice(&Field::object), view.slice(&Field::parent)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + Int destination[2]; + scene.parentsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::parentsInto(): expected a view with 3 elements but got 2\n"); +} + +template void SceneDataTest::transformations2DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + typedef typename T::Type U; + + struct Transformation { + UnsignedInt object; + T transformation; + }; + + struct Component { + UnsignedInt object; + Vector2 translation; + Vector2 scaling; + }; + + Containers::StridedArrayView1D transformations; + Containers::StridedArrayView1D components; + Containers::Array data = Containers::ArrayTuple{ + {NoInit, 4, transformations}, + {NoInit, 2, components} + }; + transformations[0] = {1, T::translation({U(3.0), U(2.0)})}; + transformations[1] = {0, T::rotation(Math::Deg(35.0))}; + transformations[2] = {4, T::translation({U(1.5), U(2.5)})* + T::rotation(Math::Deg(-15.0))}; + transformations[3] = {5, T::rotation(Math::Deg(-15.0))* + T::translation({U(1.5), U(2.5)})}; + /* Object number 4 additionally has a scaling component (which isn't + representable with dual complex numbers). It currently doesn't get added + to the transformations returned from transformations2DInto() but that + may change in the future for dual complex numbers). The translation + component is then *assumed* to be equivalent to what's stored in the + Transformation field and so applied neither. Here it's different, and + that shouldn't affect anything. */ + components[0] = {4, {-1.5f, -2.5f}, {2.0f, 5.0f}}; + /* This is deliberately an error -- specifying a TRS for an object that + doesn't have a Transformation field. Since there's no fast way to check + for those and error/warn on those, they get just ignored. */ + components[1] = {2, {3.5f, -1.0f}, {1.0f, 1.5f}}; + + SceneData scene{SceneObjectType::UnsignedInt, 6, std::move(data), { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Transformation, + transformations.slice(&Transformation::object), + transformations.slice(&Transformation::transformation)}, + SceneFieldData{SceneField::Translation, + components.slice(&Component::object), + components.slice(&Component::translation)}, + SceneFieldData{SceneField::Scaling, + components.slice(&Component::object), + components.slice(&Component::scaling)}, + }}; + + Matrix3 expected[]{ + Matrix3::translation({3.0f, 2.0f}), + Matrix3::rotation(35.0_degf), + Matrix3::translation({1.5f, 2.5f})*Matrix3::rotation(-15.0_degf), + Matrix3::rotation(-15.0_degf)*Matrix3::translation({1.5f, 2.5f}) + }; + CORRADE_COMPARE_AS(arrayView(scene.transformations2DAsArray()), + Containers::arrayView(expected), + TestSuite::Compare::Container); + + /* Test Into() as well as it only shares a common helper with AsArray() but + has different top-level code paths */ + Matrix3 out[4]; + scene.transformations2DInto(out); + CORRADE_COMPARE_AS(Containers::arrayView(out), + Containers::arrayView(expected), + TestSuite::Compare::Container); +} + +template void SceneDataTest::transformations2DAsArrayTRS() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + UnsignedInt object; + Math::Vector2 translation; + Math::Complex rotation; + Math::Vector2 scaling; + } fields[]{ + {1, {T(3.0), T(2.0)}, + {}, + {T(1.0), T(1.0)}}, + {0, {}, + Math::Complex::rotation(Math::Deg{T(35.0)}), + {T(1.0), T(1.0)}}, + {2, {}, /* Identity transformation here */ + {}, + {T(1.0), T(1.0)}}, + {4, {}, + {}, + {T(2.0), T(1.0)}}, + {7, {T(1.5), T(2.5)}, + Math::Complex::rotation(Math::Deg{T(-15.0)}), + {T(-0.5), T(4.0)}}, + }; + + Containers::StridedArrayView1D view = fields; + + SceneFieldData translation{SceneField::Translation, + view.slice(&Field::object), + view.slice(&Field::translation)}; + SceneFieldData rotation{SceneField::Rotation, + view.slice(&Field::object), + view.slice(&Field::rotation)}; + SceneFieldData scaling{SceneField::Scaling, + view.slice(&Field::object), + view.slice(&Field::scaling)}; + + /* Just one of translation / rotation / scaling */ + { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + translation + }}; + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), Containers::arrayView({ + Matrix3::translation({3.0f, 2.0f}), + Matrix3{Math::IdentityInit}, + Matrix3{Math::IdentityInit}, + Matrix3{Math::IdentityInit}, + Matrix3::translation({1.5f, 2.5f}) + }), TestSuite::Compare::Container); + } { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + rotation + }}; + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), Containers::arrayView({ + Matrix3{Math::IdentityInit}, + Matrix3::rotation(35.0_degf), + Matrix3{Math::IdentityInit}, + Matrix3{Math::IdentityInit}, + Matrix3::rotation(-15.0_degf) + }), TestSuite::Compare::Container); + } { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + scaling + }}; + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), Containers::arrayView({ + Matrix3{Math::IdentityInit}, + Matrix3{Math::IdentityInit}, + Matrix3{Math::IdentityInit}, + Matrix3::scaling({2.0f, 1.0f}), + Matrix3::scaling({-0.5f, 4.0f}) + }), TestSuite::Compare::Container); + } + + /* Pairs */ + { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + translation, + rotation + }}; + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), Containers::arrayView({ + Matrix3::translation({3.0f, 2.0f}), + Matrix3::rotation(35.0_degf), + Matrix3{Math::IdentityInit}, + Matrix3{Math::IdentityInit}, + Matrix3::translation({1.5f, 2.5f})*Matrix3::rotation({-15.0_degf}) + }), TestSuite::Compare::Container); + } { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + translation, + scaling + }}; + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), Containers::arrayView({ + Matrix3::translation({3.0f, 2.0f}), + Matrix3{Math::IdentityInit}, + Matrix3{Math::IdentityInit}, + Matrix3::scaling({2.0f, 1.0f}), + Matrix3::translation({1.5f, 2.5f})*Matrix3::scaling({-0.5f, 4.0f}) + }), TestSuite::Compare::Container); + } { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + rotation, + scaling + }}; + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), Containers::arrayView({ + Matrix3{Math::IdentityInit}, + Matrix3::rotation(35.0_degf), + Matrix3{Math::IdentityInit}, + Matrix3::scaling({2.0f, 1.0f}), + Matrix3::rotation({-15.0_degf})*Matrix3::scaling({-0.5f, 4.0f}) + }), TestSuite::Compare::Container); + } + + /* All */ + { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + translation, + rotation, + scaling + }}; + CORRADE_COMPARE_AS(scene.transformations2DAsArray(), Containers::arrayView({ + Matrix3::translation({3.0f, 2.0f}), + Matrix3::rotation(35.0_degf), + Matrix3{Math::IdentityInit}, + Matrix3::scaling({2.0f, 1.0f}), + Matrix3::translation({1.5f, 2.5f})*Matrix3::rotation({-15.0_degf})*Matrix3::scaling({-0.5f, 4.0f}) + }), TestSuite::Compare::Container); + } +} + +template void SceneDataTest::transformations2DAsArrayBut3DType() { + setTestCaseTemplateName(NameTraits::name()); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneData scene{SceneObjectType::UnsignedInt, 0, nullptr, { + SceneFieldData{SceneField::Transformation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor::type(), nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.transformations2DAsArray(); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Trade::SceneData::transformations2DInto(): field has a 3D transformation type Trade::SceneFieldType::{}\n", NameTraits::name())); +} + +template void SceneDataTest::transformations2DAsArrayBut3DTypeTRS() { + setTestCaseTemplateName(NameTraits::name()); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneData translation{SceneObjectType::UnsignedInt, 0, nullptr, { + SceneFieldData{SceneField::Translation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + }}; + SceneData rotation{SceneObjectType::UnsignedInt, 0, nullptr, { + SceneFieldData{SceneField::Rotation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + }}; + SceneData scaling{SceneObjectType::UnsignedInt, 0, nullptr, { + SceneFieldData{SceneField::Scaling, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + translation.transformations2DAsArray(); + rotation.transformations2DAsArray(); + scaling.transformations2DAsArray(); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Trade::SceneData::transformations2DInto(): field has a 3D translation type Trade::SceneFieldType::{0}\n" + "Trade::SceneData::transformations2DInto(): field has a 3D rotation type Trade::SceneFieldType::{1}\n" + "Trade::SceneData::transformations2DInto(): field has a 3D scaling type Trade::SceneFieldType::{0}\n", NameTraits>::name(), NameTraits>::name())); +} + +void SceneDataTest::transformations2DIntoArrayInvalidSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + Matrix3 transformation; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Transformation, view.slice(&Field::object), view.slice(&Field::transformation)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + Matrix3 destination[2]; + scene.transformations2DInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::transformations2DInto(): expected a view with 3 elements but got 2\n"); +} + +template void SceneDataTest::transformations3DAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + typedef typename T::Type U; + + struct Transformation { + UnsignedInt object; + T transformation; + }; + + struct Component { + UnsignedInt object; + Vector3 translation; + Vector3 scaling; + }; + + Containers::StridedArrayView1D transformations; + Containers::StridedArrayView1D components; + Containers::Array data = Containers::ArrayTuple{ + {NoInit, 4, transformations}, + {NoInit, 2, components} + }; + transformations[0] = {1, T::translation({U(3.0), U(2.0), U(-0.5)})}; + transformations[1] = {0, T::rotation(Math::Deg(35.0), + Math::Vector3::yAxis())}; + transformations[2] = {4, T::translation({U(1.5), U(2.5), U(0.75)})* + T::rotation(Math::Deg(-15.0), + Math::Vector3::xAxis())}; + transformations[3] = {5, T::rotation(Math::Deg(-15.0), + Math::Vector3::xAxis())* + T::translation({U(1.5), U(2.5), U(0.75)})}; + /* Object number 4 additionally has a scaling component (which isn't + representable with dual quaternions). It currently doesn't get added + to the transformations returned from transformations2DInto() but that + may change in the future for dual quaternions). The translation + component is then *assumed* to be equivalent to what's stored in the + Transformation field and so applied neither. Here it's different, and + that shouldn't affect anything. */ + components[0] = {4, {-1.5f, -2.5f, 5.5f}, {2.0f, 5.0f, 3.0f}}; + /* This is deliberately an error -- specifying a TRS for an object that + doesn't have a Transformation field. Since there's no fast way to check + for those and error/warn on those, they get just ignored. */ + components[1] = {2, {3.5f, -1.0f, 2.2f}, {1.0f, 1.5f, 1.0f}}; + + SceneData scene{SceneObjectType::UnsignedInt, 6, std::move(data), { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedInt, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Transformation, + transformations.slice(&Transformation::object), + transformations.slice(&Transformation::transformation)}, + SceneFieldData{SceneField::Translation, + components.slice(&Component::object), + components.slice(&Component::translation)}, + SceneFieldData{SceneField::Scaling, + components.slice(&Component::object), + components.slice(&Component::scaling)}, + }}; + + Matrix4 expected[]{ + Matrix4::translation({3.0f, 2.0f, -0.5f}), + Matrix4::rotationY(35.0_degf), + Matrix4::translation({1.5f, 2.5f, 0.75f})*Matrix4::rotationX(-15.0_degf), + Matrix4::rotationX(-15.0_degf)*Matrix4::translation({1.5f, 2.5f, 0.75f}) + }; + CORRADE_COMPARE_AS(arrayView(scene.transformations3DAsArray()), + Containers::arrayView(expected), + TestSuite::Compare::Container); + + /* Test Into() as well as it only shares a common helper with AsArray() but + has different top-level code paths */ + Matrix4 out[4]; + scene.transformations3DInto(out); + CORRADE_COMPARE_AS(Containers::arrayView(out), + Containers::arrayView(expected), + TestSuite::Compare::Container); +} + +template void SceneDataTest::transformations3DAsArrayTRS() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + UnsignedInt object; + Math::Vector3 translation; + Math::Quaternion rotation; + Math::Vector3 scaling; + } fields[]{ + {1, {T(3.0), T(2.0), T(1.0)}, + {}, + {T(1.0), T(1.0), T(1.0)}}, + {0, {}, + Math::Quaternion::rotation(Math::Deg{T(35.0)}, Math::Vector3::yAxis()), + {T(1.0), T(1.0), T(1.0)}}, + {2, {}, /* Identity transformation here */ + {}, + {T(1.0), T(1.0), T(1.0)}}, + {4, {}, + {}, + {T(2.0), T(1.0), T(0.0)}}, + {7, {T(1.5), T(2.5), T(3.5)}, + Math::Quaternion::rotation(Math::Deg{T(-15.0)}, Math::Vector3::xAxis()), + {T(-0.5), T(4.0), T(-16.0)}}, + }; + + Containers::StridedArrayView1D view = fields; + + SceneFieldData translation{SceneField::Translation, + view.slice(&Field::object), + view.slice(&Field::translation)}; + SceneFieldData rotation{SceneField::Rotation, + view.slice(&Field::object), + view.slice(&Field::rotation)}; + SceneFieldData scaling{SceneField::Scaling, + view.slice(&Field::object), + view.slice(&Field::scaling)}; + + /* Just one of translation / rotation / scaling */ + { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + translation + }}; + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), Containers::arrayView({ + Matrix4::translation({3.0f, 2.0, 1.0f}), + Matrix4{Math::IdentityInit}, + Matrix4{Math::IdentityInit}, + Matrix4{Math::IdentityInit}, + Matrix4::translation({1.5f, 2.5f, 3.5f}) + }), TestSuite::Compare::Container); + } { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + rotation + }}; + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), Containers::arrayView({ + Matrix4{Math::IdentityInit}, + Matrix4::rotationY(35.0_degf), + Matrix4{Math::IdentityInit}, + Matrix4{Math::IdentityInit}, + Matrix4::rotationX(-15.0_degf) + }), TestSuite::Compare::Container); + } { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + scaling + }}; + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), Containers::arrayView({ + Matrix4{Math::IdentityInit}, + Matrix4{Math::IdentityInit}, + Matrix4{Math::IdentityInit}, + Matrix4::scaling({2.0f, 1.0f, 0.0f}), + Matrix4::scaling({-0.5f, 4.0f, -16.0f}) + }), TestSuite::Compare::Container); + } + + /* Pairs */ + { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + translation, + rotation + }}; + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), Containers::arrayView({ + Matrix4::translation({3.0f, 2.0, 1.0f}), + Matrix4::rotationY(35.0_degf), + Matrix4{Math::IdentityInit}, + Matrix4{Math::IdentityInit}, + Matrix4::translation({1.5f, 2.5f, 3.5f})*Matrix4::rotationX(-15.0_degf) + }), TestSuite::Compare::Container); + } { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + translation, + scaling + }}; + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), Containers::arrayView({ + Matrix4::translation({3.0f, 2.0, 1.0f}), + Matrix4{Math::IdentityInit}, + Matrix4{Math::IdentityInit}, + Matrix4::scaling({2.0f, 1.0f, 0.0f}), + Matrix4::translation({1.5f, 2.5f, 3.5f})*Matrix4::scaling({-0.5f, 4.0f, -16.0f}) + }), TestSuite::Compare::Container); + } { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + rotation, + scaling + }}; + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), Containers::arrayView({ + Matrix4{Math::IdentityInit}, + Matrix4::rotationY(35.0_degf), + Matrix4{Math::IdentityInit}, + Matrix4::scaling({2.0f, 1.0f, 0.0f}), + Matrix4::rotationX({-15.0_degf})*Matrix4::scaling({-0.5f, 4.0f, -16.0f}) + }), TestSuite::Compare::Container); + } + + /* All */ + { + SceneData scene{SceneObjectType::UnsignedInt, 8, {}, fields, { + translation, + rotation, + scaling + }}; + CORRADE_COMPARE_AS(scene.transformations3DAsArray(), Containers::arrayView({ + Matrix4::translation({3.0f, 2.0, 1.0f}), + Matrix4::rotationY(35.0_degf), + Matrix4{Math::IdentityInit}, + Matrix4::scaling({2.0f, 1.0f, 0.0f}), + Matrix4::translation({1.5f, 2.5f, 3.5f})*Matrix4::rotationX({-15.0_degf})*Matrix4::scaling({-0.5f, 4.0f, -16.0f}) + }), TestSuite::Compare::Container); + } +} + +template void SceneDataTest::transformations3DAsArrayBut2DType() { + setTestCaseTemplateName(NameTraits::name()); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneData scene{SceneObjectType::UnsignedInt, 0, nullptr, { + SceneFieldData{SceneField::Transformation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor::type(), nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.transformations3DAsArray(); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Trade::SceneData::transformations3DInto(): field has a 2D transformation type Trade::SceneFieldType::{}\n", NameTraits::name())); +} + +template void SceneDataTest::transformations3DAsArrayBut2DTypeTRS() { + setTestCaseTemplateName(NameTraits::name()); + + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + SceneData translation{SceneObjectType::UnsignedInt, 0, nullptr, { + SceneFieldData{SceneField::Translation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + }}; + SceneData rotation{SceneObjectType::UnsignedInt, 0, nullptr, { + SceneFieldData{SceneField::Rotation, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + }}; + SceneData scaling{SceneObjectType::UnsignedInt, 0, nullptr, { + SceneFieldData{SceneField::Scaling, SceneObjectType::UnsignedInt, nullptr, Implementation::SceneFieldTypeFor>::type(), nullptr} + }}; + + std::ostringstream out; + Error redirectError{&out}; + translation.transformations3DAsArray(); + rotation.transformations3DAsArray(); + scaling.transformations3DAsArray(); + CORRADE_COMPARE(out.str(), Utility::formatString( + "Trade::SceneData::transformations3DInto(): field has a 2D translation type Trade::SceneFieldType::{0}\n" + "Trade::SceneData::transformations3DInto(): field has a 2D rotation type Trade::SceneFieldType::{1}\n" + "Trade::SceneData::transformations3DInto(): field has a 2D scaling type Trade::SceneFieldType::{0}\n", NameTraits>::name(), NameTraits>::name())); +} + +void SceneDataTest::transformations3DIntoArrayInvalidSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + Matrix4 transformation; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Transformation, view.slice(&Field::object), view.slice(&Field::transformation)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + Matrix4 destination[2]; + scene.transformations3DInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::transformations3DInto(): expected a view with 3 elements but got 2\n"); +} + +template void SceneDataTest::meshesAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + UnsignedByte object; + T mesh; + } fields[3]{ + {0, T(15)}, + {1, T(37)}, + {15, T(44)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedByte, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + UnsignedInt expected[]{15, 37, 44}; + CORRADE_COMPARE_AS(arrayView(scene.meshesAsArray()), + Containers::arrayView(expected), + TestSuite::Compare::Container); + + /* Test Into() as well as it only shares a common helper with AsArray() but + has different top-level code paths */ + UnsignedInt out[3]; + scene.meshesInto(out); + CORRADE_COMPARE_AS(Containers::arrayView(out), + Containers::arrayView(expected), + TestSuite::Compare::Container); +} + +void SceneDataTest::meshesIntoArrayInvalidSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt destination[2]; + scene.meshesInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::meshesInto(): expected a view with 3 elements but got 2\n"); +} + +template void SceneDataTest::meshMaterialsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + UnsignedByte object; + T meshMaterial; + } fields[]{ + {0, T(15)}, + {1, T(37)}, + {15, T(44)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedByte, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::MeshMaterial, view.slice(&Field::object), view.slice(&Field::meshMaterial)} + }}; + + UnsignedInt expected[]{15, 37, 44}; + CORRADE_COMPARE_AS(arrayView(scene.meshMaterialsAsArray()), + Containers::arrayView(expected), + TestSuite::Compare::Container); + + /* Test Into() as well as it only shares a common helper with AsArray() but + has different top-level code paths */ + UnsignedInt out[3]; + scene.meshMaterialsInto(out); + CORRADE_COMPARE_AS(Containers::arrayView(out), + Containers::arrayView(expected), + TestSuite::Compare::Container); +} + +void SceneDataTest::meshMaterialsIntoArrayInvalidSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt meshMaterial; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::MeshMaterial, view.slice(&Field::object), view.slice(&Field::meshMaterial)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt destination[2]; + scene.meshMaterialsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::meshMaterialsInto(): expected a view with 3 elements but got 2\n"); +} + +template void SceneDataTest::lightsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + UnsignedByte object; + T light; + } fields[]{ + {0, T(15)}, + {1, T(37)}, + {15, T(44)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedByte, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Light, view.slice(&Field::object), view.slice(&Field::light)} + }}; + + UnsignedInt expected[]{15, 37, 44}; + CORRADE_COMPARE_AS(arrayView(scene.lightsAsArray()), + Containers::arrayView(expected), + TestSuite::Compare::Container); + + /* Test Into() as well as it only shares a common helper with AsArray() but + has different top-level code paths */ + UnsignedInt out[3]; + scene.lightsInto(out); + CORRADE_COMPARE_AS(Containers::arrayView(out), + Containers::arrayView(expected), + TestSuite::Compare::Container); +} + +void SceneDataTest::lightsIntoArrayInvalidSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt light; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Light, view.slice(&Field::object), view.slice(&Field::light)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt destination[2]; + scene.lightsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::lightsInto(): expected a view with 3 elements but got 2\n"); +} + +template void SceneDataTest::camerasAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + UnsignedByte object; + T camera; + } fields[]{ + {0, T(15)}, + {1, T(37)}, + {15, T(44)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedByte, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Camera, view.slice(&Field::object), view.slice(&Field::camera)} + }}; + + UnsignedInt expected[]{15, 37, 44}; + CORRADE_COMPARE_AS(arrayView(scene.camerasAsArray()), + Containers::arrayView(expected), + TestSuite::Compare::Container); + + /* Test Into() as well as it only shares a common helper with AsArray() but + has different top-level code paths */ + UnsignedInt out[3]; + scene.camerasInto(out); + CORRADE_COMPARE_AS(Containers::arrayView(out), + Containers::arrayView(expected), + TestSuite::Compare::Container); +} + +void SceneDataTest::camerasIntoArrayInvalidSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt camera; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Camera, view.slice(&Field::object), view.slice(&Field::camera)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt destination[2]; + scene.camerasInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::camerasInto(): expected a view with 3 elements but got 2\n"); +} + +template void SceneDataTest::skinsAsArray() { + setTestCaseTemplateName(NameTraits::name()); + + struct Field { + UnsignedByte object; + T skin; + } fields[]{ + {0, T(15)}, + {1, T(37)}, + {15, T(44)} + }; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedByte, 50, {}, fields, { + /* To verify it isn't just picking the first ever field */ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Skin, view.slice(&Field::object), view.slice(&Field::skin)} + }}; + + UnsignedInt expected[]{15, 37, 44}; + CORRADE_COMPARE_AS(arrayView(scene.skinsAsArray()), + Containers::arrayView(expected), + TestSuite::Compare::Container); + + /* Test Into() as well as it only shares a common helper with AsArray() but + has different top-level code paths */ + UnsignedInt out[3]; + scene.skinsInto(out); + CORRADE_COMPARE_AS(Containers::arrayView(out), + Containers::arrayView(expected), + TestSuite::Compare::Container); +} + +void SceneDataTest::skinsIntoArrayInvalidSize() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt skin; + } fields[3]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + SceneFieldData{SceneField::Skin, view.slice(&Field::object), view.slice(&Field::skin)} + }}; + + std::ostringstream out; + Error redirectError{&out}; + UnsignedInt destination[2]; + scene.skinsInto(destination); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::skinsInto(): expected a view with 3 elements but got 2\n"); +} + +void SceneDataTest::mutableAccessNotAllowed() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + const struct Field { + UnsignedInt object; + UnsignedShort foobar; + UnsignedShort mesh; + } fields[2]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, {}, fields, { + SceneFieldData{sceneFieldCustom(35), + view.slice(&Field::object), + view.slice(&Field::foobar)}, + SceneFieldData{SceneField::Mesh, + view.slice(&Field::object), + view.slice(&Field::mesh)}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.mutableData(); + scene.mutableObjects(0); + scene.mutableObjects(0); + scene.mutableObjects(SceneField::Mesh); + scene.mutableObjects(SceneField::Mesh); + scene.mutableField(0); + scene.mutableField(0); + scene.mutableField(1); + scene.mutableField(SceneField::Mesh); + scene.mutableField(SceneField::Mesh); + scene.mutableField(sceneFieldCustom(35)); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::mutableData(): data not mutable\n" + "Trade::SceneData::mutableObjects(): data not mutable\n" + "Trade::SceneData::mutableObjects(): data not mutable\n" + "Trade::SceneData::mutableObjects(): data not mutable\n" + "Trade::SceneData::mutableObjects(): data not mutable\n" + "Trade::SceneData::mutableField(): data not mutable\n" + "Trade::SceneData::mutableField(): data not mutable\n" + "Trade::SceneData::mutableField(): data not mutable\n" + "Trade::SceneData::mutableField(): data not mutable\n" + "Trade::SceneData::mutableField(): data not mutable\n" + "Trade::SceneData::mutableField(): data not mutable\n"); +} + +void SceneDataTest::objectsNotFound() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedShort foobar; + UnsignedShort mesh; + } fields[2]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, DataFlag::Mutable, fields, { + SceneFieldData{sceneFieldCustom(35), view.slice(&Field::object), view.slice(&Field::foobar)}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.objects(2); + scene.objects(2); + scene.mutableObjects(2); + scene.mutableObjects(2); + scene.objects(sceneFieldCustom(666)); + scene.objects(sceneFieldCustom(666)); + scene.mutableObjects(sceneFieldCustom(666)); + scene.mutableObjects(sceneFieldCustom(666)); + + scene.objectsAsArray(2); + scene.objectsAsArray(sceneFieldCustom(666)); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::objects(): index 2 out of range for 2 fields\n" + "Trade::SceneData::objects(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mutableObjects(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mutableObjects(): index 2 out of range for 2 fields\n" + "Trade::SceneData::objects(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::objects(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mutableObjects(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mutableObjects(): field Trade::SceneField::Custom(666) not found\n" + + "Trade::SceneData::objectsInto(): index 2 out of range for 2 fields\n" + "Trade::SceneData::objectsInto(): field Trade::SceneField::Custom(666) not found\n"); +} + +void SceneDataTest::objectsWrongType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedShort object; + UnsignedShort foobar; + UnsignedInt mesh; + } fields[2]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedShort, 5, DataFlag::Mutable, fields, { + SceneFieldData{sceneFieldCustom(35), view.slice(&Field::object), view.slice(&Field::foobar)}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.objects(1); + scene.mutableObjects(1); + scene.objects(SceneField::Mesh); + scene.mutableObjects(SceneField::Mesh); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::objects(): objects are Trade::SceneObjectType::UnsignedShort but requested Trade::SceneObjectType::UnsignedByte\n" + "Trade::SceneData::mutableObjects(): objects are Trade::SceneObjectType::UnsignedShort but requested Trade::SceneObjectType::UnsignedByte\n" + "Trade::SceneData::objects(): objects are Trade::SceneObjectType::UnsignedShort but requested Trade::SceneObjectType::UnsignedByte\n" + "Trade::SceneData::mutableObjects(): objects are Trade::SceneObjectType::UnsignedShort but requested Trade::SceneObjectType::UnsignedByte\n"); +} + +void SceneDataTest::fieldNotFound() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt foo, bar; + } fields[2]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, DataFlag::Mutable, fields, { + SceneFieldData{sceneFieldCustom(34), view.slice(&Field::object), view.slice(&Field::foo)}, + SceneFieldData{sceneFieldCustom(35), view.slice(&Field::object), view.slice(&Field::bar)}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.fieldData(2); + scene.fieldName(2); + scene.fieldType(2); + scene.fieldSize(2); + scene.fieldArraySize(2); + scene.field(2); + scene.field(2); + scene.field(2); + scene.mutableField(2); + scene.mutableField(2); + scene.mutableField(2); + + scene.fieldId(sceneFieldCustom(666)); + scene.fieldType(sceneFieldCustom(666)); + scene.fieldSize(sceneFieldCustom(666)); + scene.fieldArraySize(sceneFieldCustom(666)); + scene.field(sceneFieldCustom(666)); + scene.field(sceneFieldCustom(666)); + scene.field(sceneFieldCustom(666)); + scene.mutableField(sceneFieldCustom(666)); + scene.mutableField(sceneFieldCustom(666)); + scene.mutableField(sceneFieldCustom(666)); + + scene.parentsAsArray(); + scene.transformations2DAsArray(); + scene.transformations3DAsArray(); + /* Test both AsArray() and Into() for transformations as they only share a + common helper but have different top-level code paths. They however have + the same assertion messages to save binary size a bit. */ + scene.transformations2DInto(nullptr); + scene.transformations3DInto(nullptr); + scene.meshesAsArray(); + scene.meshMaterialsAsArray(); + scene.lightsAsArray(); + scene.camerasAsArray(); + scene.skinsAsArray(); + CORRADE_COMPARE(out.str(), + "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" + "Trade::SceneData::fieldSize(): index 2 out of range for 2 fields\n" + "Trade::SceneData::fieldArraySize(): index 2 out of range for 2 fields\n" + "Trade::SceneData::field(): index 2 out of range for 2 fields\n" + "Trade::SceneData::field(): index 2 out of range for 2 fields\n" + "Trade::SceneData::field(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mutableField(): index 2 out of range for 2 fields\n" + "Trade::SceneData::mutableField(): index 2 out of range for 2 fields\n" + "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::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" + "Trade::SceneData::field(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::field(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::field(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mutableField(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mutableField(): field Trade::SceneField::Custom(666) not found\n" + "Trade::SceneData::mutableField(): field Trade::SceneField::Custom(666) not found\n" + + "Trade::SceneData::parentsInto(): field not found\n" + /* Test both AsArray() and Into() for transformations as they only + share a common helper but have different top-level code paths. They + however have the same assertion messages to save binary size a + bit. */ + "Trade::SceneData::transformations2DInto(): no transformation-related field found\n" + "Trade::SceneData::transformations3DInto(): no transformation-related field found\n" + "Trade::SceneData::transformations2DInto(): no transformation-related field found\n" + "Trade::SceneData::transformations3DInto(): no transformation-related field found\n" + "Trade::SceneData::meshesInto(): field not found\n" + "Trade::SceneData::meshMaterialsInto(): field not found\n" + "Trade::SceneData::lightsInto(): field not found\n" + "Trade::SceneData::camerasInto(): field not found\n" + "Trade::SceneData::skinsInto(): field not found\n"); +} + +void SceneDataTest::fieldWrongType() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedShort foobar; + UnsignedShort mesh; + } fields[2]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, DataFlag::Mutable, fields, { + SceneFieldData{sceneFieldCustom(35), view.slice(&Field::object), view.slice(&Field::foobar)}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.field(1); + scene.field(1); + scene.mutableField(1); + scene.mutableField(1); + scene.field(SceneField::Mesh); + scene.field(SceneField::Mesh); + scene.mutableField(SceneField::Mesh); + scene.mutableField(SceneField::Mesh); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::field(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::field(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::field(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::field(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is Trade::SceneFieldType::UnsignedShort but requested a type equivalent to Trade::SceneFieldType::UnsignedByte\n"); +} + +void SceneDataTest::fieldWrongArrayAccess() { + #ifdef CORRADE_NO_ASSERT + CORRADE_SKIP("CORRADE_NO_ASSERT defined, can't test assertions"); + #endif + + struct Field { + UnsignedInt object; + UnsignedInt mesh; + UnsignedInt foobar; + } fields[2]{}; + + Containers::StridedArrayView1D view = fields; + + SceneData scene{SceneObjectType::UnsignedInt, 5, DataFlag::Mutable, fields, { + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)}, + SceneFieldData{sceneFieldCustom(35), view.slice(&Field::object), Containers::arrayCast<2, UnsignedInt>(view.slice(&Field::foobar))}, + }}; + + std::ostringstream out; + Error redirectError{&out}; + scene.field(0); + scene.field(1); + scene.mutableField(0); + scene.mutableField(1); + scene.field(SceneField::Mesh); + scene.field(sceneFieldCustom(35)); + scene.mutableField(SceneField::Mesh); + scene.mutableField(sceneFieldCustom(35)); + CORRADE_COMPARE(out.str(), + "Trade::SceneData::field(): Trade::SceneField::Mesh is not an array field, can't use T[] to access it\n" + "Trade::SceneData::field(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is not an array field, can't use T[] to access it\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n" + "Trade::SceneData::field(): Trade::SceneField::Mesh is not an array field, can't use T[] to access it\n" + "Trade::SceneData::field(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Mesh is not an array field, can't use T[] to access it\n" + "Trade::SceneData::mutableField(): Trade::SceneField::Custom(35) is an array field, use T[] to access it\n"); +} + +void SceneDataTest::releaseFieldData() { + struct Field { + UnsignedByte object; + UnsignedInt mesh; + }; + + Containers::Array data{NoInit, 3*sizeof(Field)}; + Containers::StridedArrayView1D view = Containers::arrayCast(data); + + auto fields = Containers::array({ + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }); + SceneFieldData* originalFields = fields; + + SceneData scene{SceneObjectType::UnsignedByte, 50, std::move(data), std::move(fields)}; + + Containers::Array released = scene.releaseFieldData(); + CORRADE_COMPARE(released.data(), originalFields); + CORRADE_COMPARE(released.size(), 2); + + /* Fields are all gone */ + CORRADE_COMPARE(static_cast(scene.fieldData()), nullptr); + CORRADE_COMPARE(scene.fieldCount(), 0); + + /* Data stays untouched, object count and type as well, as it con't result + in any dangling data access */ + CORRADE_COMPARE(scene.data(), view.data()); + CORRADE_COMPARE(scene.objectCount(), 50); + CORRADE_COMPARE(scene.objectType(), SceneObjectType::UnsignedByte); +} + +void SceneDataTest::releaseData() { + struct Field { + UnsignedByte object; + UnsignedByte mesh; + }; + + Containers::Array data{NoInit, 3*sizeof(Field)}; + Containers::StridedArrayView1D view = Containers::arrayCast(data); + + SceneData scene{SceneObjectType::UnsignedByte, 50, std::move(data), { + SceneFieldData{SceneField::Parent, SceneObjectType::UnsignedByte, nullptr, SceneFieldType::Int, nullptr}, + SceneFieldData{SceneField::Mesh, view.slice(&Field::object), view.slice(&Field::mesh)} + }}; + + Containers::Array released = scene.releaseData(); + CORRADE_COMPARE(released.data(), view.data()); + CORRADE_COMPARE(released.size(), 3*sizeof(Field)); + + /* Both fields and data are all gone */ + CORRADE_COMPARE(static_cast(scene.fieldData()), nullptr); + CORRADE_COMPARE(scene.fieldCount(), 0); + CORRADE_COMPARE(static_cast(scene.data()), nullptr); + + /* Object count and type stays untouched, as it con't result in any + dangling data access */ + CORRADE_COMPARE(scene.objectCount(), 50); + CORRADE_COMPARE(scene.objectType(), SceneObjectType::UnsignedByte); +} + }}}} CORRADE_TEST_MAIN(Magnum::Trade::Test::SceneDataTest) diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index e0104d5a1..46f98370c 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -99,6 +99,11 @@ class PbrMetallicRoughnessMaterialData; class PbrSpecularGlossinessMaterialData; class PhongMaterialData; class TextureData; + +enum class SceneObjectType: UnsignedByte; +enum class SceneField: UnsignedInt; +enum class SceneFieldType: UnsignedShort; +class SceneFieldData; class SceneData; template class SkinData;