From 150d045275b0de877d13818b9e605c76e5f552f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 12 Apr 2022 22:10:51 +0200 Subject: [PATCH] Trade: full scene conversion support in AbstractSceneConverter. Wasn't really possible to split this into multiple commits, so here's the whole thing including delegation from and to the single-mesh APIs. What's not done here and postponed for later is: - an ability to feed the whole importer to it, filtering away data that aren't supported by the converter - corresponding changes in AbstractImageConverter, where it would now primarily accept ImageData to future-proof for arbitrary extra key/value data --- doc/changelog.dox | 3 + src/Magnum/Trade/AbstractSceneConverter.cpp | 1044 +++- src/Magnum/Trade/AbstractSceneConverter.h | 1754 ++++++- .../Trade/Test/AbstractSceneConverterTest.cpp | 4438 ++++++++++++++++- .../AnySceneConverter/AnySceneConverter.cpp | 2 +- 5 files changed, 7134 insertions(+), 107 deletions(-) diff --git a/doc/changelog.dox b/doc/changelog.dox index f80f4bc71..b57171cd5 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -285,6 +285,9 @@ See also: - 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" +- The @ref Trade::AbstractSceneConverter plugin interface gained support for + batch conversion of whole scenes --- meshes, hierarchies, materials, + textures, animations and other data - 1D and 3D image support in @ref Trade::AbstractImageConverter - @ref Trade::LightData got extended to support light attenuation and range parameters as well and spot light inner and outer angle diff --git a/src/Magnum/Trade/AbstractSceneConverter.cpp b/src/Magnum/Trade/AbstractSceneConverter.cpp index b72541173..a364759eb 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.cpp +++ b/src/Magnum/Trade/AbstractSceneConverter.cpp @@ -25,15 +25,23 @@ #include "AbstractSceneConverter.h" +#include #include #include +#include #include +#include #include #include #include +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" +#include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/ArrayAllocator.h" +#include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/SceneData.h" #ifdef MAGNUM_BUILD_DEPRECATED /* needed by deprecated convertToFile() that takes a std::string */ @@ -54,10 +62,75 @@ namespace Magnum { namespace Trade { using namespace Containers::Literals; +/* Gets allocated in begin*() and deallocated in end*() or abort(). The direct + conversion functions such as convert(const MeshData&) don't directly need + this state, but can indirectly delegate to it, such as when + convert(const MeshData&) is emulated with a sequence of begin(), + add(const MeshData&) and end(). */ +struct AbstractSceneConverter::State { + enum class Type { + Convert, + ConvertToData, + ConvertToFile + }; + + explicit State(Type type): type{type} { + if(type == Type::Convert) + new(&converted.mesh) Containers::Optional{}; + else if(type == Type::ConvertToData) + new(&converted.meshToData) Containers::Optional>{}; + else if(type == Type::ConvertToFile) + new(&converted.meshToFile) bool{}; + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + ~State() { + if(type == Type::Convert) + converted.mesh.~Optional(); + else if(type == Type::ConvertToData) + converted.meshToData.~Optional(); + else if(type == Type::ConvertToFile) + ; + else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + } + + Type type; + + UnsignedInt sceneCount = 0; + UnsignedInt animationCount = 0; + UnsignedInt lightCount = 0; + UnsignedInt cameraCount = 0; + UnsignedInt skin2DCount = 0; + UnsignedInt skin3DCount = 0; + UnsignedInt meshCount = 0; + UnsignedInt materialCount = 0; + UnsignedInt textureCount = 0; + UnsignedInt image1DCount = 0; + UnsignedInt image2DCount = 0; + UnsignedInt image3DCount = 0; + + /* Used if type == Type::ConvertToFile. Could theoretically be in the same + allocation as State (ArrayTuple?), or at least reusing the space in + `converted`, but I don't think a single allocation matters that much. */ + Containers::String filename; + + union Converted { + /* C++, FUCK OFF, what's the point of requiring me to create an + explicit constructor and destructor if I have no way to store or + know the information about the active field at this point?! */ + Converted() noexcept {} + ~Converted() {} + + Containers::Optional mesh; + Containers::Optional> meshToData; + bool meshToFile; + } converted; +}; + Containers::StringView AbstractSceneConverter::pluginInterface() { return /* [interface] */ -"cz.mosra.magnum.Trade.AbstractSceneConverter/0.1.2"_s +"cz.mosra.magnum.Trade.AbstractSceneConverter/0.2"_s /* [interface] */ ; } @@ -148,19 +221,29 @@ Containers::Optional> Implementation::SceneConverterOptionalButAlsoArray #endif AbstractSceneConverter::convertToData(const MeshData& mesh) { - CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToData, - "Trade::AbstractSceneConverter::convertToData(): mesh conversion not supported", {}); + if(features() >= SceneConverterFeature::ConvertMeshToData) { + Containers::Optional> out = doConvertToData(mesh); + CORRADE_ASSERT(!out || !out->deleter() || out->deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || out->deleter() == ArrayAllocator::deleter, + "Trade::AbstractSceneConverter::convertToData(): implementation is not allowed to use a custom Array deleter", {}); - Containers::Optional> out = doConvertToData(mesh); - CORRADE_ASSERT(!out || !out->deleter() || out->deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || out->deleter() == ArrayAllocator::deleter, - "Trade::AbstractSceneConverter::convertToData(): implementation is not allowed to use a custom Array deleter", {}); + /* GCC 4.8 needs an explicit conversion here */ + #ifdef MAGNUM_BUILD_DEPRECATED + return Implementation::SceneConverterOptionalButAlsoArray{std::move(out)}; + #else + return out; + #endif - /* GCC 4.8 needs an explicit conversion here */ - #ifdef MAGNUM_BUILD_DEPRECATED - return Implementation::SceneConverterOptionalButAlsoArray{std::move(out)}; - #else - return out; - #endif + } else if(features() >= (SceneConverterFeature::ConvertMultipleToData|SceneConverterFeature::AddMeshes)) { + beginData(); + + if(add(mesh)) return endData(); + + /* Finish the conversion even if add() fails -- this API shouldn't + leave it in an in-progress state */ + abort(); + return {}; + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convertToData(): mesh conversion not supported", {}); } Containers::Optional> AbstractSceneConverter::doConvertToData(const MeshData&) { @@ -168,10 +251,20 @@ Containers::Optional> AbstractSceneConverter::doConvertT } bool AbstractSceneConverter::convertToFile(const MeshData& mesh, const Containers::StringView filename) { - CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToFile, - "Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported", {}); + if(features() >= SceneConverterFeature::ConvertMeshToFile) { + return doConvertToFile(mesh, filename); + + } else if(features() & (SceneConverterFeature::ConvertMultipleToFile|SceneConverterFeature::AddMeshes)) { + beginFile(filename); + + if(add(mesh)) return endFile(); + + /* Finish the conversion even if add() fails -- this API shouldn't + leave it in an in-progress state */ + abort(); + return false; - return doConvertToFile(mesh, filename); + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported", {}); } #ifdef MAGNUM_BUILD_DEPRECATED @@ -183,18 +276,890 @@ bool AbstractSceneConverter::convertToFile(const std::string& filename, const Me bool AbstractSceneConverter::doConvertToFile(const MeshData& mesh, const Containers::StringView filename) { CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToData, "Trade::AbstractSceneConverter::convertToFile(): mesh conversion advertised but not implemented", false); - const Containers::Optional> data = doConvertToData(mesh); + const Containers::Optional> out = doConvertToData(mesh); + /* No deleter checks as it doesn't matter here */ + if(!out) return false; + + if(!Utility::Path::write(filename, *out)) { + Error() << "Trade::AbstractSceneConverter::convertToFile(): cannot write to file" << filename; + return false; + } + + return true; +} + +bool AbstractSceneConverter::isConverting() const { + return !!_state; +} + +void AbstractSceneConverter::abort() { + if(!_state) return; + + doAbort(); + _state = {}; +} + +void AbstractSceneConverter::doAbort() {} + +void AbstractSceneConverter::begin() { + if(_state) abort(); + + _state.emplace(State::Type::Convert); + + if(features() >= SceneConverterFeature::ConvertMultiple) { + doBegin(); + + } else if(features() & SceneConverterFeature::ConvertMesh) { + /* Actual operation performed in doAdd(const MeshData&) */ + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::begin(): feature not supported", ); +} + +void AbstractSceneConverter::doBegin() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::begin(): feature advertised but not implemented", ); +} + +Containers::Pointer AbstractSceneConverter::end() { + CORRADE_ASSERT(_state && _state->type == State::Type::Convert, + "Trade::AbstractSceneConverter::end(): no conversion in progress", {}); + + Containers::ScopeGuard deleteState{this, [](AbstractSceneConverter* self) { + self->_state = {}; + }}; + + if(features() & SceneConverterFeature::ConvertMesh) { + if(_state->meshCount != 1) { + Error{} << "Trade::AbstractSceneConverter::end(): the converter requires exactly one mesh"; + return {}; + } + + struct SingleMeshImporter: AbstractImporter { + explicit SingleMeshImporter(Containers::Optional&& mesh) noexcept: _mesh{std::move(mesh)} {} + + ImporterFeatures doFeatures() const override { return {}; } + bool doIsOpened() const override { return _opened; } + void doClose() override { + _opened = false; + _mesh = {}; + } + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { + /* To avoid complicated logic (such as returning non-owned + data and then having to specify the lifetime guarantees), + the mesh can be retrieved only once. Second time it's an + error. Another option would be to behave like if the + importer is closed afterwards, but that would result in + assertions which isn't nice. */ + if(!_mesh) { + Error{} << "Trade::AbstractSceneConverter::end(): mesh can be retrieved only once from a converter with just Trade::SceneConverterFeature::ConvertMesh"; + return {}; + } + + Containers::Optional out = std::move(_mesh); + _mesh = {}; + return out; + } + + private: + bool _opened = true; + Containers::Optional _mesh; + }; + + return Containers::Pointer(new SingleMeshImporter{std::move(_state->converted.mesh)}); + + } else if(features() & SceneConverterFeature::ConvertMultiple) { + return doEnd(); + + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Pointer AbstractSceneConverter::doEnd() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::end(): feature advertised but not implemented", {}); +} + +void AbstractSceneConverter::beginData() { + if(_state) abort(); + + _state.emplace(State::Type::ConvertToData); + + if(features() >= SceneConverterFeature::ConvertMultipleToData) { + doBeginData(); + + } else if(features() >= SceneConverterFeature::ConvertMeshToData) { + /* Actual operation performed in doAdd(const MeshData&) */ + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginData(): feature not supported", ); +} + +void AbstractSceneConverter::doBeginData() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginData(): feature advertised but not implemented", ); +} + +Containers::Optional> AbstractSceneConverter::endData() { + CORRADE_ASSERT(_state && _state->type == State::Type::ConvertToData, + "Trade::AbstractSceneConverter::endData(): no data conversion in progress", {}); + + Containers::ScopeGuard deleteState{this, [](AbstractSceneConverter* self) { + self->_state = {}; + }}; + + if(features() >= SceneConverterFeature::ConvertMultipleToData) { + Containers::Optional> out = doEndData(); + CORRADE_ASSERT(!out || !out->deleter() || out->deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || out->deleter() == ArrayAllocator::deleter, + "Trade::AbstractSceneConverter::endData(): implementation is not allowed to use a custom Array deleter", {}); + + return out; + + } else if(features() >= SceneConverterFeature::ConvertMeshToData) { + if(_state->meshCount != 1) { + Error{} << "Trade::AbstractSceneConverter::endData(): the converter requires exactly one mesh"; + return {}; + } + + /* No deleter validity checks here, those were performed in + convertToData(const MeshData&) already */ + + return std::move(_state->converted.meshToData); + + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +Containers::Optional> AbstractSceneConverter::doEndData() { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::endData(): feature advertised but not implemented", {}); +} + +void AbstractSceneConverter::beginFile(const Containers::StringView filename) { + if(_state) abort(); + + _state.emplace(State::Type::ConvertToFile); + _state->filename = Containers::String::nullTerminatedGlobalView(filename); + + if(features() >= SceneConverterFeature::ConvertMultipleToFile) { + doBeginFile(_state->filename); + + } else if(features() >= SceneConverterFeature::ConvertMeshToFile) { + /* Actual operation performed in doAdd(const MeshData&) */ + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginFile(): feature not supported", ); +} + +void AbstractSceneConverter::doBeginFile(Containers::StringView) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMultipleToData, + "Trade::AbstractSceneConverter::beginFile(): feature advertised but not implemented", ); + + doBeginData(); +} + +bool AbstractSceneConverter::endFile() { + CORRADE_ASSERT(_state && _state->type == State::Type::ConvertToFile, + "Trade::AbstractSceneConverter::endFile(): no file conversion in progress", {}); + + Containers::ScopeGuard deleteState{this, [](AbstractSceneConverter* self) { + self->_state = {}; + }}; + + if(features() >= SceneConverterFeature::ConvertMultipleToFile) { + return doEndFile(_state->filename); + + } else if(features() & SceneConverterFeature::ConvertMeshToFile) { + if(_state->meshCount != 1) { + Error{} << "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh"; + return {}; + } + + return _state->converted.meshToFile; + + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ +} + +bool AbstractSceneConverter::doEndFile(const Containers::StringView filename) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMultipleToData, + "Trade::AbstractSceneConverter::endFile(): feature advertised but not implemented", {}); + + const Containers::Optional> data = doEndData(); /* No deleter checks as it doesn't matter here */ if(!data) return false; if(!Utility::Path::write(filename, *data)) { - Error() << "Trade::AbstractSceneConverter::convertToFile(): cannot write to file" << filename; + Error{} << "Trade::AbstractSceneConverter::endFile(): cannot write to file" << filename; return false; } return true; } +UnsignedInt AbstractSceneConverter::sceneCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::sceneCount(): no conversion in progress", {}); + return _state->sceneCount; +} + +Containers::Optional AbstractSceneConverter::add(const SceneData& scene, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddScenes, + "Trade::AbstractSceneConverter::add(): scene conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->sceneCount, scene, name)) + return _state->sceneCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const SceneData& scene) { + return add(scene, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const SceneData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): scene conversion advertised but not implemented", {}); +} + +void AbstractSceneConverter::setSceneFieldName(const SceneField field, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddScenes, + "Trade::AbstractSceneConverter::setSceneFieldName(): feature not supported", ); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::setSceneFieldName(): no conversion in progress", ); + CORRADE_ASSERT(isSceneFieldCustom(field), + "Trade::AbstractSceneConverter::setSceneFieldName():" << field << "is not custom", ); + + doSetSceneFieldName(sceneFieldCustom(field), name); +} + +void AbstractSceneConverter::doSetSceneFieldName(UnsignedInt, Containers::StringView) {} + +void AbstractSceneConverter::setObjectName(const UnsignedLong object, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddScenes, + "Trade::AbstractSceneConverter::setObjectName(): feature not supported", ); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::setObjectName(): no conversion in progress", ); + + doSetObjectName(object, name); +} + +void AbstractSceneConverter::doSetObjectName(UnsignedLong, Containers::StringView) {} + +void AbstractSceneConverter::setDefaultScene(const UnsignedInt id) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddScenes, + "Trade::AbstractSceneConverter::setDefaultScene(): feature not supported", ); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::setDefaultScene(): no conversion in progress", ); + CORRADE_ASSERT(id < _state->sceneCount, + "Trade::AbstractSceneConverter::setDefaultScene(): index" << id << "out of range for" << _state->sceneCount << "scenes", ); + + doSetDefaultScene(id); +} + +void AbstractSceneConverter::doSetDefaultScene(UnsignedInt) {} + +UnsignedInt AbstractSceneConverter::animationCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::animationCount(): no conversion in progress", {}); + return _state->animationCount; +} + +Containers::Optional AbstractSceneConverter::add(const AnimationData& animation, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddAnimations, + "Trade::AbstractSceneConverter::add(): animation conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->animationCount, animation, name)) + return _state->animationCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const AnimationData& animation) { + return add(animation, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const AnimationData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): animation conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::lightCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::lightCount(): no conversion in progress", {}); + return _state->lightCount; +} + +Containers::Optional AbstractSceneConverter::add(const LightData& light, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddLights, + "Trade::AbstractSceneConverter::add(): light conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->lightCount, light, name)) + return _state->lightCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const LightData& light) { + return add(light, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const LightData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): light conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::cameraCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::cameraCount(): no conversion in progress", {}); + return _state->cameraCount; +} + +Containers::Optional AbstractSceneConverter::add(const CameraData& camera, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddCameras, + "Trade::AbstractSceneConverter::add(): camera conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->cameraCount, camera, name)) + return _state->cameraCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const CameraData& camera) { + return add(camera, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const CameraData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): camera conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::skin2DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::skin2DCount(): no conversion in progress", {}); + return _state->skin2DCount; +} + +Containers::Optional AbstractSceneConverter::add(const SkinData2D& skin, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddSkins2D, + "Trade::AbstractSceneConverter::add(): 2D skin conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->skin2DCount, skin, name)) + return _state->skin2DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const SkinData2D& skin) { + return add(skin, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const SkinData2D&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): 2D skin conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::skin3DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::skin3DCount(): no conversion in progress", {}); + return _state->skin3DCount; +} + +Containers::Optional AbstractSceneConverter::add(const SkinData3D& skin, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddSkins3D, + "Trade::AbstractSceneConverter::add(): 3D skin conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->skin3DCount, skin, name)) + return _state->skin3DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const SkinData3D& skin) { + return add(skin, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const SkinData3D&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): 3D skin conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::meshCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::meshCount(): no conversion in progress", {}); + return _state->meshCount; +} + +Containers::Optional AbstractSceneConverter::add(const MeshData& mesh, const Containers::StringView name) { + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(features() >= SceneConverterFeature::AddMeshes) { + if(!doAdd(_state->meshCount, mesh, name)) return {}; + + } else if(features() & (SceneConverterFeature::ConvertMesh| + SceneConverterFeature::ConvertMeshToData| + SceneConverterFeature::ConvertMeshToFile)) { + if(_state->meshCount != 0) { + Error{} << "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh"; + return {}; + } + + if(_state->type == State::Type::Convert) { + CORRADE_INTERNAL_ASSERT(features() & SceneConverterFeature::ConvertMesh); + if(!(_state->converted.mesh = doConvert(mesh))) + return {}; + } else if(_state->type == State::Type::ConvertToData) { + CORRADE_INTERNAL_ASSERT(features() >= SceneConverterFeature::ConvertMeshToData); + if(!(_state->converted.meshToData = doConvertToData(mesh))) + return {}; + } else if(_state->type == State::Type::ConvertToFile) { + CORRADE_INTERNAL_ASSERT(features() & SceneConverterFeature::ConvertMeshToFile); + if(!(_state->converted.meshToFile = doConvertToFile(mesh, _state->filename))) + return {}; + } else CORRADE_INTERNAL_ASSERT_UNREACHABLE(); /* LCOV_EXCL_LINE */ + + } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): mesh conversion not supported", {}); + + return _state->meshCount++; +} + +Containers::Optional AbstractSceneConverter::add(const MeshData& mesh) { + return add(mesh, {}); +} + +bool AbstractSceneConverter::doAdd(const UnsignedInt id, const MeshData& mesh, const Containers::StringView name) { + CORRADE_ASSERT(features() >= SceneConverterFeature::MeshLevels, "Trade::AbstractSceneConverter::add(): mesh conversion advertised but not implemented", {}); + + return doAdd(id, Containers::Iterable{mesh}, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable meshLevels, const Containers::StringView name) { + CORRADE_ASSERT(features() >= (SceneConverterFeature::AddMeshes|SceneConverterFeature::MeshLevels), + "Trade::AbstractSceneConverter::add(): multi-level mesh conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + CORRADE_ASSERT(!meshLevels.isEmpty(), + "Trade::AbstractSceneConverter::add(): at least one mesh level has to be specified", false); + + if(doAdd(_state->meshCount, meshLevels, name)) + return _state->meshCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable meshLevels) { + return add(meshLevels, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): multi-level mesh conversion advertised but not implemented", {}); +} + +void AbstractSceneConverter::setMeshAttributeName(const MeshAttribute attribute, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddMeshes, + "Trade::AbstractSceneConverter::setMeshAttributeName(): feature not supported", ); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::setMeshAttributeName(): no conversion in progress", ); + CORRADE_ASSERT(isMeshAttributeCustom(attribute), + "Trade::AbstractSceneConverter::setMeshAttributeName():" << attribute << "is not custom", ); + + doSetMeshAttributeName(meshAttributeCustom(attribute), name); +} + +void AbstractSceneConverter::doSetMeshAttributeName(UnsignedShort, Containers::StringView) {} + +UnsignedInt AbstractSceneConverter::materialCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::materialCount(): no conversion in progress", {}); + return _state->materialCount; +} + +Containers::Optional AbstractSceneConverter::add(const MaterialData& material, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddMaterials, + "Trade::AbstractSceneConverter::add(): material conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->materialCount, material, name)) + return _state->materialCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const MaterialData& material) { + return add(material, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const MaterialData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): material conversion advertised but not implemented", {}); +} + +UnsignedInt AbstractSceneConverter::textureCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::textureCount(): no conversion in progress", {}); + return _state->textureCount; +} + +Containers::Optional AbstractSceneConverter::add(const TextureData& texture, const Containers::StringView name) { + CORRADE_ASSERT(features() & SceneConverterFeature::AddTextures, + "Trade::AbstractSceneConverter::add(): texture conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->textureCount, texture, name)) + return _state->textureCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const TextureData& texture) { + return add(texture, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, const TextureData&, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): texture conversion advertised but not implemented", {}); +} + +#ifndef CORRADE_NO_ASSERT +namespace { + +template bool checkImageValidity(const char* const messagePrefix, const ImageData& image) { + /* At some point there might be a file format that allows zero-sized + images, but so far I don't know about any. When such format appears, + this check will get moved to plugin implementations that can't work with + zero-sized images. + + Also note that this check isn't done for the Image->Image conversion + above, there zero-sized images and nullptr *could* make sense. */ + CORRADE_ASSERT(image.size().product(), + messagePrefix << "can't add image with a zero size:" << image.size(), false); + CORRADE_ASSERT(image.data(), + messagePrefix << "can't add image with a nullptr view", false); + return true; +} + +template bool checkImageValidity(const char* const messagePrefix, const Containers::Iterable> imageLevels) { + CORRADE_ASSERT(!imageLevels.isEmpty(), + messagePrefix << "at least one image level has to be specified", false); + + const bool isCompressed = imageLevels[0].isCompressed(); + const PixelFormat format = isCompressed ? PixelFormat{} : imageLevels[0].format(); + const UnsignedInt formatExtra = isCompressed ? 0 : imageLevels[0].formatExtra(); + const CompressedPixelFormat compressedFormat = isCompressed ? imageLevels[0].compressedFormat() : CompressedPixelFormat{}; + const ImageFlags flags = imageLevels[0].flags(); + /* Going through *all* levels although the format assertion is never fired + in the first iteration in order to properly check also the first one for + zero size / nullptr. */ + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + CORRADE_ASSERT(imageLevels[i].size().product(), + messagePrefix << "can't add image level" << i << "with a zero size:" << imageLevels[i].size(), false); + CORRADE_ASSERT(imageLevels[i].data(), + messagePrefix << "can't add image level" << i << "with a nullptr view", false); + CORRADE_ASSERT(imageLevels[i].isCompressed() == isCompressed, + messagePrefix << "image level" << i << (isCompressed ? "is not" : "is") << "compressed but previous" << (isCompressed ? "are" : "aren't"), false); + if(!isCompressed) { + CORRADE_ASSERT(imageLevels[i].format() == format, + messagePrefix << "image levels don't have the same format, expected" << format << "but got" << imageLevels[i].format() << "for level" << i, false); + CORRADE_ASSERT(imageLevels[i].formatExtra() == formatExtra, + messagePrefix << "image levels don't have the same extra format field, expected" << formatExtra << "but got" << imageLevels[i].formatExtra() << "for level" << i, false); + } else { + CORRADE_ASSERT(imageLevels[i].compressedFormat() == compressedFormat, + messagePrefix << "image levels don't have the same format, expected" << compressedFormat << "but got" << imageLevels[i].compressedFormat() << "for level" << i, false); + } + CORRADE_ASSERT(imageLevels[i].flags() == flags, + messagePrefix << "image levels don't have the same flags, expected" << flags << "but got" << imageLevels[i].flags() << "for level" << i, false); + } + + return true; +} + +} +#endif + +UnsignedInt AbstractSceneConverter::image1DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::image1DCount(): no conversion in progress", {}); + return _state->image1DCount; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData1D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() & (image.isCompressed() ? SceneConverterFeature::AddCompressedImages1D : SceneConverterFeature::AddImages1D), + "Trade::AbstractSceneConverter::add():" << (image.isCompressed() ? "compressed 1D" : "1D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", image)) + return {}; + #endif + + if(doAdd(_state->image1DCount, image, name)) + return _state->image1DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData1D& image) { + return add(image, {}); +} + +bool AbstractSceneConverter::doAdd(const UnsignedInt id, const ImageData1D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ImageLevels, "Trade::AbstractSceneConverter::add(): 1D image conversion advertised but not implemented", {}); + + return doAdd(id, Containers::Iterable{image}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView1D& image, const Containers::StringView name) { + return add(ImageData1D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView1D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView1D& image, const Containers::StringView name) { + return add(ImageData1D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView1D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds. + Has to be first so we can safely ask for the first item in asserts + below. */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", imageLevels)) + return {}; + #endif + CORRADE_ASSERT(features() >= ((imageLevels.front().isCompressed() ? SceneConverterFeature::AddCompressedImages1D : SceneConverterFeature::AddImages1D)|SceneConverterFeature::ImageLevels), + "Trade::AbstractSceneConverter::add(): multi-level" << (imageLevels.front().isCompressed() ? "compressed 1D" : "1D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->image1DCount, imageLevels, name)) + return _state->image1DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): multi-level 1D image conversion advertised but not implemented", {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const ImageView1D& image = imageLevels[i]; + new(&data[i]) ImageData1D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const CompressedImageView1D& image = imageLevels[i]; + new(&data[i]) ImageData1D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +UnsignedInt AbstractSceneConverter::image2DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::image2DCount(): no conversion in progress", {}); + return _state->image2DCount; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData2D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() & (image.isCompressed() ? SceneConverterFeature::AddCompressedImages2D : SceneConverterFeature::AddImages2D), + "Trade::AbstractSceneConverter::add():" << (image.isCompressed() ? "compressed 2D" : "2D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", image)) + return {}; + #endif + + if(doAdd(_state->image2DCount, image, name)) + return _state->image2DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData2D& image) { + return add(image, {}); +} + +bool AbstractSceneConverter::doAdd(const UnsignedInt id, const ImageData2D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ImageLevels, "Trade::AbstractSceneConverter::add(): 2D image conversion advertised but not implemented", {}); + + return doAdd(id, Containers::Iterable{image}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView2D& image, const Containers::StringView name) { + return add(ImageData2D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView2D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView2D& image, const Containers::StringView name) { + return add(ImageData2D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView2D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds. + Has to be first so we can safely ask for the first item in asserts + below. */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", imageLevels)) + return {}; + #endif + CORRADE_ASSERT(features() >= ((imageLevels.front().isCompressed() ? SceneConverterFeature::AddCompressedImages2D : SceneConverterFeature::AddImages2D)|SceneConverterFeature::ImageLevels), + "Trade::AbstractSceneConverter::add(): multi-level" << (imageLevels.front().isCompressed() ? "compressed 2D" : "2D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->image2DCount, imageLevels, name)) + return _state->image2DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): multi-level 2D image conversion advertised but not implemented", {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const ImageView2D& image = imageLevels[i]; + new(&data[i]) ImageData2D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const CompressedImageView2D& image = imageLevels[i]; + new(&data[i]) ImageData2D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +UnsignedInt AbstractSceneConverter::image3DCount() const { + CORRADE_ASSERT(_state, "Trade::AbstractSceneConverter::image3DCount(): no conversion in progress", {}); + return _state->image3DCount; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData3D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() & (image.isCompressed() ? SceneConverterFeature::AddCompressedImages3D : SceneConverterFeature::AddImages3D), + "Trade::AbstractSceneConverter::add():" << (image.isCompressed() ? "compressed 3D" : "3D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", image)) + return {}; + #endif + + if(doAdd(_state->image3DCount, image, name)) + return _state->image3DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const ImageData3D& image) { + return add(image, {}); +} + +bool AbstractSceneConverter::doAdd(const UnsignedInt id, const ImageData3D& image, const Containers::StringView name) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ImageLevels, "Trade::AbstractSceneConverter::add(): 3D image conversion advertised but not implemented", {}); + + return doAdd(id, Containers::Iterable{image}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView3D& image, const Containers::StringView name) { + return add(ImageData3D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const ImageView3D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView3D& image, const Containers::StringView name) { + return add(ImageData3D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}, name); +} + +Containers::Optional AbstractSceneConverter::add(const CompressedImageView3D& image) { + return add(image, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + #ifndef CORRADE_NO_ASSERT + /* Explicitly return if checks fail for CORRADE_GRACEFUL_ASSERT builds. + Has to be first so we can safely ask for the first item in asserts + below. */ + if(!checkImageValidity("Trade::AbstractSceneConverter::add():", imageLevels)) + return {}; + #endif + CORRADE_ASSERT(features() >= ((imageLevels.front().isCompressed() ? SceneConverterFeature::AddCompressedImages3D : SceneConverterFeature::AddImages3D)|SceneConverterFeature::ImageLevels), + "Trade::AbstractSceneConverter::add(): multi-level" << (imageLevels.front().isCompressed() ? "compressed 3D" : "3D") << "image conversion not supported", {}); + CORRADE_ASSERT(_state, + "Trade::AbstractSceneConverter::add(): no conversion in progress", {}); + + if(doAdd(_state->image3DCount, imageLevels, name)) + return _state->image3DCount++; + return {}; +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +bool AbstractSceneConverter::doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::add(): multi-level 3D image conversion advertised but not implemented", {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const ImageView3D& image = imageLevels[i]; + new(&data[i]) ImageData3D{image.storage(), image.format(), image.formatExtra(), image.pixelSize(), image.size(), DataFlags{}, image.data()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels, const Containers::StringView name) { + Containers::Array data{NoInit, imageLevels.size()}; + for(std::size_t i = 0; i != imageLevels.size(); ++i) { + const CompressedImageView3D& image = imageLevels[i]; + new(&data[i]) ImageData3D{image.storage(), image.format(), image.size(), DataFlags{}, image.data()}; + } + + return add(data, name); +} + +Containers::Optional AbstractSceneConverter::add(const Containers::Iterable imageLevels) { + return add(imageLevels, {}); +} + Debug& operator<<(Debug& debug, const SceneConverterFeature value) { debug << "Trade::SceneConverterFeature" << Debug::nospace; @@ -205,11 +1170,31 @@ Debug& operator<<(Debug& debug, const SceneConverterFeature value) { _c(ConvertMeshInPlace) _c(ConvertMeshToData) _c(ConvertMeshToFile) + _c(ConvertMultiple) + _c(ConvertMultipleToData) + _c(ConvertMultipleToFile) + _c(AddScenes) + _c(AddAnimations) + _c(AddLights) + _c(AddCameras) + _c(AddSkins2D) + _c(AddSkins3D) + _c(AddMeshes) + _c(AddMaterials) + _c(AddTextures) + _c(AddImages1D) + _c(AddImages2D) + _c(AddImages3D) + _c(AddCompressedImages1D) + _c(AddCompressedImages2D) + _c(AddCompressedImages3D) + _c(MeshLevels) + _c(ImageLevels) #undef _c /* LCOV_EXCL_STOP */ } - return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << ")"; } Debug& operator<<(Debug& debug, const SceneConverterFeatures value) { @@ -218,7 +1203,28 @@ Debug& operator<<(Debug& debug, const SceneConverterFeatures value) { SceneConverterFeature::ConvertMeshInPlace, SceneConverterFeature::ConvertMeshToData, /* Implied by ConvertMeshToData, has to be after */ - SceneConverterFeature::ConvertMeshToFile}); + SceneConverterFeature::ConvertMeshToFile, + SceneConverterFeature::ConvertMultiple, + SceneConverterFeature::ConvertMultipleToData, + /* Implied by ConvertMultipleToData, has to be after */ + SceneConverterFeature::ConvertMultipleToFile, + SceneConverterFeature::AddScenes, + SceneConverterFeature::AddAnimations, + SceneConverterFeature::AddLights, + SceneConverterFeature::AddCameras, + SceneConverterFeature::AddSkins2D, + SceneConverterFeature::AddSkins3D, + SceneConverterFeature::AddMeshes, + SceneConverterFeature::AddMaterials, + SceneConverterFeature::AddTextures, + SceneConverterFeature::AddImages1D, + SceneConverterFeature::AddImages2D, + SceneConverterFeature::AddImages3D, + SceneConverterFeature::AddCompressedImages1D, + SceneConverterFeature::AddCompressedImages2D, + SceneConverterFeature::AddCompressedImages3D, + SceneConverterFeature::MeshLevels, + SceneConverterFeature::ImageLevels}); } Debug& operator<<(Debug& debug, const SceneConverterFlag value) { diff --git a/src/Magnum/Trade/AbstractSceneConverter.h b/src/Magnum/Trade/AbstractSceneConverter.h index f5cac1b5b..226df8035 100644 --- a/src/Magnum/Trade/AbstractSceneConverter.h +++ b/src/Magnum/Trade/AbstractSceneConverter.h @@ -30,6 +30,7 @@ * @m_since{2020,06} */ +#include #include #include "Magnum/Magnum.h" @@ -53,31 +54,214 @@ namespace Magnum { namespace Trade { @see @ref SceneConverterFeatures, @ref AbstractSceneConverter::features() */ -enum class SceneConverterFeature: UnsignedByte { +enum class SceneConverterFeature: UnsignedInt { /** - * Convert a mesh with - * @ref AbstractSceneConverter::convert(const MeshData&). + * Convert a single mesh instance with + * @ref AbstractSceneConverter::convert(const MeshData&). The function can + * be also used if both @ref SceneConverterFeature::ConvertMultiple and + * @ref SceneConverterFeature::AddMeshes are supported. */ ConvertMesh = 1 << 0, /** - * Convert a mesh in-place with + * Convert a single mesh instance in-place with * @ref AbstractSceneConverter::convertInPlace(MeshData&). */ ConvertMeshInPlace = 1 << 1, /** - * Converting a mesh to a file with - * @ref AbstractSceneConverter::convertToFile(const MeshData&, Containers::StringView). + * Convert a single mesh instance to a file with + * @ref AbstractSceneConverter::convertToFile(const MeshData&, Containers::StringView). The function can be also used if both + * @ref SceneConverterFeature::ConvertMultipleToFile and + * @ref SceneConverterFeature::AddMeshes are supported. */ ConvertMeshToFile = 1 << 2, /** - * Converting a mesh to raw data with + * Convert a single mesh instance to raw data with * @ref AbstractSceneConverter::convertToData(const MeshData&). Implies - * @ref SceneConverterFeature::ConvertMeshToFile. + * @ref SceneConverterFeature::ConvertMeshToFile. The function can be also + * used if both @ref SceneConverterFeature::ConvertMultipleToData and + * @ref SceneConverterFeature::AddMeshes are supported. */ - ConvertMeshToData = ConvertMeshToFile|(1 << 3) + ConvertMeshToData = ConvertMeshToFile|(1 << 3), + + /** + * Convert multiple data with + * @ref AbstractSceneConverter::begin() and + * @relativeref{AbstractSceneConverter,end()}. + * @m_since_latest + */ + ConvertMultiple = 1 << 4, + + /** + * Convert multiple data to a file with + * @ref AbstractSceneConverter::beginFile() and + * @relativeref{AbstractSceneConverter,endFile()}. The functions can be + * also used if @ref SceneConverterFeature::ConvertMeshToFile is supported. + * @m_since_latest + */ + ConvertMultipleToFile = 1 << 5, + + /** + * Convert multiple data to raw data with + * @ref AbstractSceneConverter::beginData() and + * @relativeref{AbstractSceneConverter,endData()}. Implies + * @ref SceneConverterFeature::ConvertMultipleToFile. The functions can be + * also used if @ref SceneConverterFeature::ConvertMeshToData is supported. + * @m_since_latest + */ + ConvertMultipleToData = ConvertMultipleToFile|(1 << 6), + + /** + * Add scene instances with + * @ref AbstractSceneConverter::add(const SceneData&, Containers::StringView), + * together with @relativeref{AbstractSceneConverter,setSceneFieldName()}, + * @relativeref{AbstractSceneConverter,setObjectName()} and + * @relativeref{AbstractSceneConverter,setDefaultScene()}. + * @m_since_latest + */ + AddScenes = 1 << 7, + + /** + * Add animation instances with + * @ref AbstractSceneConverter::add(const AnimationData&, Containers::StringView). + * @m_since_latest + */ + AddAnimations = 1 << 8, + + /** + * Add light instances with + * @ref AbstractSceneConverter::add(const LightData&, Containers::StringView). + * @m_since_latest + */ + AddLights = 1 << 9, + + /** + * Add camera instances with + * @ref AbstractSceneConverter::add(const CameraData&, Containers::StringView). + * @m_since_latest + */ + AddCameras = 1 << 10, + + /** + * Add 2D skin instances with + * @ref AbstractSceneConverter::add(const SkinData2D&, Containers::StringView). + * @m_since_latest + */ + AddSkins2D = 1 << 11, + + /** + * Add 3D skin instances with + * @ref AbstractSceneConverter::add(const SkinData3D&, Containers::StringView). + * @m_since_latest + */ + AddSkins3D = 1 << 12, + + /** + * Add single-level mesh instances with + * @ref AbstractSceneConverter::add(const MeshData&, Containers::StringView), + * together with @relativeref{AbstractSceneConverter,setMeshAttributeName()}. + * This function can be also used if + * @ref SceneConverterFeature::ConvertMesh, + * @ref SceneConverterFeature::ConvertMeshToFile or + * @ref SceneConverterFeature::ConvertMeshToData is supported. + * @m_since_latest + * @see @ref SceneConverterFeature::MeshLevels + */ + AddMeshes = 1 << 13, + + /** + * Add material instances with + * @ref AbstractSceneConverter::add(const MaterialData&, Containers::StringView). + * @m_since_latest + */ + AddMaterials = 1 << 14, + + /** + * Add texture instances with + * @ref AbstractSceneConverter::add(const TextureData&, Containers::StringView). + * @m_since_latest + */ + AddTextures = 1 << 15, + + /** + * Add single-level 1D image instances with + * @ref AbstractSceneConverter::add(const ImageData1D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const ImageView1D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddImages1D = 1 << 16, + + /** + * Add single-level 2D image instances with + * @ref AbstractSceneConverter::add(const ImageData2D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const ImageView2D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddImages2D = 1 << 17, + + /** + * Add single-level 3D image instances with + * @ref AbstractSceneConverter::add(const ImageData3D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const ImageView3D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddImages3D = 1 << 18, + + /** + * Add single-level compressed 1D image instances with + * @ref AbstractSceneConverter::add(const ImageData1D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const CompressedImageView1D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddCompressedImages1D = 1 << 19, + + /** + * Add single-level compressed 2D image instances with + * @ref AbstractSceneConverter::add(const ImageData2D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const CompressedImageView2D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddCompressedImages2D = 1 << 20, + + /** + * Add single-level compressed 3D image instances with + * @ref AbstractSceneConverter::add(const ImageData3D&, Containers::StringView) + * or @ref AbstractSceneConverter::add(const CompressedImageView3D&, Containers::StringView). + * @m_since_latest + * @see @ref SceneConverterFeature::ImageLevels + */ + AddCompressedImages3D = 1 << 21, + + /** + * Add multiple mesh levels with + * @ref AbstractSceneConverter::add(Containers::Iterable, Containers::StringView) + * if @ref SceneConverterFeature::AddMeshes is also supported. + * @m_since_latest + */ + MeshLevels = 1 << 22, + + /** + * Add multiple image levels with + * @ref AbstractSceneConverter::add(Containers::Iterable, Containers::StringView) + * if @ref SceneConverterFeature::AddImages1D or + * @relativeref{SceneConverterFeature,AddCompressedImages1D} is also + * supported; with @ref AbstractSceneConverter::add(Containers::Iterable, Containers::StringView) + * if @ref SceneConverterFeature::AddImages2D or + * @relativeref{SceneConverterFeature,AddCompressedImages2D} is also + * supported; or with @ref AbstractSceneConverter::add(Containers::Iterable, Containers::StringView) + * if @ref SceneConverterFeature::AddImages3D or + * @relativeref{SceneConverterFeature,AddCompressedImages3D} is also + * supported. + * @m_since_latest + */ + ImageLevels = 1 << 23 }; /** @@ -162,15 +346,16 @@ namespace Implementation { Provides functionality for converting meshes and other scene data between various formats or performing optimizations and other operations on them. -The interface supports three main kinds of operation, with implementations -advertising support for a subset of them via @ref features(): +The interface provides a direct and a batch interface, with implementations +advertising support for a subset of them via @ref features(). The direct +interface has three main kinds of operation: - Saving a mesh to a file / data using @ref convertToFile(const MeshData&, Containers::StringView) / @ref convertToData(const MeshData&). This is mostly for exporting the mesh data to a common format like OBJ or PLY in order to be used with an external tool. Advertised with @ref SceneConverterFeature::ConvertMeshToFile - or @ref SceneConverterFeature::ConvertMeshToData + / @relativeref{SceneConverterFeature,ConvertMeshToData}. - Performing an operation on the mesh data itself using @ref convert(const MeshData&), from which you get a @ref MeshData again. This includes operations like mesh decimation or topology cleanup. @@ -181,23 +366,48 @@ advertising support for a subset of them via @ref features(): shuffle the data around. Advertised with @ref SceneConverterFeature::ConvertMeshInPlace. +The batch interface allows converting a whole scene consisting of multiple +meshes, materials, images, ... instead of a single mesh. Similarly, it has two +main kinds of operation: + +- Saving a scene to a file / data using @ref beginFile() / @ref beginData() + and @ref endFile() / @ref endData(). This is the usual process for + exporting a whole scene to file formats such as glTF. Advertised with + @ref SceneConverterFeature::ConvertMultipleToFile / + @relativeref{SceneConverterFeature,ConvertMultipleToData}. +- Performing an operation on the whole scene using @ref begin() and + @ref end(), getting a live @ref AbstractImporter instance as a result. This + includes more complex operations such as scale-aware mesh decimation or + texture downsampling, joining meshes with the same material, or exploding + large meshes into smaller and easier to cull chunks. Advertised with + @ref SceneConverterFeature::ConvertMultiple. + +The actual data is then supplied to the converter in between the begin and end +calls using various @ref add() overloads and related APIs. Support for +particular data types is then advertised with +@ref SceneConverterFeature::AddScenes, +@relativeref{SceneConverterFeature,AddMeshes}, +@relativeref{SceneConverterFeature,AddCameras}, +@relativeref{SceneConverterFeature,AddImages2D} etc. + @section Trade-AbstractSceneConverter-usage Usage Scene converters are commonly implemented as plugins, which means the concrete converter implementation is loaded and instantiated through a @relativeref{Corrade,PluginManager::Manager}. Then, based on the intent and on -what the particular converter supports, @ref convertToFile(), -@ref convertToData(), @ref convert() or @ref convertInPlace() gets called. +what the particular converter supports, either a single-mesh conversion with +@ref convert() and related functions is performed, or a whole-scene conversion +with @ref begin(), various @ref add() APIs and @ref end() is done. -As each converter has different requirements on the input data layout and -vertex formats, you're expected to perform error handling on the application -side --- if a conversion fails, you get an empty +As each converter has different requirements on the input data, their layout +and formats, you're expected to perform error handling on the application side +--- if a conversion fails, you get an empty @relativeref{Corrade,Containers::Optional} or @cpp false @ce and a reason printed to @relativeref{Magnum,Error}. Everything else (using a feature not implemented in the converter, ...) is treated as a programmer error and will produce the usual assertions. -@subsection Trade-AbstractSceneConverter-usage-file Saving a mesh to a file +@subsection Trade-AbstractSceneConverter-usage-mesh-file Converting a single mesh to a file In the following example a mesh is saved to a PLY file using the @ref AnySceneConverter plugin, together with all needed error handling. In this @@ -219,7 +429,7 @@ scene converter plugins. that exposes functionality of all scene converter plugins through a command line interface. -@subsection Trade-AbstractSceneConverter-usage-mesh Converting mesh data +@subsection Trade-AbstractSceneConverter-usage-mesh-data Converting a single mesh data In the following snippet we use the @ref MeshOptimizerSceneConverter to perform a set of optimizations on the mesh to make it render faster. While @@ -234,7 +444,7 @@ of configuration options to specify what actually gets done and how, and the default setup may not even do anything. See @ref plugins-configuration for details and a usage example. -@subsection Trade-AbstractSceneConverter-usage-mesh-in-place Converting mesh data in-place +@subsection Trade-AbstractSceneConverter-usage-mesh-in-place Converting a single mesh data in-place Certain operations such as buffer reordering can be performed by directly modifying the input data instead of having to allocate a copy of the whole @@ -249,6 +459,16 @@ following: @snippet MagnumTrade.cpp AbstractSceneConverter-usage-mesh-in-place +@subsection Trade-AbstractSceneConverter-usage-multiple Converting multiple data + +While the operations shown above are convenient enough for simple cases +involving just a single mesh, general conversion of a whole scene needs much +more than that. Such conversion is done in a batch way --- first initializing +the conversion of desired kind, adding particular data, and finalizing the +conversion together with getting the output back. + +@todoc usage example once a batch converter exists + @section Trade-AbstractSceneConverter-data-dependency Data dependency The instances returned from various functions *by design* have no dependency on @@ -263,8 +483,11 @@ plugin module has been unloaded. @section Trade-AbstractSceneConverter-subclassing Subclassing The plugin needs to implement the @ref doFeatures() function and one or more of -@ref doConvert(), @ref doConvertInPlace(), @ref doConvertToData() or -@ref doConvertToFile() functions based on what features are supported. +@ref doConvert(), @ref doConvertInPlace(), @ref doConvertToData(), +@ref doConvertToFile() functions based on what single-mesh conversion features +are supported, or pairs of @ref doBegin() / @ref doEnd(), @ref doBeginData() / +@ref doEndData(), @ref doBeginFile() / @ref doEndFile() functions and one or +more @ref doAdd() functions based on what multiple-data features are supported. You don't need to do most of the redundant sanity checks, these things are checked by the implementation: @@ -278,6 +501,58 @@ checked by the implementation: - The @ref doConvertToFile(const MeshData&, Containers::StringView) function is called only if @ref SceneConverterFeature::ConvertMeshToFile is supported. +- The @ref doBegin() and @ref doEnd() functions are called only if + @ref SceneConverterFeature::ConvertMultiple is supported. +- The @ref doBeginData() and @ref doEndData() functions are called only if + @ref SceneConverterFeature::ConvertMultipleToData is supported. +- The @ref doBeginFile() and @ref doEndFile() functions are called only if + @ref SceneConverterFeature::ConvertMultipleToFile is supported. +- The @ref doEnd(), @ref doEndData(), @ref doEndFile(), @ref doAbort() and + @ref doAdd() functions are called only if a corresponding @ref begin(), + @ref beginData() or @ref beginFile() was called before and @ref abort() + wasn't called in the meantime. +- The @ref doAdd() and various `doSet*()` functions are called only if a + corresponding @ref SceneConverterFeature is supported. +- The @ref doAdd((UnsignedInt, Containers::Iterable, Containers::StringView) + function is called only if the list has at least one mesh +- All @ref doAdd() functions taking a single image are called only if the + image has a non-zero size in all dimensions and the data is not + @cpp nullptr @ce. +- All @ref doAdd() functions taking multiple images are called only if the + list has at least one image, each of the images has a non-zero size, the + data are not @cpp nullptr @ce and additionally all images are either + uncompressed or all compressed and they have the same pixel format. + Since file formats have varying requirements on image level sizes and their + order and some don't impose any requirements at all, the plugin + implementation is expected to check the sizes on its own. + +For user convenience it's possible to use a single-mesh converter through the +multi-mesh interface as well as use a multi-mesh converter through the +single-mesh interface. The base class can proxy one to the other and does all +necessary edge-case checks: + +- If you have a multi-mesh interface implemented using @ref doBeginFile() / + @ref doBeginData(), @ref doEndFile() / @ref doEndData() and + @ref doAdd(UnsignedInt, const MeshData&, Containers::StringView), it's + automatically delegated to from + @ref convertToFile(const MeshData&, Containers::StringView) + and @ref convertToData(const MeshData&). +- If you have a single-mesh interface implemented using + @ref doConvert(const MeshData&) / @ref doConvertToFile(const MeshData&, Containers::StringView) + / @ref doConvertToData(const MeshData&), it's automatically delegated to + from @ref begin() / @ref beginFile() / @ref beginData(), + @ref end() / @ref endFile() / @ref endData() and + @ref add(const MeshData&, Containers::StringView), succeeding only if + exactly one mesh is added. +- As an exception, a multi-mesh interface implemented using @ref doBegin() + and @ref doEnd() can't be automatically delegated to from + @ref convert(const MeshData&), as the returned @ref AbstractImporter + instance is allowed to have any number of meshes and the delegation logic + would be too complex. In that case, you need to implement + @ref doConvert(const MeshData&) and advertise + @ref SceneConverterFeature::ConvertMesh in @ref doFeatures() yourself. + + @m_class{m-block m-warning} @@ -289,6 +564,10 @@ checked by the implementation: could cause dangling function pointer call on array destruction if the plugin gets unloaded before the array is destroyed. This is asserted by the base implementation on return. +@par + The only exception is the @ref AbstractImporter instance returned by + @ref end() --- since its implementation is in the plugin module itself, the + plugin can't be unloaded until the returned instance is destroyed. */ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::AbstractManagingPlugin { public: @@ -373,11 +652,15 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract /** * @brief Convert a mesh * - * Depending on the plugin, can perform for example vertex format - * conversion, overdraw optimization or decimation / subdivision. - * Available only if @ref SceneConverterFeature::ConvertMesh is - * supported. On failure prints a message to @relativeref{Magnum,Error} - * and returns @ref Containers::NullOpt. + * On failure prints a message to @relativeref{Magnum,Error} and + * returns @ref Containers::NullOpt. + * + * Expects that @ref SceneConverterFeature::ConvertMesh is supported. + * If @ref SceneConverterFeature::AddMeshes is supported instead, you + * have to use @ref begin(), @ref add(const MeshData&, Containers::StringView) + * and retrieve the output from the importer returned by @ref end() --- + * in such case the process can also return zero or more than one mesh + * instead of always exactly one. * @see @ref features(), @ref convertInPlace(MeshData&) */ Containers::Optional convert(const MeshData& mesh); @@ -385,11 +668,11 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract /** * @brief Convert a mesh in-place * - * Depending on the plugin, can perform for example index buffer - * reordering for better vertex cache use or overdraw optimization. - * Available only if @ref SceneConverterFeature::ConvertMeshInPlace is - * supported. On failure prints a message to @relativeref{Magnum,Error} - * and returns @cpp false @ce, @p mesh is guaranteed to stay unchanged. + * On failure prints a message to @relativeref{Magnum,Error} and + * returns @cpp false @ce, @p mesh is guaranteed to stay unchanged. + * + * Expects that @ref SceneConverterFeature::ConvertMeshInPlace is + * supported. * @see @ref features(), @ref convert(const MeshData&) */ bool convertInPlace(MeshData& mesh); @@ -397,11 +680,15 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract /** * @brief Convert a mesh to a raw data * - * Depending on the plugin, can convert the mesh to a file format that - * can be saved to disk. Available only if - * @ref SceneConverterFeature::ConvertMeshToData is supported. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @ref Containers::NullOpt. + * On failure prints a message to @relativeref{Magnum,Error} and + * returns @ref Containers::NullOpt. + * + * Expects that @ref SceneConverterFeature::ConvertMeshToData is + * supported. If not and both + * @ref SceneConverterFeature::ConvertMultipleToData and + * @ref SceneConverterFeature::AddMeshes is supported instead, + * delegates to a sequence of @ref beginData(), + * @ref add(const MeshData&, Containers::StringView) and @ref endData(). * @see @ref features(), @ref convertToFile() */ #if !defined(MAGNUM_BUILD_DEPRECATED) || defined(DOXYGEN_GENERATING_OUTPUT) @@ -415,10 +702,16 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract * @brief Convert a mesh to a file * @m_since_latest * - * Available only if @ref SceneConverterFeature::ConvertMeshToFile or - * @ref SceneConverterFeature::ConvertMeshToData is supported. On - * failure prints a message to @relativeref{Magnum,Error} and returns - * @cpp false @ce. + * On failure prints a message to @relativeref{Magnum,Error} and + * returns @cpp false @ce. + * + * Expects that @ref SceneConverterFeature::ConvertMeshToFile is + * supported. If not and both + * @ref SceneConverterFeature::ConvertMultipleToFile and + * @ref SceneConverterFeature::AddMeshes is supported instead, + * delegates to a sequence of @ref beginFile(), + * @ref add(const MeshData&, Containers::StringView) and + * @ref endFile(). * @see @ref features(), @ref convertToData() */ bool convertToFile(const MeshData& mesh, Containers::StringView filename); @@ -432,52 +725,1373 @@ class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::Abstract CORRADE_DEPRECATED("use convertToFile(const MeshData&, Containers::StringView) instead") bool convertToFile(const std::string& filename, const MeshData& mesh); #endif - protected: /** - * @brief Implementation for @ref convertToFile(const MeshData&, Containers::StringView) + * @brief Whether any conversion is in progress + * @m_since_latest * - * If @ref SceneConverterFeature::ConvertMeshToData is supported, - * default implementation calls @ref doConvertToData(const MeshData&) - * and saves the result to given file. It is allowed to call this - * function from your @ref doConvertToFile() implementation, for - * example when you only need to do format detection based on file - * extension. + * Returns @cpp true @ce if any conversion started by @ref begin(), + * @ref beginData() or @ref beginFile() has not ended yet and + * @ref abort() wasn't called; @cpp false @ce otherwise. */ - virtual bool doConvertToFile(const MeshData& mesh, Containers::StringView filename); + bool isConverting() const; - private: /** - * @brief Implementation for @ref features() + * @brief Abort any in-progress conversion + * @m_since_latest * - * The implementation is expected to support at least one feature. + * On particular implementations an explicit call to this function may + * result in freed memory. If no conversion is currently in progress, + * does nothing. After this function is called, @ref isConverting() + * returns @cpp false @ce. */ - virtual SceneConverterFeatures doFeatures() const = 0; + void abort(); /** - * @brief Implementation for @ref setFlags() + * @brief Begin converting a scene + * @m_since_latest * - * Useful when the converter needs to modify some internal state on - * flag setup. Default implementation does nothing and this - * function doesn't need to be implemented --- the flags are available - * through @ref flags(). + * If a conversion is currently in progress, calls @ref abort() first. + * The converted output of data supplied via various @ref add() and + * `set*()` APIs is returned via an importer instance upon calling + * @ref end(). * - * To reduce the amount of error checking on user side, this function - * isn't expected to fail --- if a flag combination is invalid / - * unsuported, error reporting should be delayed to various conversion - * functions, where the user is expected to do error handling anyway. + * Expects that @ref SceneConverterFeature::ConvertMultiple is + * supported. If not and @ref SceneConverterFeature::ConvertMesh is + * supported instead, sets up internal state in order to delegate + * @ref add(const MeshData&, Containers::StringView) to + * @ref convert(const MeshData&) and return the result from + * @ref end(). + * @see @ref features(), @ref beginData(), @ref beginFile() */ - virtual void doSetFlags(SceneConverterFlags flags); + void begin(); - /** @brief Implementation for @ref convert(const MeshData&) */ - virtual Containers::Optional doConvert(const MeshData& mesh); + /** + * @brief End converting a scene + * @m_since_latest + * + * Expects that @ref begin() was called before. The returned + * @ref AbstractImporter may contain arbitrary amounts of data + * depending on the particular converter plugin. On failure prints a + * message to @relativeref{Magnum,Error} and returns @cpp nullptr @ce. + * + * @m_class{m-note m-warning} + * + * @par Data dependency + * The returned importer instance is fully self-contained, with no + * dependency on the originating converter instance or its + * internal state, meaning you can immediately reuse the instance + * for another conversion without corrupting the importer instance + * returned earlier. + * @par + * Its *code*, however, is coming from the plugin binary and thus + * the plugin should not be unloaded (or its originating + * @relativeref{Corrade,PluginManager::Manager} destroyed) before + * the importer instance is destroyed. + * + * If @ref SceneConverterFeature::ConvertMultiple is not supported and + * @ref SceneConverterFeature::ConvertMesh is supported instead, + * returns an importer exposing a single mesh that was converted via + * the @ref add(const MeshData&, Containers::StringView) call, which + * delegated to @ref convert(const MeshData&). To simplify data + * ownership logic, the mesh can be extracted from the returned + * importer only once, subsequent calls to @ref AbstractImporter::mesh() + * will fail. If no mesh was added, prints a message to + * @relativeref{Magnum,Error} and returns @cpp nullptr @ce. + */ + Containers::Pointer end(); - /** @brief Implementation for @ref convertInPlace(MeshData&) */ - virtual bool doConvertInPlace(MeshData& mesh); + /** + * @brief Begin converting a scene to raw data + * @m_since_latest + * + * If a conversion is currently in progress, calls @ref abort() first. + * The converted output of data supplied via various @ref add() and + * `set*()` APIs is returned upon calling @ref endData(). + * + * Expects that @ref SceneConverterFeature::ConvertMultipleToData is + * supported. If not and @ref SceneConverterFeature::ConvertMeshToData + * is supported instead, sets up internal state in order to delegate + * @ref add(const MeshData&, Containers::StringView) to + * @ref convertToData(const MeshData&) and return the result from + * @ref endData(). + * @see @ref features(), @ref begin(), @ref beginFile() + */ + void beginData(); - /** @brief Implementation for @ref convertToData(const MeshData&) */ - virtual Containers::Optional> doConvertToData(const MeshData& mesh); + /** + * @brief End converting a scene to raw data + * @m_since_latest + * + * Expects that @ref beginData() was called before. On failure prints a + * message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt. + * + * If @ref SceneConverterFeature::ConvertMultipleToData is not + * supported and @ref SceneConverterFeature::ConvertMeshToData is + * supported instead, returns data converted via the + * @ref add(const MeshData&, Containers::StringView) call, which + * delegated to @ref convertToData(const MeshData&). If no mesh was + * added, prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt. + */ + Containers::Optional> endData(); + + /** + * @brief Begin converting a scene to a file + * @m_since_latest + * + * If a conversion is currently in progress, calls @ref abort() first. + * The converted output of data supplied via various @ref add() and + * `set*()` APIs is returned upon calling @ref endFile(). + * + * Expects that @ref SceneConverterFeature::ConvertMultipleToFile is + * supported. If not and @ref SceneConverterFeature::ConvertMeshToFile + * is supported instead, sets up internal state in order to delegate + * @ref add(const MeshData&, Containers::StringView) to + * @ref convertToFile(const MeshData&, Containers::StringView) and + * return the result from @ref endFile(). + * @see @ref features(), @ref begin(), @ref beginData() + */ + void beginFile(Containers::StringView filename); + + /** + * @brief End converting a scene to raw data + * @m_since_latest + * + * Expects that @ref beginData() was called before. On failure prints a + * message to @relativeref{Magnum,Error} and returns @cpp false @ce. + * + * If @ref SceneConverterFeature::ConvertMultipleToFile is not + * supported and @ref SceneConverterFeature::ConvertMeshToFile is + * supported instead, returns a result of the + * @ref add(const MeshData&, Containers::StringView) call, which + * delegated to @ref convertToFile(const MeshData&, Containers::StringView). + * If no mesh was added, prints a message to @relativeref{Magnum,Error} + * and returns @ref Containers::NullOpt. + */ + bool endFile(); + + /** + * @brief Count of added scenes + * @m_since_latest + * + * Count of scenes successfully added with + * @ref add(const SceneData&, Containers::StringView) since the initial + * @ref begin(), @ref beginData() or @ref beginFile() call. Expects + * that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddScenes is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt sceneCount() const; + + /** + * @brief Add a scene + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddScenes is supported. The returned ID + * is implicitly equal to @ref sceneCount() before calling this + * function and can be subsequently used in functions like + * @ref setDefaultScene(). On failure prints a message to + * @relativeref{Magnum,Error} and returns @ref Containers::NullOpt --- + * the count of added animations doesn't change in that case. + * + * If the converter doesn't support scene naming, @p name is ignored. + * + * Because a scene directly or indirectly references majority of other + * data, it's recommended to be added only after all data it uses are + * added as well. Particular converter plugins may have looser + * requirements, but adding it last guarantees that the conversion + * process doesn't fail due to the scene referencing yet-unknown data. + * @see @ref isConverting(), @ref features(), + * @ref setSceneFieldName(), @ref setObjectName(), + * @ref setDefaultScene() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const SceneData& data, Containers::StringView name = {}); + #else + Containers::Optional add(const SceneData& data, Containers::StringView name); + Containers::Optional add(const SceneData& data); + #endif + + /** + * @brief Set name of a custom scene field + * @m_since_latest + * + * Expects that a conversion is currently in progress, + * @ref SceneConverterFeature::AddScenes is supported and @p field is a + * custom field. The field name will get used only for scene data added + * after this function has been called. If the converter doesn't + * support custom scene fields or doesn't support naming them, the call + * is ignored. + * @see @ref isConverting(), @ref features(), @ref isSceneFieldCustom(), + * @ref setMeshAttributeName() + */ + void setSceneFieldName(SceneField field, Containers::StringView name); + + /** + * @brief Set object name + * @m_since_latest + * + * Expects that a conversion is currently in progress, + * @ref SceneConverterFeature::AddScenes is supported and @p field is a + * custom field. The object name will get used only for scene data + * added after this function has been called. If the converter doesn't + * support naming objects, the call is ignored. + * @see @ref isConverting(), @ref features() + */ + void setObjectName(UnsignedLong object, Containers::StringView name); + + /** + * @brief Set default scene + * @m_since_latest + * + * Expects that a conversion is currently in progress, + * @ref SceneConverterFeature::AddScenes is supported and + * @ref sceneCount() is greater than @p id. If the converter doesn't + * support multiple scenes or default scene selection, the call is + * ignored. + * @see @ref isConverting(), @ref features() + */ + void setDefaultScene(UnsignedInt id); + + /** + * @brief Count of added animations + * @m_since_latest + * + * Count of animations successfully added with + * @ref add(const AnimationData&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddAnimations is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt animationCount() const; + + /** + * @brief Add an animation + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddAnimations is supported. The returned + * ID is implicitly equal to @ref animationCount() before calling this + * function. On failure prints a message to @relativeref{Magnum,Error} + * and returns @ref Containers::NullOpt --- the count of added + * animations doesn't change in that case. + * + * If the converter doesn't support animation naming, @p name is + * ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const AnimationData& animation, Containers::StringView name = {}); + #else + Containers::Optional add(const AnimationData& animation, Containers::StringView name); + Containers::Optional add(const AnimationData& animation); + #endif + + /** + * @brief Count of added lights + * @m_since_latest + * + * Count of lights successfully added with + * @ref add(const LightData&, Containers::StringView) since the initial + * @ref begin(), @ref beginData() or @ref beginFile() call. Expects + * that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddLights is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt lightCount() const; + + /** + * @brief Add a light + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddLights is supported. The returned ID + * is implicitly equal to @ref lightCount() before calling this + * function and can be subsequently used to for example reference a + * light from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added lights doesn't + * change in that case. + * + * If the converter doesn't support light naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const LightData& light, Containers::StringView name = {}); + #else + Containers::Optional add(const LightData& light, Containers::StringView name); + Containers::Optional add(const LightData& light); + #endif + + /** + * @brief Count of added cameras + * @m_since_latest + * + * Count of cameras successfully added with + * @ref add(const CameraData&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddCameras is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt cameraCount() const; + + /** + * @brief Add a camera + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddCameras is supported. The returned ID + * is implicitly equal to @ref cameraCount() before calling this + * function and can be subsequently used to for example reference a + * camera from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added cameras doesn't + * change in that case. + * + * If the converter doesn't support camera naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const CameraData& camera, Containers::StringView name = {}); + #else + Containers::Optional add(const CameraData& camera, Containers::StringView name); + Containers::Optional add(const CameraData& camera); + #endif + + /** + * @brief Count of added 2D skins + * @m_since_latest + * + * Count of skins successfully added with + * @ref add(const SkinData2D&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddSkins2D is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt skin2DCount() const; + + /** + * @brief Add a 2D skin + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddSkins2D is supported. The returned ID + * is implicitly equal to @ref skin2DCount() before calling this + * function. On failure prints a message to @relativeref{Magnum,Error} + * and returns @ref Containers::NullOpt --- the count of added skins + * doesn't change in that case. + * + * If the converter doesn't support skin naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const SkinData2D& skin, Containers::StringView name = {}); + #else + Containers::Optional add(const SkinData2D& skin, Containers::StringView name); + Containers::Optional add(const SkinData2D& skin); + #endif + + /** + * @brief Count of added 3D skins + * @m_since_latest + * + * Count of skins successfully added with + * @ref add(const SkinData3D&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddSkins3D is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt skin3DCount() const; + + /** + * @brief Add a 3D skin + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddSkins3D is supported. The returned ID + * is implicitly equal to @ref skin3DCount() before calling this + * function. On failure prints a message to @relativeref{Magnum,Error} + * and returns @ref Containers::NullOpt --- the count of added skins + * doesn't change in that case. + * + * If the converter doesn't support skin naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const SkinData3D& skin, Containers::StringView name = {}); + #else + Containers::Optional add(const SkinData3D& skin, Containers::StringView name); + Containers::Optional add(const SkinData3D& skin); + #endif + + /** + * @brief Count of added meshes + * @m_since_latest + * + * Count of meshes successfully added with + * @ref add(const MeshData&, Containers::StringView) or + * @ref add(Containers::Iterable, Containers::StringView) + * since the initial @ref begin(), @ref beginData() or @ref beginFile() + * call. Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddMeshes is not supported and only the + * singular @relativeref{SceneConverterFeature,ConvertMesh}, + * @relativeref{SceneConverterFeature,ConvertMeshToData} or + * @relativeref{SceneConverterFeature,ConvertMeshToFile} is supported, + * returns always either @cpp 0 @ce or @cpp 1 @ce. Otherwise returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt meshCount() const; + + /** + * @brief Add a mesh + * @m_since_latest + * + * Expects that a conversion is currently in progress and either + * @ref SceneConverterFeature::AddMeshes, + * @relativeref{SceneConverterFeature,ConvertMesh}, + * @relativeref{SceneConverterFeature,ConvertMeshToData} or + * @relativeref{SceneConverterFeature,ConvertMeshToFile} is + * supported. The returned ID is implicitly equal to @ref meshCount() + * before calling this function and can be subsequently used to for + * example reference a mesh from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added meshes doesn't + * change in that case. + * + * If the converter doesn't support mesh naming, @p name is ignored. + * + * If only the singular @ref SceneConverterFeature::ConvertMesh, + * @relativeref{SceneConverterFeature,ConvertMeshToData} or + * @relativeref{SceneConverterFeature,ConvertMeshToFile} is supported, + * the function can be called only exactly once to successfully produce + * an output, and the process is equivalent to + * @ref convert(const MeshData&), + * @ref convertToData(const MeshData&) or + * @ref convertToFile(const MeshData&, Containers::StringView), with + * the @p name ignored. + * @see @ref isConverting(), @ref features(), + * @ref setMeshAttributeName() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const MeshData& mesh, Containers::StringView name = {}); + #else + Containers::Optional add(const MeshData& mesh, Containers::StringView name); + Containers::Optional add(const MeshData& mesh); + #endif + + /** + * @brief Add a set of mesh levels + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::MeshLevels together with + * @relativeref{SceneConverterFeature,AddMeshes} is supported. The + * returned ID is implicitly equal to @ref meshCount() before calling + * this function and can be subsequently used to for example reference + * a mesh from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView); all levels + * together are treated as a single mesh. On failure prints a message + * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt + * --- the count of added meshes doesn't change in that case. + * + * If the converter doesn't support mesh naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref setMeshAttributeName() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable meshLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable meshLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable meshLevels); + #endif + + /** + * @brief Set name of a custom mesh attribute + * @m_since_latest + * + * Expects that a conversion is currently in progress, + * @ref SceneConverterFeature::AddMeshes is supported and @p attribute + * is a custom attribute. The attribute name will get used only mesh + * data added after this function has been called. If the converter + * doesn't support custom mesh attributes or doesn't support naming + * them, the call is ignored. + * @see @ref isConverting(), @ref features(), + * @ref isMeshAttributeCustom(), @ref setSceneFieldName() + */ + void setMeshAttributeName(MeshAttribute attribute, Containers::StringView name); + + /** + * @brief Count of added materials + * @m_since_latest + * + * Count of materials successfully added with + * @ref add(const MaterialData&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddMaterials is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt materialCount() const; + + /** + * @brief Add a material + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddMaterials is supported. The returned + * ID is implicitly equal to @ref materialCount() before calling this + * function and can be subsequently used to for example reference a + * material from a @ref SceneData passed to + * @ref add(const SceneData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added materials doesn't + * change in that case. + * + * If the converter doesn't support material naming, @p name is + * ignored. + * + * Because a material directly or indirectly references textures and + * images, it's recommended to be added only after all data it uses + * are added as well. Particular converter plugins may have looser + * requirements, but adding it last guarantees that the conversion + * process doesn't fail due to the material referencing yet-unknown + * data. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const MaterialData& material, Containers::StringView name = {}); + #else + Containers::Optional add(const MaterialData& material, Containers::StringView name); + Containers::Optional add(const MaterialData& material); + #endif + + /** + * @brief Count of added textures + * @m_since_latest + * + * Count of textures successfully added with + * @ref add(const TextureData&, Containers::StringView) since the + * initial @ref begin(), @ref beginData() or @ref beginFile() call. + * Expects that a conversion is currently in progress. If + * @ref SceneConverterFeature::AddTextures is not supported, returns + * @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt textureCount() const; + + /** + * @brief Add a texture + * @m_since_latest + * + * Expects that a conversion is currently in progress and + * @ref SceneConverterFeature::AddTextures is supported. The returned + * ID is implicitly equal to @ref textureCount() before calling this + * function and can be subsequently used to for example reference a + * texture from a @ref MaterialData passed to + * @ref add(const MaterialData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added textures doesn't + * change in that case. + * + * If the converter doesn't support texture naming, @p name is ignored. + * + * Because a texture references an image, it's recommended to be added + * only after the image it uses is added as well. Particular converter + * plugins may have looser requirements, but adding it last guarantees + * that the conversion process doesn't fail due to the texture + * referencing yet-unknown data. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const TextureData& texture, Containers::StringView name = {}); + #else + Containers::Optional add(const TextureData& texture, Containers::StringView name); + Containers::Optional add(const TextureData& texture); + #endif + + /** + * @brief Count of added 1D images + * @m_since_latest + * + * Count of images successfully added with + * @ref add(const ImageData1D&, Containers::StringView) or + * @ref add(Containers::Iterable, Containers::StringView) + * and overloads since the initial @ref begin(), @ref beginData() or + * @ref beginFile() call. Expects that a conversion is currently in + * progress. If neither @ref SceneConverterFeature::AddImages1D nor + * @relativeref{SceneConverterFeature,AddCompressedImages1D} is + * supported, returns @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt image1DCount() const; + + /** + * @brief Add a 1D image + * @m_since_latest + * + * Expects that a conversion is currently in progress and either + * @ref SceneConverterFeature::AddImages1D or + * @relativeref{SceneConverterFeature,AddCompressedImages1D} is + * supported based on whether @p image is compressed. The image is + * expected to not be @cpp nullptr @ce and to have a non-zero size. The + * returned ID is implicitly equal to @ref image1DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added images doesn't + * change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref ImageData::isCompressed() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageData1D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageData1D& image, Containers::StringView name); + Containers::Optional add(const ImageData1D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageView1D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageView1D& image, Containers::StringView name); + Containers::Optional add(const ImageView1D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const CompressedImageView1D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const CompressedImageView1D& image, Containers::StringView name); + Containers::Optional add(const CompressedImageView1D& image); + #endif + + /** + * @brief Add a set of 1D image levels + * @m_since_latest + * + * The @p imageLevels are expected to have at least one image, with the + * images either all uncompressed or all compressed, none having + * @cpp nullptr @ce data or zero size in any dimension, and all sharing + * the same pixel format and layout flags. Expects that a conversion is + * currently in progress and + * @ref SceneConverterFeature::ImageLevels together with either + * @relativeref{SceneConverterFeature,AddImages1D} or + * @relativeref{SceneConverterFeature,AddCompressedImages1D} is + * supported based on whether @p imageLevels are compressed. The + * returned ID is implicitly equal to @ref image1DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView); all levels + * together are treated as a single image. On failure prints a message + * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt + * --- the count of added images doesn't change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref ImageData::isCompressed() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @brief Count of added 2D images + * @m_since_latest + * + * Count of images successfully added with + * @ref add(const ImageData2D&, Containers::StringView) or + * @ref add(Containers::Iterable, Containers::StringView) + * and overloads since the initial @ref begin(), @ref beginData() or + * @ref beginFile() call. Expects that a conversion is currently in + * progress. If neither @ref SceneConverterFeature::AddImages2D nor + * @relativeref{SceneConverterFeature,AddCompressedImages2D} is + * supported, returns @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt image2DCount() const; + + /** + * @brief Add a 2D image + * @m_since_latest + * + * Expects that a conversion is currently in progress and either + * @ref SceneConverterFeature::AddImages2D or + * @relativeref{SceneConverterFeature,AddCompressedImages2D} is + * supported based on whether @p image is compressed. The image is + * expected to not be @cpp nullptr @ce and to have a non-zero size. The + * returned ID is implicitly equal to @ref image2DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added images doesn't + * change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref ImageData::isCompressed() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageData2D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageData2D& image, Containers::StringView name); + Containers::Optional add(const ImageData2D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageView2D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageView2D& image, Containers::StringView name); + Containers::Optional add(const ImageView2D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const CompressedImageView2D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const CompressedImageView2D& image, Containers::StringView name); + Containers::Optional add(const CompressedImageView2D& image); + #endif + + /** + * @brief Add a set of 2D image levels + * @m_since_latest + * + * The @p imageLevels are expected to have at least one image, with the + * images either all uncompressed or all compressed, none having + * @cpp nullptr @ce data or zero size in any dimension, and all sharing + * the same pixel format and layout flags. Expects that a conversion is + * currently in progress and + * @ref SceneConverterFeature::ImageLevels together with either + * @relativeref{SceneConverterFeature,AddImages2D} or + * @relativeref{SceneConverterFeature,AddCompressedImages2D} is + * supported based on whether @p imageLevels are compressed. The + * returned ID is implicitly equal to @ref image2DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView); all levels + * together are treated as a single image. On failure prints a message + * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt + * --- the count of added images doesn't change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @brief Count of added 3D images + * @m_since_latest + * + * Count of images successfully added with + * @ref add(const ImageData3D&, Containers::StringView) or + * @ref add(Containers::Iterable, Containers::StringView) + * and overloads since the initial @ref begin(), @ref beginData() or + * @ref beginFile() call. Expects that a conversion is currently in + * progress. If neither @ref SceneConverterFeature::AddImages3D nor + * @relativeref{SceneConverterFeature,AddCompressedImages3D} is + * supported, returns @cpp 0 @ce. + * @see @ref isConverting(), @ref features() + */ + UnsignedInt image3DCount() const; + + /** + * @brief Add a 3D image + * @m_since_latest + * + * Expects that a conversion is currently in progress and either + * @ref SceneConverterFeature::AddImages3D or + * @relativeref{SceneConverterFeature,AddCompressedImages3D} is + * supported based on whether @p image is compressed. The image is + * expected to not be @cpp nullptr @ce and to have a non-zero size. The + * returned ID is implicitly equal to @ref image3DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView). On failure + * prints a message to @relativeref{Magnum,Error} and returns + * @ref Containers::NullOpt --- the count of added images doesn't + * change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features(), + * @ref ImageData::isCompressed() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageData3D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageData3D& image, Containers::StringView name); + Containers::Optional add(const ImageData3D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const ImageView3D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const ImageView3D& image, Containers::StringView name); + Containers::Optional add(const ImageView3D& image); + #endif + + /** + * @overload + * @m_since_latest + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(const CompressedImageView3D& image, Containers::StringView name = {}); + #else + Containers::Optional add(const CompressedImageView3D& image, Containers::StringView name); + Containers::Optional add(const CompressedImageView3D& image); + #endif + + /** + * @brief Add a set of 3D image levels + * @m_since_latest + * + * The @p imageLevels are expected to have at least one image, with the + * images either all uncompressed or all compressed, none having + * @cpp nullptr @ce data or zero size in any dimension, and all sharing + * the same pixel format and layout flags. Expects that a conversion is + * currently in progress and + * @ref SceneConverterFeature::ImageLevels together with either + * @relativeref{SceneConverterFeature,AddImages3D} or + * @relativeref{SceneConverterFeature,AddCompressedImages3D} is + * supported based on whether @p imageLevels are compressed. The + * returned ID is implicitly equal to @ref image3DCount() before + * calling this function and can be subsequently used to for example + * reference an image from a @ref TextureData passed to + * @ref add(const TextureData&, Containers::StringView); all levels + * together are treated as a single image. On failure prints a message + * to @relativeref{Magnum,Error} and returns @ref Containers::NullOpt + * --- the count of added images doesn't change in that case. + * + * If the converter doesn't support image naming, @p name is ignored. + * @see @ref isConverting(), @ref features() + */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + /** + * @overload + * @m_since_latest + */ + /* Could be taking an ArrayView together with a std::initializer_list + overload, but that'd make passing {ImageData} ambiguous because it + can convert to either ImageView or CompressedImageView. Using + Iterable for all three variants, even though only really needed to + allow passing an array of Reference, fixes the + ambiguity. */ + #ifdef DOXYGEN_GENERATING_OUTPUT + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name = {}); + #else + Containers::Optional add(Containers::Iterable imageLevels, Containers::StringView name); + Containers::Optional add(Containers::Iterable imageLevels); + #endif + + protected: + /** + * @brief Implementation for @ref convertToFile(const MeshData&, Containers::StringView) + * + * If @ref SceneConverterFeature::ConvertMeshToData is supported, + * default implementation calls @ref doConvertToData(const MeshData&) + * and saves the result to given file. It is allowed to call this + * function from your @ref doConvertToFile() implementation, for + * example when you only need to do format detection based on file + * extension. + * + * Otherwise, if both @ref SceneConverterFeature::ConvertMultipleToFile + * and @ref SceneConverterFeature::AddMeshes is supported, default + * implementation calls @ref beginData(), + * @ref add(const MeshData&, Containers::StringView) and returns the + * output of @ref endData(), or @ref Containers::NullOpt if any of + * those failed. + */ + virtual bool doConvertToFile(const MeshData& mesh, Containers::StringView filename); + + /** + * @brief Implementation for @ref beginFile() + * @m_since_latest + * + * If @ref SceneConverterFeature::ConvertMultipleToData is supported, + * default implementation delegates to @ref doBeginData(). + * + * It is allowed to call this function from your @ref doBeginFile() + * implementation, for example when you only need to do format + * detection based on file extension. + * + * The @p filename string is guaranteed to stay in scope until a call + * to @ref doEndFile(). It's not guaranteed to be + * @relativeref{Corrade,Containers::StringViewFlag::Global} or + * @relativeref{Corrade,Containers::StringViewFlag::NullTerminated}, + * however. + */ + virtual void doBeginFile(Containers::StringView filename); + + /** + * @brief Implementation for @ref endFile() + * @m_since_latest + * + * Receives the same @p filename as was passed to @ref doBeginFile() + * earlier. Expected to save the output data and reset the internal + * state for a potential new conversion to happen. + * + * If @ref SceneConverterFeature::ConvertMultipleToData is supported, + * default implementation calls @ref doEndData() and saves the result + * to given file. + * + * It is allowed to call this function from your @ref doEndFile() + * implementation, for example when you only need to do format + * detection based on file extension. + */ + virtual bool doEndFile(Containers::StringView filename); + + private: + struct State; + + /** + * @brief Implementation for @ref features() + * + * The implementation is expected to support at least one feature. + */ + virtual SceneConverterFeatures doFeatures() const = 0; + + /** + * @brief Implementation for @ref setFlags() + * + * Useful when the converter needs to modify some internal state on + * flag setup. Default implementation does nothing and this + * function doesn't need to be implemented --- the flags are available + * through @ref flags(). + * + * To reduce the amount of error checking on user side, this function + * isn't expected to fail --- if a flag combination is invalid / + * unsuported, error reporting should be delayed to various conversion + * functions, where the user is expected to do error handling anyway. + */ + virtual void doSetFlags(SceneConverterFlags flags); + + /** @brief Implementation for @ref convert(const MeshData&) */ + virtual Containers::Optional doConvert(const MeshData& mesh); + + /** @brief Implementation for @ref convertInPlace(MeshData&) */ + virtual bool doConvertInPlace(MeshData& mesh); + + /** + * @brief Implementation for @ref convertToData(const MeshData&) + * + * If both @ref SceneConverterFeature::ConvertMultipleToData and + * @ref SceneConverterFeature::AddMeshes is supported, default + * implementation calls @ref beginData(), + * @ref add(const MeshData&, Containers::StringView) and returns the + * output of @ref endData(), or @ref Containers::NullOpt if any of + * those failed. + */ + virtual Containers::Optional> doConvertToData(const MeshData& mesh); + + /** + * @brief Implementation for @ref abort() + * @m_since_latest + * + * Default implementation does nothing. + */ + virtual void doAbort(); + + /** + * @brief Implementation for @ref begin() + * @m_since_latest + */ + virtual void doBegin(); + + /** + * @brief Implementation for @ref end() + * @m_since_latest + * + * Expected to return an importer instance owning all output data and + * reset the internal state for a potential new conversion to happen. + */ + virtual Containers::Pointer doEnd(); + + /** + * @brief Implementation for @ref beginData() + * @m_since_latest + */ + virtual void doBeginData(); + + /** + * @brief Implementation for @ref endData() + * @m_since_latest + * + * Expected to return the output data and reset the internal state for + * a potential new conversion to happen. + */ + virtual Containers::Optional> doEndData(); + + /** + * @brief Implementation for @ref add(const SceneData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref sceneCount() at the time this function is + * called. + */ + virtual bool doAdd(UnsignedInt id, const SceneData& scene, Containers::StringView name); + + /** + * @brief Implementation for @ref setSceneFieldName() + * @m_since_latest + * + * Receives the custom ID extracted via + * @ref sceneFieldCustom(SceneField). Default implementation does + * nothing. + */ + virtual void doSetSceneFieldName(UnsignedInt field, Containers::StringView name); + + /** + * @brief Implementation for @ref setObjectName() + * @m_since_latest + * + * Default implementation does nothing. + */ + virtual void doSetObjectName(UnsignedLong object, Containers::StringView name); + + /** + * @brief Implementation for @ref setDefaultScene() + * @m_since_latest + * + * Default implementation does nothing. + */ + virtual void doSetDefaultScene(UnsignedInt id); + + /** + * @brief Implementation for @ref add(const AnimationData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref animationCount() at the time this + * function is called. + */ + virtual bool doAdd(UnsignedInt id, const AnimationData& animation, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const LightData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref lightCount() at the time this function is + * called. + */ + virtual bool doAdd(UnsignedInt id, const LightData& light, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const CameraData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref cameraCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const CameraData& camera, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const SkinData2D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref skin2DCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const SkinData2D& skin, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const SkinData3D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref skin3DCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const SkinData3D& skin, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const MeshData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref meshCount() at the time this function is + * called. + * + * If @ref SceneConverterFeature::AddMeshes together with + * @relativeref{SceneConverterFeature,MeshLevels} is supported, default + * implementation calls @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + * with just the single @p mesh. + * + * Otherwise, if @ref SceneConverterFeature::ConvertMesh, + * @relativeref{SceneConverterFeature,ConvertMeshToData} or + * @relativeref{SceneConverterFeature,ConvertMeshToFile} is supported, + * default implementation delegates to @ref doConvert(const MeshData&), + * @ref doConvertToData(const MeshData&) or + * @ref doConvertToFile(const MeshData&, Containers::StringView) and + * remembers the result to return it from @ref doEnd(), + * @ref doEndData() or @ref doEndFile(). If the delegated-to function + * fails, returns @cpp false @ce and the subsequent @ref doEnd(), + * @ref doEndData() or @ref doEndFile() call prints a message to + * @relativeref{Magnum,Error} and returns a @cpp nullptr @ce, + * @ref Containers::NullOpt or @cpp false @ce. Since the delegation + * operates just on a single mesh at a time, if this function is called + * more than once after a @ref begin(), @ref beginData() or + * @ref beginFile(), prints a message to @relativeref{Magnum,Error} and + * returns @cpp false @ce. + */ + virtual bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView name); + + /** + * @brief Implementation for @ref add(Containers::Iterable, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref meshCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, Containers::Iterable meshLevels, Containers::StringView name); + + /** + * @brief Implementation for @ref setMeshAttributeName() + * @m_since_latest + * + * Receives the custom ID extracted via + * @ref meshAttributeCustom(MeshAttribute). Default implementation does + * nothing. + */ + virtual void doSetMeshAttributeName(UnsignedShort attribute, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const MaterialData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref materialCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const MaterialData& material, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const TextureData&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref textureCount() at the time this function + * is called. + */ + virtual bool doAdd(UnsignedInt id, const TextureData& texture, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const ImageData1D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image1DCount() at the time this function + * is called. If @ref add(const ImageView1D&, Containers::StringView) + * or @ref add(const CompressedImageView1D&, Containers::StringView) + * was called, receives the view wrapped in a non-owning + * @ref ImageData1D instance. + * + * If @ref SceneConverterFeature::ImageLevels is supported, default + * implementation calls @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + * with just the single @p image. + */ + virtual bool doAdd(UnsignedInt id, const ImageData1D& image, Containers::StringView name); + + /** + * @brief Implementation for @ref add(Containers::Iterable, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image1DCount() at the time this function + * is called. If @ref add(Containers::Iterable, Containers::StringView) + * or @ref add(Containers::Iterable, Containers::StringView) + * was called, receives the views wrapped in non-owning + * @ref ImageData1D instances. + */ + virtual bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const ImageData2D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image2DCount() at the time this function + * is called. If @ref add(const ImageView2D&, Containers::StringView) + * or @ref add(const CompressedImageView2D&, Containers::StringView) + * was called, receives the view wrapped in a non-owning + * @ref ImageData2D instance. + * + * If @ref SceneConverterFeature::ImageLevels is supported, default + * implementation calls @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + * with just the single @p image. + */ + virtual bool doAdd(UnsignedInt id, const ImageData2D& image, Containers::StringView name); + + /** + * @brief Implementation for @ref add(Containers::Iterable, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image2DCount() at the time this function + * is called. If @ref add(Containers::Iterable, Containers::StringView) + * or @ref add(Containers::Iterable, Containers::StringView) + * was called, receives the views wrapped in non-owning + * @ref ImageData2D instances. + */ + virtual bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name); + + /** + * @brief Implementation for @ref add(const ImageData3D&, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image3DCount() at the time this function + * is called. If @ref add(const ImageView3D&, Containers::StringView) + * or @ref add(const CompressedImageView3D&, Containers::StringView) + * was called, receives the view wrapped in a non-owning + * @ref ImageData3D instance. + * + * If @ref SceneConverterFeature::ImageLevels is supported, default + * implementation calls @ref doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) + * with just the single @p image. + */ + virtual bool doAdd(UnsignedInt id, const ImageData3D& image, Containers::StringView name); + + /** + * @brief Implementation for @ref add(Containers::Iterable, Containers::StringView) + * @m_since_latest + * + * The @p id is equal to @ref image3DCount() at the time this function + * is called. If @ref add(Containers::Iterable, Containers::StringView) + * or @ref add(Containers::Iterable, Containers::StringView) + * was called, receives the views wrapped in non-owning + * @ref ImageData3D instances. + */ + virtual bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name); SceneConverterFlags _flags; + Containers::Pointer _state; }; }} diff --git a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp index 5cee66b72..675b20adc 100644 --- a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp +++ b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include /** @todo remove once Debug is stream-free */ #include @@ -34,10 +35,23 @@ #include #include +#include "Magnum/ImageView.h" +#include "Magnum/PixelFormat.h" #include "Magnum/Math/Vector3.h" +#include "Magnum/Math/Matrix3.h" +#include "Magnum/Math/Matrix4.h" #include "Magnum/Trade/ArrayAllocator.h" +#include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/AbstractSceneConverter.h" +#include "Magnum/Trade/AnimationData.h" +#include "Magnum/Trade/CameraData.h" +#include "Magnum/Trade/ImageData.h" +#include "Magnum/Trade/LightData.h" +#include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/SceneData.h" +#include "Magnum/Trade/SkinData.h" +#include "Magnum/Trade/TextureData.h" #include "configure.h" @@ -52,6 +66,8 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void setFlagsNotImplemented(); void thingNotSupported(); + /* Certain features need a combination of flags, test them explicitly */ + void thingLevelsNotSupported(); void convertMesh(); void convertMeshFailed(); @@ -68,6 +84,9 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void convertMeshToData(); void convertMeshToDataFailed(); + void convertMeshToDataThroughBatch(); + void convertMeshToDataThroughBatchAddFailed(); + void convertMeshToDataThroughBatchEndFailed(); void convertMeshToDataNotImplemented(); void convertMeshToDataNonOwningDeleter(); void convertMeshToDataGrowableDeleter(); @@ -78,8 +97,173 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void convertMeshToFileThroughData(); void convertMeshToFileThroughDataFailed(); void convertMeshToFileThroughDataNotWritable(); + void convertMeshToFileThroughBatch(); + void convertMeshToFileThroughBatchAddFailed(); + void convertMeshToFileThroughBatchEndFailed(); void convertMeshToFileNotImplemented(); + void beginEnd(); + void beginEndFailed(); + void beginNotImplemented(); + void endNotImplemented(); + + void beginEndData(); + void beginEndDataFailed(); + void beginDataNotImplemented(); + void endDataNotImplemented(); + void beginEndDataCustomDeleter(); + + void beginEndFile(); + void beginEndFileFailed(); + void beginEndFileThroughData(); + void beginEndFileThroughDataFailed(); + void beginEndFileThroughDataNotWritable(); + void beginFileNotImplemented(); + void endFileNotImplemented(); + + void abort(); + void abortNotImplemented(); + + void thingNoBegin(); + void endMismatchedBegin(); + void endDataMismatchedBegin(); + void endFileMismatchedBegin(); + + void addScene(); + void addSceneFailed(); + void addSceneNotImplemented(); + + void setSceneFieldName(); + void setSceneFieldNameNotImplemented(); + void setSceneFieldNameNotCustom(); + + void setObjectName(); + void setObjectNameNotImplemented(); + + void setDefaultScene(); + void setDefaultSceneNotImplemented(); + void setDefaultSceneOutOfRange(); + + void addAnimation(); + void addAnimationFailed(); + void addAnimationNotImplemented(); + + void addLight(); + void addLightFailed(); + void addLightNotImplemented(); + + void addCamera(); + void addCameraFailed(); + void addCameraNotImplemented(); + + void addSkin2D(); + void addSkin2DFailed(); + void addSkin2DNotImplemented(); + + void addSkin3D(); + void addSkin3DFailed(); + void addSkin3DNotImplemented(); + + void addMesh(); + void addMeshFailed(); + void addMeshThroughConvertMesh(); + void addMeshThroughConvertMeshFailed(); + void addMeshThroughConvertMeshZeroMeshes(); + void addMeshThroughConvertMeshTwoMeshes(); + void addMeshThroughConvertMeshToData(); + void addMeshThroughConvertMeshToDataFailed(); + void addMeshThroughConvertMeshToDataZeroMeshes(); + void addMeshThroughConvertMeshToDataTwoMeshes(); + void addMeshThroughConvertMeshToFile(); + void addMeshThroughConvertMeshToFileThroughData(); + void addMeshThroughConvertMeshToFileFailed(); + void addMeshThroughConvertMeshToFileZeroMeshes(); + void addMeshThroughConvertMeshToFileTwoMeshes(); + void addMeshNotImplemented(); + + void addMeshLevels(); + void addMeshLevelsFailed(); + void addMeshLevelsNoLevels(); + void addMeshLevelsNotImplemented(); + + void addMeshThroughLevels(); + + void setMeshAttributeName(); + void setMeshAttributeNameNotImplemented(); + void setMeshAttributeNameNotCustom(); + + void addMaterial(); + void addMaterialFailed(); + void addMaterialNotImplemented(); + + void addTexture(); + void addTextureFailed(); + void addTextureNotImplemented(); + + void addImage1D(); + void addImage1DView(); + void addImage1DCompressedView(); + void addImage1DFailed(); + /* 1D/2D/3D share the same image validity check function, so it's verified + only for 2D thoroughly and for others just that the check is used */ + void addImage1DInvalidImage(); + void addImage1DNotImplemented(); + + void addImage2D(); + void addImage2DView(); + void addImage2DCompressedView(); + void addImage2DFailed(); + void addImage2DZeroSize(); + void addImage2DNullptr(); + void addImage2DNotImplemented(); + + void addImage3D(); + void addImage3DView(); + void addImage3DCompressedView(); + void addImage3DFailed(); + /* 1D/2D/3D share the same image validity check function, so it's verified + only for 2D thoroughly and for others just that the check is used */ + void addImage3DInvalidImage(); + void addImage3DNotImplemented(); + + void addImageLevels1D(); + void addImageLevels1DView(); + void addImageLevels1DCompressedView(); + void addImageLevels1DFailed(); + /* 1D/2D/3D share the same image list validity check function, so it's + verified only for 2D thoroughly and for others just that the check is + used */ + void addImageLevels1DInvalidImage(); + void addImageLevels1DNotImplemented(); + + void addImageLevels2D(); + void addImageLevels2DView(); + void addImageLevels2DCompressedView(); + void addImageLevels2DFailed(); + void addImageLevels2DNoLevels(); + void addImageLevels2DZeroSize(); + void addImageLevels2DNullptr(); + void addImageLevels2DInconsistentCompressed(); + void addImageLevels2DInconsistentFormat(); + void addImageLevels2DInconsistentFormatExtra(); + void addImageLevels2DInconsistentCompressedFormat(); + void addImageLevels2DInconsistentFlags(); + void addImageLevels2DNotImplemented(); + + void addImageLevels3D(); + void addImageLevels3DView(); + void addImageLevels3DCompressedView(); + void addImageLevels3DFailed(); + /* 1D/2D/3D share the same image list validity check function, so it's + verified only for 2D thoroughly and for others just that the check is + used */ + void addImageLevels3DInvalidImage(); + void addImageLevels3DNotImplemented(); + + void addImage1DThroughLevels(); + void addImage2DThroughLevels(); + void addImage3DThroughLevels(); + void debugFeature(); void debugFeatures(); void debugFeaturesSupersets(); @@ -87,6 +271,8 @@ struct AbstractSceneConverterTest: TestSuite::Tester { void debugFlags(); }; +using namespace Containers::Literals; + AbstractSceneConverterTest::AbstractSceneConverterTest() { addTests({&AbstractSceneConverterTest::featuresNone, @@ -94,6 +280,7 @@ AbstractSceneConverterTest::AbstractSceneConverterTest() { &AbstractSceneConverterTest::setFlagsNotImplemented, &AbstractSceneConverterTest::thingNotSupported, + &AbstractSceneConverterTest::thingLevelsNotSupported, &AbstractSceneConverterTest::convertMesh, &AbstractSceneConverterTest::convertMeshFailed, @@ -110,6 +297,9 @@ AbstractSceneConverterTest::AbstractSceneConverterTest() { &AbstractSceneConverterTest::convertMeshToData, &AbstractSceneConverterTest::convertMeshToDataFailed, + &AbstractSceneConverterTest::convertMeshToDataThroughBatch, + &AbstractSceneConverterTest::convertMeshToDataThroughBatchAddFailed, + &AbstractSceneConverterTest::convertMeshToDataThroughBatchEndFailed, &AbstractSceneConverterTest::convertMeshToDataNotImplemented, &AbstractSceneConverterTest::convertMeshToDataNonOwningDeleter, &AbstractSceneConverterTest::convertMeshToDataGrowableDeleter, @@ -120,8 +310,163 @@ AbstractSceneConverterTest::AbstractSceneConverterTest() { &AbstractSceneConverterTest::convertMeshToFileThroughData, &AbstractSceneConverterTest::convertMeshToFileThroughDataFailed, &AbstractSceneConverterTest::convertMeshToFileThroughDataNotWritable, + &AbstractSceneConverterTest::convertMeshToFileThroughBatch, + &AbstractSceneConverterTest::convertMeshToFileThroughBatchAddFailed, + &AbstractSceneConverterTest::convertMeshToFileThroughBatchEndFailed, &AbstractSceneConverterTest::convertMeshToFileNotImplemented, + &AbstractSceneConverterTest::beginEnd, + &AbstractSceneConverterTest::beginEndFailed, + &AbstractSceneConverterTest::beginNotImplemented, + &AbstractSceneConverterTest::endNotImplemented, + + &AbstractSceneConverterTest::beginEndData, + &AbstractSceneConverterTest::beginEndDataFailed, + &AbstractSceneConverterTest::beginDataNotImplemented, + &AbstractSceneConverterTest::endDataNotImplemented, + &AbstractSceneConverterTest::beginEndDataCustomDeleter, + + &AbstractSceneConverterTest::beginEndFile, + &AbstractSceneConverterTest::beginEndFileFailed, + &AbstractSceneConverterTest::beginEndFileThroughData, + &AbstractSceneConverterTest::beginEndFileThroughDataFailed, + &AbstractSceneConverterTest::beginEndFileThroughDataNotWritable, + &AbstractSceneConverterTest::beginFileNotImplemented, + &AbstractSceneConverterTest::endFileNotImplemented, + + &AbstractSceneConverterTest::abort, + &AbstractSceneConverterTest::abortNotImplemented, + + &AbstractSceneConverterTest::thingNoBegin, + &AbstractSceneConverterTest::endMismatchedBegin, + &AbstractSceneConverterTest::endDataMismatchedBegin, + &AbstractSceneConverterTest::endFileMismatchedBegin, + + &AbstractSceneConverterTest::addScene, + &AbstractSceneConverterTest::addSceneFailed, + &AbstractSceneConverterTest::addSceneNotImplemented, + + &AbstractSceneConverterTest::setSceneFieldName, + &AbstractSceneConverterTest::setSceneFieldNameNotImplemented, + &AbstractSceneConverterTest::setSceneFieldNameNotCustom, + + &AbstractSceneConverterTest::setObjectName, + &AbstractSceneConverterTest::setObjectNameNotImplemented, + + &AbstractSceneConverterTest::setDefaultScene, + &AbstractSceneConverterTest::setDefaultSceneNotImplemented, + &AbstractSceneConverterTest::setDefaultSceneOutOfRange, + + &AbstractSceneConverterTest::addAnimation, + &AbstractSceneConverterTest::addAnimationFailed, + &AbstractSceneConverterTest::addAnimationNotImplemented, + + &AbstractSceneConverterTest::addLight, + &AbstractSceneConverterTest::addLightFailed, + &AbstractSceneConverterTest::addLightNotImplemented, + + &AbstractSceneConverterTest::addCamera, + &AbstractSceneConverterTest::addCameraFailed, + &AbstractSceneConverterTest::addCameraNotImplemented, + + &AbstractSceneConverterTest::addSkin2D, + &AbstractSceneConverterTest::addSkin2DFailed, + &AbstractSceneConverterTest::addSkin2DNotImplemented, + + &AbstractSceneConverterTest::addSkin3D, + &AbstractSceneConverterTest::addSkin3DFailed, + &AbstractSceneConverterTest::addSkin3DNotImplemented, + + &AbstractSceneConverterTest::addMesh, + &AbstractSceneConverterTest::addMeshFailed, + &AbstractSceneConverterTest::addMeshThroughConvertMesh, + &AbstractSceneConverterTest::addMeshThroughConvertMeshFailed, + &AbstractSceneConverterTest::addMeshThroughConvertMeshZeroMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshTwoMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToData, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToDataFailed, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToDataZeroMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToDataTwoMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFile, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFileThroughData, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFileFailed, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFileZeroMeshes, + &AbstractSceneConverterTest::addMeshThroughConvertMeshToFileTwoMeshes, + &AbstractSceneConverterTest::addMeshNotImplemented, + + &AbstractSceneConverterTest::addMeshLevels, + &AbstractSceneConverterTest::addMeshLevelsFailed, + &AbstractSceneConverterTest::addMeshLevelsNoLevels, + &AbstractSceneConverterTest::addMeshLevelsNotImplemented, + + &AbstractSceneConverterTest::addMeshThroughLevels, + + &AbstractSceneConverterTest::setMeshAttributeName, + &AbstractSceneConverterTest::setMeshAttributeNameNotImplemented, + &AbstractSceneConverterTest::setMeshAttributeNameNotCustom, + + &AbstractSceneConverterTest::addMaterial, + &AbstractSceneConverterTest::addMaterialFailed, + &AbstractSceneConverterTest::addMaterialNotImplemented, + + &AbstractSceneConverterTest::addTexture, + &AbstractSceneConverterTest::addTextureFailed, + &AbstractSceneConverterTest::addTextureNotImplemented, + + &AbstractSceneConverterTest::addImage1D, + &AbstractSceneConverterTest::addImage1DView, + &AbstractSceneConverterTest::addImage1DCompressedView, + &AbstractSceneConverterTest::addImage1DFailed, + &AbstractSceneConverterTest::addImage1DInvalidImage, + &AbstractSceneConverterTest::addImage1DNotImplemented, + + &AbstractSceneConverterTest::addImage2D, + &AbstractSceneConverterTest::addImage2DView, + &AbstractSceneConverterTest::addImage2DCompressedView, + &AbstractSceneConverterTest::addImage2DFailed, + &AbstractSceneConverterTest::addImage2DZeroSize, + &AbstractSceneConverterTest::addImage2DNullptr, + &AbstractSceneConverterTest::addImage2DNotImplemented, + + &AbstractSceneConverterTest::addImage3D, + &AbstractSceneConverterTest::addImage3DView, + &AbstractSceneConverterTest::addImage3DCompressedView, + &AbstractSceneConverterTest::addImage3DFailed, + &AbstractSceneConverterTest::addImage3DInvalidImage, + &AbstractSceneConverterTest::addImage3DNotImplemented, + + &AbstractSceneConverterTest::addImageLevels1D, + &AbstractSceneConverterTest::addImageLevels1DView, + &AbstractSceneConverterTest::addImageLevels1DCompressedView, + &AbstractSceneConverterTest::addImageLevels1DFailed, + &AbstractSceneConverterTest::addImageLevels1DInvalidImage, + &AbstractSceneConverterTest::addImageLevels1DNotImplemented, + + &AbstractSceneConverterTest::addImageLevels2D, + &AbstractSceneConverterTest::addImageLevels2DView, + &AbstractSceneConverterTest::addImageLevels2DCompressedView, + &AbstractSceneConverterTest::addImageLevels2DFailed, + &AbstractSceneConverterTest::addImageLevels2DNoLevels, + &AbstractSceneConverterTest::addImageLevels2DZeroSize, + &AbstractSceneConverterTest::addImageLevels2DNullptr, + &AbstractSceneConverterTest::addImageLevels2DInconsistentCompressed, + &AbstractSceneConverterTest::addImageLevels2DInconsistentFormat, + &AbstractSceneConverterTest::addImageLevels2DInconsistentFormatExtra, + &AbstractSceneConverterTest::addImageLevels2DInconsistentCompressedFormat, + &AbstractSceneConverterTest::addImageLevels2DInconsistentFlags, + &AbstractSceneConverterTest::addImageLevels2DNotImplemented, + + &AbstractSceneConverterTest::addImageLevels3D, + &AbstractSceneConverterTest::addImageLevels3DView, + &AbstractSceneConverterTest::addImageLevels3DCompressedView, + &AbstractSceneConverterTest::addImageLevels3DFailed, + &AbstractSceneConverterTest::addImageLevels3DInvalidImage, + &AbstractSceneConverterTest::addImageLevels3DNotImplemented, + + &AbstractSceneConverterTest::addImage1DThroughLevels, + &AbstractSceneConverterTest::addImage2DThroughLevels, + &AbstractSceneConverterTest::addImage3DThroughLevels, + &AbstractSceneConverterTest::debugFeature, &AbstractSceneConverterTest::debugFeatures, &AbstractSceneConverterTest::debugFeaturesSupersets, @@ -194,11 +539,19 @@ void AbstractSceneConverterTest::thingNotSupported() { struct: AbstractSceneConverter { SceneConverterFeatures doFeatures() const override { /* Assuming this bit is unused */ - return SceneConverterFeature(1 << 7); + return SceneConverterFeature(1u << 31); } } converter; - MeshData mesh{MeshPrimitive::Triangles, 3}; + MeshData mesh{MeshPrimitive::Triangles, 0}; + + const char imageData[4*4]{}; + ImageData1D image1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}; + ImageData1D compressedImage1D{CompressedPixelFormat::Astc4x4RGBAF, 1, DataFlags{}, imageData}; + ImageData2D image2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}; + ImageData2D compressedImage2D{CompressedPixelFormat::Astc4x4RGBAF, {1, 1}, DataFlags{}, imageData}; + ImageData3D image3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}; + ImageData3D compressedImage3D{CompressedPixelFormat::Astc4x4RGBAF, {1, 1, 1}, DataFlags{}, imageData}; std::ostringstream out; Error redirectError{&out}; @@ -206,11 +559,131 @@ void AbstractSceneConverterTest::thingNotSupported() { converter.convertInPlace(mesh); converter.convertToData(mesh); converter.convertToFile(mesh, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out")); + + converter.begin(); + converter.beginData(); + converter.beginFile(Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out")); + + converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}); + converter.setSceneFieldName({}, {}); + converter.setObjectName(0, {}); + converter.setDefaultScene(0); + + converter.add(AnimationData{nullptr, nullptr}); + converter.add(LightData{LightData::Type::Point, {}, 0.0f}); + converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f}); + converter.add(SkinData2D{nullptr, nullptr}); + converter.add(SkinData3D{nullptr, nullptr}); + + converter.add(mesh); + converter.add({mesh, mesh}); + converter.setMeshAttributeName({}, {}); + + converter.add(MaterialData{{}, nullptr}); + converter.add(TextureData{{}, {}, {}, {}, {}, 0}); + + converter.add(image1D); + converter.add(compressedImage1D); + converter.add({image1D, image1D}); + converter.add({compressedImage1D, compressedImage1D}); + + converter.add(image2D); + converter.add(compressedImage2D); + converter.add({image2D, image2D}); + converter.add({compressedImage2D, compressedImage2D}); + + converter.add(image3D); + converter.add(compressedImage3D); + converter.add({image3D, image3D}); + converter.add({compressedImage3D, compressedImage3D}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convert(): mesh conversion not supported\n" "Trade::AbstractSceneConverter::convertInPlace(): mesh conversion not supported\n" "Trade::AbstractSceneConverter::convertToData(): mesh conversion not supported\n" - "Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported\n"); + "Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported\n" + + "Trade::AbstractSceneConverter::begin(): feature not supported\n" + "Trade::AbstractSceneConverter::beginData(): feature not supported\n" + "Trade::AbstractSceneConverter::beginFile(): feature not supported\n" + + "Trade::AbstractSceneConverter::add(): scene conversion not supported\n" + "Trade::AbstractSceneConverter::setSceneFieldName(): feature not supported\n" + "Trade::AbstractSceneConverter::setObjectName(): feature not supported\n" + "Trade::AbstractSceneConverter::setDefaultScene(): feature not supported\n" + + "Trade::AbstractSceneConverter::add(): animation conversion not supported\n" + "Trade::AbstractSceneConverter::add(): light conversion not supported\n" + "Trade::AbstractSceneConverter::add(): camera conversion not supported\n" + "Trade::AbstractSceneConverter::add(): 2D skin conversion not supported\n" + "Trade::AbstractSceneConverter::add(): 3D skin conversion not supported\n" + + "Trade::AbstractSceneConverter::add(): mesh conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level mesh conversion not supported\n" + "Trade::AbstractSceneConverter::setMeshAttributeName(): feature not supported\n" + + "Trade::AbstractSceneConverter::add(): material conversion not supported\n" + "Trade::AbstractSceneConverter::add(): texture conversion not supported\n" + + "Trade::AbstractSceneConverter::add(): 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): compressed 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 1D image conversion not supported\n" + + "Trade::AbstractSceneConverter::add(): 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): compressed 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 2D image conversion not supported\n" + + "Trade::AbstractSceneConverter::add(): 3D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): compressed 3D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 3D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 3D image conversion not supported\n"); +} + +void AbstractSceneConverterTest::thingLevelsNotSupported() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::AddCompressedImages1D| + SceneConverterFeature::AddCompressedImages2D| + SceneConverterFeature::AddCompressedImages3D; + } + } converter; + + MeshData mesh{MeshPrimitive::Triangles, 3}; + + const char imageData[4*4]{}; + ImageData1D image1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}; + ImageData1D compressedImage1D{CompressedPixelFormat::Astc4x4RGBAF, 1, DataFlags{}, imageData}; + ImageData2D image2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}; + ImageData2D compressedImage2D{CompressedPixelFormat::Astc4x4RGBAF, {1, 1}, DataFlags{}, imageData}; + ImageData3D image3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}; + ImageData3D compressedImage3D{CompressedPixelFormat::Astc4x4RGBAF, {1, 1, 1}, DataFlags{}, imageData}; + + std::ostringstream out; + Error redirectError{&out}; + converter.add({mesh, mesh}); + converter.add({image1D, image1D}); + converter.add({compressedImage1D, compressedImage1D}); + converter.add({image2D, image2D}); + converter.add({compressedImage2D, compressedImage2D}); + converter.add({image3D, image3D}); + converter.add({compressedImage3D, compressedImage3D}); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::add(): multi-level mesh conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 1D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 2D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level 3D image conversion not supported\n" + "Trade::AbstractSceneConverter::add(): multi-level compressed 3D image conversion not supported\n"); } void AbstractSceneConverterTest::convertMesh() { @@ -461,6 +934,95 @@ void AbstractSceneConverterTest::convertMeshToDataFailed() { CORRADE_COMPARE(out.str(), ""); } +void AbstractSceneConverterTest::convertMeshToDataThroughBatch() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData| + SceneConverterFeature::AddMeshes; + } + + Containers::Optional> doConvertToData(const Magnum::Trade::MeshData&) override { + CORRADE_FAIL_IF(true, "doConvertToData() should not be called"); + return {}; + } + + void doBeginData() override { + _vertexCount = 42; + } + + bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView) override { + CORRADE_COMPARE(id, 0); + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + _vertexCount *= mesh.vertexCount(); + return true; + } + + Containers::Optional> doEndData() override { + return Containers::Array{nullptr, _vertexCount}; + } + + std::size_t _vertexCount = 0; + } converter; + + Containers::Optional> data = converter.convertToData(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->size(), 42*6); +} + +void AbstractSceneConverterTest::convertMeshToDataThroughBatchAddFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData| + SceneConverterFeature::AddMeshes; + } + + void doBeginData() override {} + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return false; + } + + Containers::Optional> doEndData() override { + CORRADE_FAIL_IF(true, "doEndData() shouldn't be called"); + return {}; + } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToData(MeshData{MeshPrimitive::Triangles, 6})); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), ""); +} + +void AbstractSceneConverterTest::convertMeshToDataThroughBatchEndFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData| + SceneConverterFeature::AddMeshes; + } + + void doBeginData() override {} + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return true; + } + + Containers::Optional> doEndData() override { + return {}; + } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToData(MeshData{MeshPrimitive::Triangles, 6})); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), ""); +} + void AbstractSceneConverterTest::convertMeshToDataNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -536,7 +1098,7 @@ void AbstractSceneConverterTest::convertMeshToFile() { SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToFile; } bool doConvertToFile(const MeshData& mesh, Containers::StringView filename) override { - return Utility::Path::write(filename, Containers::arrayView( {char(mesh.vertexCount())})); + return Utility::Path::write(filename, Containers::arrayView({char(mesh.vertexCount())})); } } converter; @@ -629,6 +1191,102 @@ void AbstractSceneConverterTest::convertMeshToFileThroughDataNotWritable() { TestSuite::Compare::StringHasSuffix); } +void AbstractSceneConverterTest::convertMeshToFileThroughBatch() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile| + SceneConverterFeature::AddMeshes; + } + + bool doConvertToFile(const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doConvertToFile() should not be called"); + return {}; + } + + void doBeginFile(Containers::StringView filename) override { + _filename = filename; + } + + bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView) override { + CORRADE_COMPARE(id, 0); + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + _vertexCount = mesh.vertexCount(); + return true; + } + + bool doEndFile(Containers::StringView filename) override { + CORRADE_COMPARE(filename, _filename); + return Utility::Path::write(filename, Containers::arrayView({char(_vertexCount)})); + } + + std::size_t _vertexCount = 0; + Containers::StringView _filename; + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + CORRADE_VERIFY(converter.convertToFile(MeshData{MeshPrimitive::Triangles, 0xfc}, filename)); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE_AS(filename, + "\xfc", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::convertMeshToFileThroughBatchAddFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile| + SceneConverterFeature::AddMeshes; + } + + void doBeginFile(Containers::StringView) override {} + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return false; + } + + bool doEndFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doEndFile() shouldn't be called"); + return {}; + } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToFile(MeshData{MeshPrimitive::Triangles, 0xfc}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"))); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), ""); +} + +void AbstractSceneConverterTest::convertMeshToFileThroughBatchEndFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile| + SceneConverterFeature::AddMeshes; + } + + void doBeginFile(Containers::StringView) override {} + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return true; + } + + bool doEndFile(Containers::StringView) override { + return {}; + } + } converter; + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToFile(MeshData{MeshPrimitive::Triangles, 0xfc}, Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"))); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), ""); +} + void AbstractSceneConverterTest::convertMeshToFileNotImplemented() { CORRADE_SKIP_IF_NO_ASSERT(); @@ -642,27 +1300,3773 @@ void AbstractSceneConverterTest::convertMeshToFileNotImplemented() { CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convertToFile(): mesh conversion advertised but not implemented\n"); } -void AbstractSceneConverterTest::debugFeature() { +void AbstractSceneConverterTest::beginEnd() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override { + CORRADE_VERIFY(!beginCalled); + beginCalled = true; + } + + Containers::Pointer doEnd() override { + CORRADE_VERIFY(!endCalled); + endCalled = true; + + struct Importer: AbstractImporter { + ImporterFeatures doFeatures() const override { return {}; } + void doClose() override {} + bool doIsOpened() const override { return true; } + + const void* doImporterState() const override { + return reinterpret_cast(0xdeadbeef); + } + }; + + return Containers::Pointer{new Importer}; + } + + bool beginCalled = false, endCalled = false; + } converter; + + CORRADE_VERIFY(!converter.isConverting()); + converter.begin(); + CORRADE_VERIFY(converter.beginCalled); + CORRADE_VERIFY(!converter.endCalled); + CORRADE_VERIFY(converter.isConverting()); + + Containers::Pointer out = converter.end(); + CORRADE_VERIFY(out); + CORRADE_COMPARE(out->importerState(), reinterpret_cast(0xdeadbeef)); + CORRADE_VERIFY(converter.endCalled); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginEndFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override {} + + Containers::Pointer doEnd() override { + return nullptr; + } + } converter; + + converter.begin(); + + /* The implementation is expected to print an error message on its own */ std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.end()); + CORRADE_COMPARE(out.str(), ""); - Debug{&out} << SceneConverterFeature::ConvertMeshInPlace << SceneConverterFeature(0xf0); - CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshInPlace Trade::SceneConverterFeature(0xf0)\n"); + CORRADE_VERIFY(!converter.isConverting()); } -void AbstractSceneConverterTest::debugFeatures() { +void AbstractSceneConverterTest::beginNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + } converter; + std::ostringstream out; + Error redirectError{&out}; + converter.begin(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::begin(): feature advertised but not implemented\n"); +} - Debug{&out} << (SceneConverterFeature::ConvertMesh|SceneConverterFeature::ConvertMeshToFile) << SceneConverterFeatures{}; - CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMesh|Trade::SceneConverterFeature::ConvertMeshToFile Trade::SceneConverterFeatures{}\n"); +void AbstractSceneConverterTest::endNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.end(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::end(): feature advertised but not implemented\n"); } -void AbstractSceneConverterTest::debugFeaturesSupersets() { - /* ConvertMeshToData is a superset of ConvertMeshToFile, so only one should - be printed */ - { - std::ostringstream out; - Debug{&out} << (SceneConverterFeature::ConvertMeshToData|SceneConverterFeature::ConvertMeshToFile); - CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshToData\n"); +void AbstractSceneConverterTest::beginEndData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override { + CORRADE_VERIFY(!beginCalled); + beginCalled = true; + } + + Containers::Optional> doEndData() override { + CORRADE_VERIFY(!endCalled); + endCalled = true; + return Containers::array({'h', 'e', 'l', 'l', 'o'}); + } + + bool beginCalled = false, endCalled = false; + } converter; + + CORRADE_VERIFY(!converter.isConverting()); + converter.beginData(); + CORRADE_VERIFY(converter.beginCalled); + CORRADE_VERIFY(!converter.endCalled); + CORRADE_VERIFY(converter.isConverting()); + + Containers::Optional> out = converter.endData(); + CORRADE_VERIFY(out); + CORRADE_COMPARE_AS(*out, + Containers::arrayView({'h', 'e', 'l', 'l', 'o'}), + TestSuite::Compare::Container); + CORRADE_VERIFY(converter.endCalled); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginEndDataFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + + Containers::Optional> doEndData() override { + return {}; + } + } converter; + + converter.beginData(); + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endData()); + CORRADE_COMPARE(out.str(), ""); + + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginDataNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.beginData(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::beginData(): feature advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::endDataNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + } converter; + + converter.beginData(); + + std::ostringstream out; + Error redirectError{&out}; + converter.endData(); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::endData(): feature advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::beginEndDataCustomDeleter() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + + Containers::Optional> doEndData() override { + return Containers::Array{data, 1, [](char*, std::size_t) {}}; + } + + char data[1]; + } converter; + + converter.beginData(); + + std::ostringstream out; + Error redirectError{&out}; + converter.endData(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endData(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractSceneConverterTest::beginEndFile() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + + void doBeginFile(Containers::StringView filename) override { + CORRADE_VERIFY(!beginCalled); + beginCalled = true; + CORRADE_COMPARE(filename, "file.gltf"); + filenameDataPointer = filename.data(); + } + + bool doEndFile(Containers::StringView filename) override { + CORRADE_VERIFY(!endCalled); + endCalled = true; + CORRADE_COMPARE(filename, "file.gltf"); + + /* The filename should stay in scope and be the same pointer */ + CORRADE_COMPARE(filename.data(), filenameDataPointer); + return true; + } + + bool beginCalled = false, endCalled = false; + const void* filenameDataPointer; + } converter; + + CORRADE_VERIFY(!converter.isConverting()); + converter.beginFile("file.gltf!"_s.exceptSuffix(1)); + CORRADE_VERIFY(converter.beginCalled); + CORRADE_VERIFY(!converter.endCalled); + CORRADE_VERIFY(converter.isConverting()); + + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(converter.endCalled); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginEndFileFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + + void doBeginFile(Containers::StringView) override {} + + bool doEndFile(Containers::StringView) override { + return false; + } + } converter; + + converter.beginFile("file.gltf"); + + /* The implementation is expected to print an error message on its own */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_COMPARE(out.str(), ""); + + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::beginEndFileThroughData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + + Containers::Optional> doEndData() override { + return Containers::array({'h', 'e', 'l', 'l', 'o'}); + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + + /* doEndFile() should call doEndData() */ + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE_AS(filename, "hello", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::beginEndFileThroughDataFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + + Containers::Optional> doEndData() override { + return {}; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + + /* Function should fail, no file should get written and no error output + should be printed (the base implementation assumes the plugin does it) */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + CORRADE_COMPARE(out.str(), ""); +} + +void AbstractSceneConverterTest::beginEndFileThroughDataNotWritable() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + + Containers::Optional> doEndData() override { + return Containers::array({'h', 'e', 'l', 'l', 'o'}); + } + } converter; + + converter.beginFile("/some/path/that/does/not/exist"); + + /* Function should fail, no file should get written and no error output + should be printed (the base implementation assumes the plugin does it) */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + /* There's an error from Path::write() before */ + CORRADE_COMPARE_AS(out.str(), + "\nTrade::AbstractSceneConverter::endFile(): cannot write to file /some/path/that/does/not/exist\n", + TestSuite::Compare::StringHasSuffix); +} + +void AbstractSceneConverterTest::beginFileNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.beginFile("file.gltf"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::beginFile(): feature advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::endFileNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToFile; + } + + void doBeginFile(Containers::StringView) override {} + } converter; + + converter.beginFile("file.gltf"); + + std::ostringstream out; + Error redirectError{&out}; + converter.endFile(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): feature advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::abort() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override {} + + void doAbort() override { + CORRADE_VERIFY(!abortCalled); + abortCalled = true; + } + + bool abortCalled = false; + } converter; + + CORRADE_VERIFY(!converter.abortCalled); + converter.begin(); + CORRADE_VERIFY(!converter.abortCalled); + CORRADE_VERIFY(converter.isConverting()); + converter.abort(); + CORRADE_VERIFY(converter.abortCalled); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::abortNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override {} + } converter; + + /* This should work, there's no need for a plugin to implement this */ + converter.begin(); + CORRADE_VERIFY(converter.isConverting()); + converter.abort(); + CORRADE_VERIFY(!converter.isConverting()); +} + +void AbstractSceneConverterTest::thingNoBegin() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::AddScenes| + SceneConverterFeature::AddAnimations| + SceneConverterFeature::AddLights| + SceneConverterFeature::AddCameras| + SceneConverterFeature::AddSkins2D| + SceneConverterFeature::AddSkins3D| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::AddMaterials| + SceneConverterFeature::AddTextures| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::AddCompressedImages1D| + SceneConverterFeature::AddCompressedImages2D| + SceneConverterFeature::AddCompressedImages3D| + SceneConverterFeature::MeshLevels| + SceneConverterFeature::ImageLevels; + } + + void doBeginData() override {} + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.end(); + converter.endData(); + converter.endFile(); + + converter.sceneCount(); + converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}); + converter.setSceneFieldName({}, {}); + converter.setObjectName(0, {}); + converter.setDefaultScene(0); + + converter.animationCount(); + converter.add(AnimationData{nullptr, nullptr}); + + converter.lightCount(); + converter.add(LightData{LightData::Type::Point, {}, 0.0f}); + + converter.cameraCount(); + converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f}); + + converter.skin2DCount(); + converter.add(SkinData2D{nullptr, nullptr}); + + converter.skin3DCount(); + converter.add(SkinData3D{nullptr, nullptr}); + + converter.meshCount(); + converter.add(MeshData{MeshPrimitive::Triangles, 0}); + converter.add({MeshData{MeshPrimitive::Triangles, 0}, + MeshData{MeshPrimitive::Triangles, 0}}); + converter.setMeshAttributeName({}, {}); + + converter.materialCount(); + converter.add(MaterialData{{}, nullptr}); + + converter.textureCount(); + converter.add(TextureData{{}, {}, {}, {}, {}, 0}); + + const char imageData[4]{}; + + converter.image1DCount(); + converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}); + converter.add({ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}}); + + converter.image2DCount(); + converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}); + converter.add({ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}}); + + converter.image3DCount(); + converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}); + converter.add({ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}}); + + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::end(): no conversion in progress\n" + "Trade::AbstractSceneConverter::endData(): no data conversion in progress\n" + "Trade::AbstractSceneConverter::endFile(): no file conversion in progress\n" + + "Trade::AbstractSceneConverter::sceneCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::setSceneFieldName(): no conversion in progress\n" + "Trade::AbstractSceneConverter::setObjectName(): no conversion in progress\n" + "Trade::AbstractSceneConverter::setDefaultScene(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::animationCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::lightCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::cameraCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::skin2DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::skin3DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::meshCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::setMeshAttributeName(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::materialCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::textureCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::image1DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::image2DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + + "Trade::AbstractSceneConverter::image3DCount(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n" + "Trade::AbstractSceneConverter::add(): no conversion in progress\n"); +} + +void AbstractSceneConverterTest::endMismatchedBegin() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultipleToData; + } + + void doBeginData() override {} + } converter; + + converter.beginData(); + + std::ostringstream out; + Error redirectError{&out}; + converter.end(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::end(): no conversion in progress\n"); +} + +void AbstractSceneConverterTest::endDataMismatchedBegin() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.endData(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endData(): no data conversion in progress\n"); +} + +void AbstractSceneConverterTest::endFileMismatchedBegin() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.endFile(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): no file conversion in progress\n"); +} + +void AbstractSceneConverterTest::addScene() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const SceneData& scene, Containers::StringView name) override { + /* Scene count should not be increased before the function + returns */ + CORRADE_COMPARE(id, sceneCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(scene.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.sceneCount(), 0); + CORRADE_COMPARE(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.sceneCount(), 1); + CORRADE_COMPARE(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.sceneCount(), 2); +} + +void AbstractSceneConverterTest::addSceneFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.sceneCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.sceneCount(), 0); +} + +void AbstractSceneConverterTest::addSceneNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): scene conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::setSceneFieldName() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + void doSetSceneFieldName(UnsignedInt field, Containers::StringView name) override { + CORRADE_COMPARE(field, 1337); + CORRADE_COMPARE(name, "hello!"); + setSceneFieldNameCalled = true; + } + + bool setSceneFieldNameCalled = false; + } converter; + + CORRADE_VERIFY(true); /* capture correct function name */ + + converter.begin(); + converter.setSceneFieldName(sceneFieldCustom(1337), "hello!"); + CORRADE_VERIFY(converter.setSceneFieldNameCalled); +} + +void AbstractSceneConverterTest::setSceneFieldNameNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + } converter; + + /* This should work, there's no need for a plugin to implement this */ + converter.begin(); + converter.setSceneFieldName(sceneFieldCustom(1337), "hello!"); + CORRADE_VERIFY(true); +} + +void AbstractSceneConverterTest::setSceneFieldNameNotCustom() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.setSceneFieldName(SceneField::Transformation, "hello!"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::setSceneFieldName(): Trade::SceneField::Transformation is not custom\n"); +} + +void AbstractSceneConverterTest::setObjectName() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + void doSetObjectName(UnsignedLong object, Containers::StringView name) override { + CORRADE_COMPARE(object, 1337); + CORRADE_COMPARE(name, "hey!"); + setObjectNameCalled = true; + } + + bool setObjectNameCalled = false; + } converter; + + CORRADE_VERIFY(true); /* capture correct function name */ + + converter.begin(); + converter.setObjectName(1337, "hey!"); + CORRADE_VERIFY(converter.setObjectNameCalled); +} + +void AbstractSceneConverterTest::setObjectNameNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + } converter; + + /* This should work, there's no need for a plugin to implement this */ + converter.begin(); + converter.setObjectName(1337, "hey!"); + CORRADE_VERIFY(true); +} + +void AbstractSceneConverterTest::setDefaultScene() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { + return true; + } + + void doSetDefaultScene(UnsignedInt id) override { + CORRADE_COMPARE(id, 2); + setDefaultSceneCalled = true; + } + + bool setDefaultSceneCalled = false; + } converter; + + converter.begin(); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_COMPARE(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr}), 2); + + converter.setDefaultScene(2); + CORRADE_VERIFY(converter.setDefaultSceneCalled); +} + +void AbstractSceneConverterTest::setDefaultSceneNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { + return true; + } + } converter; + + converter.begin(); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_COMPARE(converter.sceneCount(), 1); + + /* This should work, there's no need for a plugin to implement this */ + converter.setDefaultScene(0); +} + +void AbstractSceneConverterTest::setDefaultSceneOutOfRange() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddScenes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const SceneData&, Containers::StringView) override { + return true; + } + } converter; + + converter.begin(); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_VERIFY(converter.add(SceneData{SceneMappingType::UnsignedInt, 0, nullptr, nullptr})); + CORRADE_COMPARE(converter.sceneCount(), 2); + + std::ostringstream out; + Error redirectError{&out}; + converter.setDefaultScene(2); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::setDefaultScene(): index 2 out of range for 2 scenes\n"); +} + +void AbstractSceneConverterTest::addAnimation() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddAnimations; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const AnimationData& animation, Containers::StringView name) override { + /* Animation count should not be increased before the function + returns */ + CORRADE_COMPARE(id, animationCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(animation.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.animationCount(), 0); + CORRADE_COMPARE(converter.add(AnimationData{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.animationCount(), 1); + CORRADE_COMPARE(converter.add(AnimationData{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.animationCount(), 2); +} + +void AbstractSceneConverterTest::addAnimationFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddAnimations; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const AnimationData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.animationCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(AnimationData{nullptr, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.animationCount(), 0); +} + +void AbstractSceneConverterTest::addAnimationNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddAnimations; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(AnimationData{nullptr, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): animation conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addLight() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddLights; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const LightData& light, Containers::StringView name) override { + /* Light count should not be increased before the function + returns */ + CORRADE_COMPARE(id, lightCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(light.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.lightCount(), 0); + CORRADE_COMPARE(converter.add(LightData{LightData::Type::Point, {}, 0.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.lightCount(), 1); + CORRADE_COMPARE(converter.add(LightData{LightData::Type::Point, {}, 0.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.lightCount(), 2); +} + +void AbstractSceneConverterTest::addLightFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddLights; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const LightData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.lightCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(LightData{LightData::Type::Point, {}, 0.0f})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.lightCount(), 0); +} + +void AbstractSceneConverterTest::addLightNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddLights; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(LightData{LightData::Type::Point, {}, 0.0f}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): light conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addCamera() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCameras; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const CameraData& camera, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, cameraCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(camera.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.cameraCount(), 0); + CORRADE_COMPARE(converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.cameraCount(), 1); + CORRADE_COMPARE(converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.cameraCount(), 2); +} + +void AbstractSceneConverterTest::addCameraFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCameras; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const CameraData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.cameraCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.cameraCount(), 0); +} + +void AbstractSceneConverterTest::addCameraNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCameras; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(CameraData{CameraType::Orthographic3D, {}, 0.0f, 1.0f}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): camera conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addSkin2D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins2D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const SkinData2D& skin, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, skin2DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(skin.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.skin2DCount(), 0); + CORRADE_COMPARE(converter.add(SkinData2D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.skin2DCount(), 1); + CORRADE_COMPARE(converter.add(SkinData2D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.skin2DCount(), 2); +} + +void AbstractSceneConverterTest::addSkin2DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins2D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const SkinData2D&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.skin2DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(SkinData2D{nullptr, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.skin2DCount(), 0); +} + +void AbstractSceneConverterTest::addSkin2DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins2D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(SkinData2D{nullptr, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 2D skin conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addSkin3D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins3D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const SkinData3D& skin, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, skin3DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(skin.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.skin3DCount(), 0); + CORRADE_COMPARE(converter.add(SkinData3D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.skin3DCount(), 1); + CORRADE_COMPARE(converter.add(SkinData3D{nullptr, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.skin3DCount(), 2); +} + +void AbstractSceneConverterTest::addSkin3DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins3D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const SkinData3D&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.skin3DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(SkinData3D{nullptr, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.skin3DCount(), 0); +} + +void AbstractSceneConverterTest::addSkin3DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddSkins3D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(SkinData3D{nullptr, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 3D skin conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addMesh() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const MeshData& mesh, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, meshCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(mesh.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.meshCount(), 2); +} + +void AbstractSceneConverterTest::addMeshFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const MeshData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Triangles, 0})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMesh() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh; + } + + Containers::Optional doConvert(const MeshData& mesh) override { + CORRADE_COMPARE(mesh.primitive(), MeshPrimitive::Triangles); + return MeshData{MeshPrimitive::Lines, mesh.vertexCount()*2}; + } + + void doBegin() override { + CORRADE_FAIL_IF(true, "doBegin() should not be called"); + } + + Containers::Pointer doEnd() override { + CORRADE_FAIL_IF(true, "doEnd() should not be called"); + return {}; + } + + bool doAdd(UnsignedInt, const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doAdd() should not be called"); + return {}; + } + } converter; + + CORRADE_VERIFY(true); /* capture correct function name */ + + converter.begin(); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + Containers::Pointer importer = converter.end(); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(importer); + CORRADE_VERIFY(importer->isOpened()); + CORRADE_COMPARE(importer->meshCount(), 1); + + Containers::Optional mesh = importer->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_COMPARE(mesh->primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(mesh->vertexCount(), 12); + + /* The mesh is returned only once, second time it will fail (but just an + error, not an assert */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!importer->mesh(0)); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::end(): mesh can be retrieved only once from a converter with just Trade::SceneConverterFeature::ConvertMesh\n"); + } + + /* Verify that it's also possible to close the importer without hitting + some nasty assert */ + importer->close(); + CORRADE_VERIFY(!importer->isOpened()); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh; + } + + Containers::Optional doConvert(const MeshData&) override { + return {}; + } + } converter; + + converter.begin(); + + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Lines, 6})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* But the observable behavior is as if no mesh was added */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.end()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::end(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshZeroMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh; + } + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.end()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::end(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshTwoMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMesh; + } + + Containers::Optional doConvert(const MeshData& mesh) override { + return MeshData{MeshPrimitive::Lines, mesh.vertexCount()*2}; + } + } converter; + + converter.begin(); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Triangles, 7})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh\n"); + } + + /* Getting the result should still work */ + Containers::Pointer out = converter.end(); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(out); + CORRADE_COMPARE(out->meshCount(), 1); + + Containers::Optional mesh = out->mesh(0); + CORRADE_VERIFY(mesh); + CORRADE_COMPARE(mesh->primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(mesh->vertexCount(), 12); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + + Containers::Optional> doConvertToData(const MeshData& mesh) override { + return Containers::Array{nullptr, mesh.vertexCount()}; + } + + void doBeginData() override { + CORRADE_FAIL_IF(true, "doBeginData() should not be called"); + } + + Containers::Optional> doEndData() override { + CORRADE_FAIL_IF(true, "doEndData() should not be called"); + return {}; + } + + bool doAdd(UnsignedInt, const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doAdd() should not be called"); + return {}; + } + } converter; + + converter.beginData(); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + Containers::Optional> data = converter.endData(); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->size(), 6); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + + Containers::Optional> doConvertToData(const MeshData&) override { + return {}; + } + } converter; + + converter.beginData(); + + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Lines, 6})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* But the observable behavior is as if no mesh was added */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endData()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endData(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataZeroMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + } converter; + + converter.beginData(); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endData()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endData(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToDataTwoMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + + Containers::Optional> doConvertToData(const MeshData& mesh) override { + return Containers::Array{nullptr, mesh.vertexCount()}; + } + } converter; + + converter.beginData(); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 6}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Triangles, 7})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh\n"); + } + + /* Getting the result should still work */ + Containers::Optional> data = converter.endData(); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(data); + CORRADE_COMPARE(data->size(), 6); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFile() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile; + } + + bool doConvertToFile(const MeshData& mesh, Containers::StringView filename) override { + return Utility::Path::write(filename, Containers::arrayView({char(mesh.vertexCount())})); + } + + void doBeginFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doBeginFile() should not be called"); + } + + bool doEndFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doEndFile() should not be called"); + return {}; + } + + bool doAdd(UnsignedInt, const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doAdd() should not be called"); + return {}; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0xef}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + /* It's easier to just perform the operation right during add() than to + make a copy of the passed MeshData and do it at endFile(), so the file + exists at this point already */ + CORRADE_VERIFY(Utility::Path::exists(filename)); + + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(Utility::Path::exists(filename)); + CORRADE_COMPARE_AS(filename, + "\xef", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileThroughData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToData; + } + + Containers::Optional> doConvertToData(const MeshData& mesh) override { + return Containers::array({char(mesh.vertexCount())}); + } + + void doBeginData() override { + CORRADE_FAIL_IF(true, "doBeginData() should not be called"); + } + + Containers::Optional> doEndData() override { + CORRADE_FAIL_IF(true, "doEndData() should not be called"); + return {}; + } + + void doBeginFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doBeginFile() should not be called"); + } + + bool doEndFile(Containers::StringView) override { + CORRADE_FAIL_IF(true, "doEndFile() should not be called"); + return {}; + } + + bool doAdd(UnsignedInt, const Trade::MeshData&, Containers::StringView) override { + CORRADE_FAIL_IF(true, "doAdd() should not be called"); + return {}; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0xef}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + /* It's easier to just perform the operation right during add() than to + make a copy of the passed MeshData and do it at the end(), so the file + exists at this point already */ + CORRADE_VERIFY(Utility::Path::exists(filename)); + + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(Utility::Path::exists(filename)); + CORRADE_COMPARE_AS(filename, + "\xef", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile; + } + + bool doConvertToFile(const MeshData&, Containers::StringView) override { + return false; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Lines, 6})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* But the observable behavior is as if no mesh was added */ + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileZeroMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile; + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh\n"); +} + +void AbstractSceneConverterTest::addMeshThroughConvertMeshToFileTwoMeshes() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMeshToFile; + } + + bool doConvertToFile(const MeshData& mesh, Containers::StringView filename) override { + return Utility::Path::write(filename, Containers::arrayView({char(mesh.vertexCount())})); + } + } converter; + + /* Remove previous file, if any */ + Containers::String filename = Utility::Path::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"); + if(Utility::Path::exists(filename)) + CORRADE_VERIFY(Utility::Path::remove(filename)); + + converter.beginFile(filename); + CORRADE_VERIFY(!Utility::Path::exists(filename)); + + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0xef}), 0); + CORRADE_COMPARE(converter.meshCount(), 1); + + /* It's easier to just perform the operation right during add() than to + make a copy of the passed MeshData and do it at the endFile(), so the + file exists at this point already */ + CORRADE_VERIFY(Utility::Path::exists(filename)); + + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MeshData{MeshPrimitive::Triangles, 0xb0})); + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh\n"); + } + + /* Getting the result should still work */ + CORRADE_VERIFY(converter.endFile()); + CORRADE_VERIFY(!converter.isConverting()); + CORRADE_VERIFY(Utility::Path::exists(filename)); + CORRADE_COMPARE_AS(filename, + "\xef", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::addMeshNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(MeshData{MeshPrimitive::Triangles, 0}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): mesh conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addMeshLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, Containers::Iterable meshLevels, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, meshCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(meshLevels.size(), 3); + CORRADE_COMPARE(meshLevels[1].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.meshCount(), 0); + CORRADE_COMPARE(converter.add({ + MeshData{MeshPrimitive::Lines, 0}, + MeshData{MeshPrimitive::Triangles, 3, reinterpret_cast(0xdeadbeef)}, + MeshData{MeshPrimitive::Faces, 0}, + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.meshCount(), 1); + CORRADE_COMPARE(converter.add({ + MeshData{MeshPrimitive::Faces, 2}, + MeshData{MeshPrimitive::Meshlets, 1, reinterpret_cast(0xdeadbeef)}, + MeshData{MeshPrimitive::Points, 0}, + }, "hello"), 1); + CORRADE_COMPARE(converter.meshCount(), 2); +} + +void AbstractSceneConverterTest::addMeshLevelsFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.meshCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add({ + MeshData{MeshPrimitive::Triangles, 0}, + MeshData{MeshPrimitive::Triangles, 0} + })); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.meshCount(), 0); +} + +void AbstractSceneConverterTest::addMeshLevelsNoLevels() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(Containers::Iterable{}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): at least one mesh level has to be specified\n"); +} + +void AbstractSceneConverterTest::addMeshLevelsNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + MeshData{MeshPrimitive::Triangles, 0}, + MeshData{MeshPrimitive::Triangles, 0} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): multi-level mesh conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addMeshThroughLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes| + SceneConverterFeature::MeshLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable meshLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(meshLevels.size(), 1); + CORRADE_COMPARE(meshLevels[0].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.add(MeshData{MeshPrimitive::Triangles, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.meshCount(), 1); +} + +void AbstractSceneConverterTest::setMeshAttributeName() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + void doBegin() override {} + + void doSetMeshAttributeName(UnsignedShort field, Containers::StringView name) override { + CORRADE_COMPARE(field, 1337); + CORRADE_COMPARE(name, "hello!"); + setMeshAttributeNameCalled = true; + } + + bool setMeshAttributeNameCalled = false; + } converter; + + CORRADE_VERIFY(true); /* capture correct function name */ + + converter.begin(); + converter.setMeshAttributeName(meshAttributeCustom(1337), "hello!"); + CORRADE_VERIFY(converter.setMeshAttributeNameCalled); +} + +void AbstractSceneConverterTest::setMeshAttributeNameNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + void doBegin() override {} + } converter; + + /* This should work, there's no need for a plugin to implement this */ + converter.begin(); + converter.setMeshAttributeName(meshAttributeCustom(1337), "hello!"); + CORRADE_VERIFY(true); +} + +void AbstractSceneConverterTest::setMeshAttributeNameNotCustom() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMeshes; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.setMeshAttributeName(MeshAttribute::ObjectId, "hello!"); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::setMeshAttributeName(): Trade::MeshAttribute::ObjectId is not custom\n"); +} + +void AbstractSceneConverterTest::addMaterial() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMaterials; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const MaterialData& material, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, materialCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(material.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.materialCount(), 0); + CORRADE_COMPARE(converter.add(MaterialData{{}, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.materialCount(), 1); + CORRADE_COMPARE(converter.add(MaterialData{{}, nullptr, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.materialCount(), 2); +} + +void AbstractSceneConverterTest::addMaterialFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMaterials; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const MaterialData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.materialCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(MaterialData{{}, nullptr})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.materialCount(), 0); +} + +void AbstractSceneConverterTest::addMaterialNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddMaterials; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(MaterialData{{}, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): material conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addTexture() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddTextures; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const TextureData& texture, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, textureCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(texture.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.textureCount(), 0); + CORRADE_COMPARE(converter.add(TextureData{{}, {}, {}, {}, {}, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.textureCount(), 1); + CORRADE_COMPARE(converter.add(TextureData{{}, {}, {}, {}, {}, 0, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.textureCount(), 2); +} + +void AbstractSceneConverterTest::addTextureFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddTextures; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const TextureData&, Containers::StringView) override { + return false; + } + } converter; + + converter.begin(); + CORRADE_COMPARE(converter.textureCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.add(TextureData{{}, {}, {}, {}, {}, 0})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.textureCount(), 0); +} + +void AbstractSceneConverterTest::addTextureNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddTextures; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(TextureData{{}, {}, {}, {}, {}, 0}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): texture conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImage1D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const ImageData1D& image, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image1DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(image.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, {}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); + CORRADE_COMPARE(converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, {}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.image1DCount(), 2); +} + +void AbstractSceneConverterTest::addImage1DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData1D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(!image.isCompressed()); + CORRADE_COMPARE(image.storage().alignment(), 2); + CORRADE_COMPARE(image.format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(image.size(), 3); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add(ImageView1D{ + PixelStorage{}.setAlignment(2), + PixelFormat::RG8Snorm, 3, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImage1DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages1D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData1D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(image.isCompressed()); + CORRADE_COMPARE(image.compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(image.compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(image.size(), 3); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add(CompressedImageView1D{ + CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), + CompressedPixelFormat::Astc3x3x3RGBASrgb, 3, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImage1DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::AddCompressedImages1D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData1D&, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData})); + CORRADE_VERIFY(!converter.add(ImageView1D{PixelFormat::RGBA8Unorm, 1, imageData})); + CORRADE_VERIFY(!converter.add(CompressedImageView1D{CompressedPixelFormat::Astc4x4RGBAUnorm, 1, imageData})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image1DCount(), 0); +} + +void AbstractSceneConverterTest::addImage1DInvalidImage() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData1D{PixelFormat::RGBA8Unorm, {}, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image with a zero size: Vector(0)\n"); +} + +void AbstractSceneConverterTest::addImage1DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + const char imageData[4]{}; + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 1D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImage2D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const ImageData2D& image, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image2DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(image.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, {}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); + CORRADE_COMPARE(converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, {}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.image2DCount(), 2); +} + +void AbstractSceneConverterTest::addImage2DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData2D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(!image.isCompressed()); + CORRADE_COMPARE(image.storage().alignment(), 2); + CORRADE_COMPARE(image.format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(image.size(), (Vector2i{3, 1})); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add(ImageView2D{ + PixelStorage{}.setAlignment(2), + PixelFormat::RG8Snorm, {3, 1}, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImage2DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages2D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData2D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(image.isCompressed()); + CORRADE_COMPARE(image.compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(image.compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(image.size(), (Vector2i{3, 2})); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add(CompressedImageView2D{ + CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), + CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 2}, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImage2DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::AddCompressedImages2D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData2D&, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData})); + CORRADE_VERIFY(!converter.add(ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, imageData})); + CORRADE_VERIFY(!converter.add(CompressedImageView2D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1}, imageData})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image2DCount(), 0); +} + +void AbstractSceneConverterTest::addImage2DZeroSize() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + void doBegin() override {} + } converter; + + const char imageData[16]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {4, 0}, DataFlags{}, imageData}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image with a zero size: Vector(4, 0)\n"); +} + +void AbstractSceneConverterTest::addImage2DNullptr() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, {nullptr, 4}}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image with a nullptr view\n"); +} + +void AbstractSceneConverterTest::addImage2DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + const char imageData[4]{}; + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 2D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImage3D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, const ImageData3D& image, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image3DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(image.importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, {}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); + CORRADE_COMPARE(converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, {}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 1); + CORRADE_COMPARE(converter.image3DCount(), 2); +} + +void AbstractSceneConverterTest::addImage3DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData3D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(!image.isCompressed()); + CORRADE_COMPARE(image.storage().alignment(), 2); + CORRADE_COMPARE(image.format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(image.size(), (Vector3i{1, 3, 1})); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add(ImageView3D{ + PixelStorage{}.setAlignment(2), + PixelFormat::RG8Snorm, {1, 3, 1}, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::addImage3DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages3D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData3D& image, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_VERIFY(image.isCompressed()); + CORRADE_COMPARE(image.compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(image.compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(image.size(), (Vector3i{1, 3, 1})); + CORRADE_VERIFY(image.data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add(CompressedImageView3D{ + CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), + CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 3, 1}, imageData}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::addImage3DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::AddCompressedImages3D; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, const ImageData3D&, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData})); + CORRADE_VERIFY(!converter.add(ImageView3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, imageData})); + CORRADE_VERIFY(!converter.add(CompressedImageView3D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1, 1}, imageData})); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image3DCount(), 0); +} + +void AbstractSceneConverterTest::addImage3DInvalidImage() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {}, nullptr}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image with a zero size: Vector(0, 0, 0)\n"); +} + +void AbstractSceneConverterTest::addImage3DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D; + } + + void doBegin() override {} + } converter; + + const char imageData[4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): 3D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImageLevels1D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image1DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 3); + CORRADE_COMPARE(imageLevels[1].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add({ + /* Arbitrary dimensions should be fine */ + ImageData1D{PixelFormat::RGBA8Unorm, 4, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 2, DataFlags{}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, + ImageData1D{PixelFormat::RGBA8Unorm, 3, DataFlags{}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); + CORRADE_COMPARE(converter.add({ + ImageData1D{PixelFormat::RGBA8Unorm, 2, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, + ImageData1D{PixelFormat::RGBA8Unorm, 4, DataFlags{}, imageData} + }, "hello"), 1); + CORRADE_COMPARE(converter.image1DCount(), 2); +} + +void AbstractSceneConverterTest::addImageLevels1DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(!imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].storage().alignment(), 2); + CORRADE_COMPARE(imageLevels[1].format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(imageLevels[1].size(), 3); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add({ + ImageView1D{PixelFormat::RG8Snorm, 1, imageData}, + ImageView1D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, 3, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels1DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(imageLevels[1].compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(imageLevels[1].size(), 3); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + CORRADE_COMPARE(converter.add({ + CompressedImageView1D{CompressedPixelFormat::Astc3x3x3RGBASrgb, 1, imageData}, + CompressedImageView1D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, 3, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels1DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::AddCompressedImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image1DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add({ + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + ImageView1D{PixelFormat::RGBA8Unorm, 1, imageData}, + ImageView1D{PixelFormat::RGBA8Unorm, 1, imageData}, + })); + CORRADE_VERIFY(!converter.add({ + CompressedImageView1D{CompressedPixelFormat::Astc4x4RGBAUnorm, 1, imageData}, + CompressedImageView1D{CompressedPixelFormat::Astc4x4RGBAUnorm, 1, imageData}, + })); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image1DCount(), 0); +} + +void AbstractSceneConverterTest::addImageLevels1DInvalidImage() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(std::initializer_list>{}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): at least one image level has to be specified\n"); +} + +void AbstractSceneConverterTest::addImageLevels1DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData}, + ImageData1D{PixelFormat::RGBA8Unorm, 1, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): multi-level 1D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImageLevels2D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image2DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 3); + CORRADE_COMPARE(imageLevels[1].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add({ + /* Arbitrary dimensions should be fine */ + ImageData2D{PixelFormat::RGBA8Unorm, {4, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 3}, DataFlags{}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); + CORRADE_COMPARE(converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 3}, DataFlags{}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, + ImageData2D{PixelFormat::RGBA8Unorm, {4, 1}, DataFlags{}, imageData} + }, "hello"), 1); + CORRADE_COMPARE(converter.image2DCount(), 2); +} + +void AbstractSceneConverterTest::addImageLevels2DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(!imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].storage().alignment(), 2); + CORRADE_COMPARE(imageLevels[1].format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(imageLevels[1].size(), (Vector2i{1, 3})); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add({ + ImageView2D{PixelFormat::RG8Snorm, {1, 1}, imageData}, + ImageView2D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, {1, 3}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels2DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(imageLevels[1].compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(imageLevels[1].size(), (Vector2i{3, 1})); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + CORRADE_COMPARE(converter.add({ + CompressedImageView2D{CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 1}, imageData}, + CompressedImageView2D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 1}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels2DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::AddCompressedImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image2DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, imageData}, + ImageView2D{PixelFormat::RGBA8Unorm, {1, 1}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + CompressedImageView2D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1}, imageData}, + CompressedImageView2D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1}, imageData} + })); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image2DCount(), 0); +} + +void AbstractSceneConverterTest::addImageLevels2DNoLevels() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(std::initializer_list>{}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): at least one image level has to be specified\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DZeroSize() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {4, 0}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image level 1 with a zero size: Vector(4, 0)\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DNullptr() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, {nullptr, 4}} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): can't add image level 1 with a nullptr view\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentCompressed() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + ImageData2D a{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}; + ImageData2D b{CompressedPixelFormat::Astc10x10RGBAF, {1, 1}, DataFlags{}, imageData}; + + std::ostringstream out; + Error redirectError{&out}; + converter.add({a, b}); + converter.add({b, b, a}); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::add(): image level 1 is compressed but previous aren't\n" + "Trade::AbstractSceneConverter::add(): image level 2 is not compressed but previous are\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentFormat() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Srgb, {4, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): image levels don't have the same format, expected PixelFormat::RGBA8Unorm but got PixelFormat::RGBA8Srgb for level 2\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentFormatExtra() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelStorage{}, 252, 1037, 4, {2, 2}, DataFlags{}, imageData}, + ImageData2D{PixelStorage{}, 252, 1037, 4, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelStorage{}, 252, 4467, 4, {4, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): image levels don't have the same extra format field, expected 1037 but got 4467 for level 2\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentCompressedFormat() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{CompressedPixelFormat::Bc1RGBAUnorm, {2, 2}, DataFlags{}, imageData}, + ImageData2D{CompressedPixelFormat::Bc1RGBAUnorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{CompressedPixelFormat::Bc1RGBASrgb, {4, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): image levels don't have the same format, expected CompressedPixelFormat::Bc1RGBAUnorm but got CompressedPixelFormat::Bc1RGBASrgb for level 2\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DInconsistentFlags() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {2, 2}, DataFlags{}, imageData, ImageFlag2D::Array}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData, ImageFlag2D::Array}, + ImageData2D{PixelFormat::RGBA8Unorm, {4, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): image levels don't have the same flags, expected ImageFlag2D::Array but got ImageFlags2D{} for level 2\n"); +} + +void AbstractSceneConverterTest::addImageLevels2DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData}, + ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): multi-level 2D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImageLevels3D() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt id, Containers::Iterable imageLevels, Containers::StringView name) override { + /* Camera count should not be increased before the function + returns */ + CORRADE_COMPARE(id, image3DCount()); + + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 3); + CORRADE_COMPARE(imageLevels[1].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4*4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add({ + /* Arbitrary dimensions should be fine */ + ImageData3D{PixelFormat::RGBA8Unorm, {4, 1, 1}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {2, 2, 1}, DataFlags{}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 3}, DataFlags{}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); + CORRADE_COMPARE(converter.add({ + ImageData3D{PixelFormat::RGBA8Unorm, {2, 1, 2}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 3, 1}, DataFlags{}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 4, 1}, DataFlags{}, imageData} + }, "hello"), 1); + CORRADE_COMPARE(converter.image3DCount(), 2); +} + +void AbstractSceneConverterTest::addImageLevels3DView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(!imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].storage().alignment(), 2); + CORRADE_COMPARE(imageLevels[1].format(), PixelFormat::RG8Snorm); + CORRADE_COMPARE(imageLevels[1].size(), (Vector3i{1, 3, 1})); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add({ + ImageView3D{PixelFormat::RG8Snorm, {1, 1, 1}, imageData}, + ImageView3D{PixelStorage{}.setAlignment(2), PixelFormat::RG8Snorm, {1, 3, 1}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels3DCompressedView() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddCompressedImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 2); + CORRADE_VERIFY(imageLevels[1].isCompressed()); + CORRADE_COMPARE(imageLevels[1].compressedStorage().compressedBlockSize(), Vector3i{3}); + CORRADE_COMPARE(imageLevels[1].compressedFormat(), CompressedPixelFormat::Astc3x3x3RGBASrgb); + CORRADE_COMPARE(imageLevels[1].size(), (Vector3i{3, 1, 1})); + CORRADE_VERIFY(imageLevels[1].data()); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[6]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + CORRADE_COMPARE(converter.add({ + CompressedImageView3D{CompressedPixelFormat::Astc3x3x3RGBASrgb, {1, 1, 1}, imageData}, + CompressedImageView3D{CompressedPixelStorage{}.setCompressedBlockSize({3, 3, 3}), CompressedPixelFormat::Astc3x3x3RGBASrgb, {3, 1, 1}, imageData} + }, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::addImageLevels3DFailed() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::AddCompressedImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable, Containers::StringView) override { + return false; + } + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.image3DCount(), 0); + + /* The implementation is expected to print an error message on its own */ + { + std::ostringstream out; + Error redirectError{&out}; + /* Testing all three variants to "fake" coverage for the name-less + overloads as well */ + CORRADE_VERIFY(!converter.add({ + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + ImageView3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, imageData}, + ImageView3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, imageData} + })); + CORRADE_VERIFY(!converter.add({ + CompressedImageView3D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1, 1}, imageData}, + CompressedImageView3D{CompressedPixelFormat::Astc4x4RGBAUnorm, {1, 1, 1}, imageData} + })); + CORRADE_COMPARE(out.str(), ""); + } + + /* It shouldn't abort the whole process */ + CORRADE_VERIFY(converter.isConverting()); + CORRADE_COMPARE(converter.image3DCount(), 0); +} + +void AbstractSceneConverterTest::addImageLevels3DInvalidImage() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add(std::initializer_list>{}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): at least one image level has to be specified\n"); +} + +void AbstractSceneConverterTest::addImageLevels3DNotImplemented() { + CORRADE_SKIP_IF_NO_ASSERT(); + + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + } converter; + + const char imageData[4]{}; + + converter.begin(); + + std::ostringstream out; + Error redirectError{&out}; + converter.add({ + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData}, + ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, DataFlags{}, imageData} + }); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::add(): multi-level 3D image conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::addImage1DThroughLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages1D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 1); + CORRADE_COMPARE(imageLevels[0].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.add(ImageData1D{PixelFormat::RGBA8Unorm, 1, {}, imageData, ImageFlags1D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image1DCount(), 1); +} + +void AbstractSceneConverterTest::addImage2DThroughLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages2D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 1); + CORRADE_COMPARE(imageLevels[0].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.add(ImageData2D{PixelFormat::RGBA8Unorm, {1, 1}, {}, imageData, ImageFlags2D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image2DCount(), 1); +} + +void AbstractSceneConverterTest::addImage3DThroughLevels() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + return SceneConverterFeature::ConvertMultiple| + SceneConverterFeature::AddImages3D| + SceneConverterFeature::ImageLevels; + } + + void doBegin() override {} + + bool doAdd(UnsignedInt, Containers::Iterable imageLevels, Containers::StringView name) override { + CORRADE_COMPARE(name, "hello"); + CORRADE_COMPARE(imageLevels.size(), 1); + CORRADE_COMPARE(imageLevels[0].importerState(), reinterpret_cast(0xdeadbeef)); + + addCalled = true; + return true; + } + + bool addCalled = false; + } converter; + + const char imageData[4]{}; + + converter.begin(); + CORRADE_COMPARE(converter.add(ImageData3D{PixelFormat::RGBA8Unorm, {1, 1, 1}, {}, imageData, ImageFlags3D{}, reinterpret_cast(0xdeadbeef)}, "hello"), 0); + CORRADE_VERIFY(converter.addCalled); + CORRADE_COMPARE(converter.image3DCount(), 1); +} + +void AbstractSceneConverterTest::debugFeature() { + std::ostringstream out; + + Debug{&out} << SceneConverterFeature::ConvertMeshInPlace << SceneConverterFeature(0xdeaddead); + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshInPlace Trade::SceneConverterFeature(0xdeaddead)\n"); +} + +void AbstractSceneConverterTest::debugFeatures() { + std::ostringstream out; + + Debug{&out} << (SceneConverterFeature::ConvertMesh|SceneConverterFeature::ConvertMeshToFile) << SceneConverterFeatures{}; + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMesh|Trade::SceneConverterFeature::ConvertMeshToFile Trade::SceneConverterFeatures{}\n"); +} + +void AbstractSceneConverterTest::debugFeaturesSupersets() { + /* ConvertMeshToData is a superset of ConvertMeshToFile, so only one should + be printed */ + { + std::ostringstream out; + Debug{&out} << (SceneConverterFeature::ConvertMeshToData|SceneConverterFeature::ConvertMeshToFile); + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshToData\n"); + + /* ConvertMultipleToData is a superset of ConvertMultipleToFile, so only + one should be printed */ + } { + std::ostringstream out; + Debug{&out} << (SceneConverterFeature::ConvertMultipleToData|SceneConverterFeature::ConvertMultipleToFile); + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMultipleToData\n"); } } diff --git a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp index 9c5934767..bcde3cf34 100644 --- a/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp +++ b/src/MagnumPlugins/AnySceneConverter/AnySceneConverter.cpp @@ -98,4 +98,4 @@ bool AnySceneConverter::doConvertToFile(const MeshData& mesh, const Containers:: }} CORRADE_PLUGIN_REGISTER(AnySceneConverter, Magnum::Trade::AnySceneConverter, - "cz.mosra.magnum.Trade.AbstractSceneConverter/0.1.2") + "cz.mosra.magnum.Trade.AbstractSceneConverter/0.2")