/* This file is part of Magnum. Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019, 2020, 2021, 2022 Vladimír Vondruš Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #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/AnimationData.h" #include "Magnum/Trade/CameraData.h" #include "Magnum/Trade/ImageData.h" #include "Magnum/Trade/LightData.h" #include "Magnum/Trade/MeshData.h" #include "Magnum/Trade/MaterialData.h" #include "Magnum/Trade/SceneData.h" #include "Magnum/Trade/SkinData.h" #include "Magnum/Trade/TextureData.h" #ifdef MAGNUM_BUILD_DEPRECATED /* needed by deprecated convertToFile() that takes a std::string */ #include #endif #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT #include "Magnum/Trade/configure.h" #endif namespace Corrade { namespace PluginManager { template class MAGNUM_TRADE_EXPORT Manager; }} namespace Magnum { namespace Trade { using namespace Containers::Literals; SceneContents sceneContentsFor(const AbstractImporter& importer) { CORRADE_ASSERT(importer.isOpened(), "Trade::sceneContentsFor(): the importer is not opened", {}); SceneContents contents = SceneContent::Names; if(importer.sceneCount()) contents |= SceneContent::Scenes; if(importer.animationCount()) contents |= SceneContent::Animations; if(importer.lightCount()) contents |= SceneContent::Lights; if(importer.cameraCount()) contents |= SceneContent::Cameras; if(importer.skin2DCount()) contents |= SceneContent::Skins2D; if(importer.skin3DCount()) contents |= SceneContent::Skins3D; if(importer.meshCount()) contents |= SceneContent::Meshes; if(importer.materialCount()) contents |= SceneContent::Materials; if(importer.textureCount()) contents |= SceneContent::Textures; if(importer.image1DCount()) contents |= SceneContent::Images1D; if(importer.image2DCount()) contents |= SceneContent::Images2D; if(importer.image3DCount()) contents |= SceneContent::Images3D; return contents; } SceneContents sceneContentsFor(const AbstractSceneConverter& converter) { const SceneConverterFeatures features = converter.features(); SceneContents contents = SceneContent::Names; if(features & SceneConverterFeature::AddScenes) contents |= SceneContent::Scenes; if(features & SceneConverterFeature::AddAnimations) contents |= SceneContent::Animations; if(features & SceneConverterFeature::AddLights) contents |= SceneContent::Lights; if(features & SceneConverterFeature::AddCameras) contents |= SceneContent::Cameras; if(features & SceneConverterFeature::AddSkins2D) contents |= SceneContent::Skins2D; if(features & SceneConverterFeature::AddSkins3D) contents |= SceneContent::Skins3D; if(features & (SceneConverterFeature::AddMeshes| SceneConverterFeature::ConvertMesh| SceneConverterFeature::ConvertMeshToFile| SceneConverterFeature::ConvertMeshToData)) contents |= SceneContent::Meshes; if(features & SceneConverterFeature::AddMaterials) contents |= SceneContent::Materials; if(features & SceneConverterFeature::AddTextures) contents |= SceneContent::Textures; if(features & (SceneConverterFeature::AddImages1D| SceneConverterFeature::AddCompressedImages1D)) contents |= SceneContent::Images1D; if(features & (SceneConverterFeature::AddImages2D| SceneConverterFeature::AddCompressedImages2D)) contents |= SceneContent::Images2D; if(features & (SceneConverterFeature::AddImages3D| SceneConverterFeature::AddCompressedImages3D)) contents |= SceneContent::Images3D; if(features & SceneConverterFeature::MeshLevels) contents |= SceneContent::MeshLevels; if(features & SceneConverterFeature::ImageLevels) contents |= SceneContent::ImageLevels; return contents; } /* 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 MAGNUM_TRADE_ABSTRACTSCENECONVERTER_PLUGIN_INTERFACE ""_s; } #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT Containers::Array AbstractSceneConverter::pluginSearchPaths() { const Containers::Optional libraryLocation = Utility::Path::libraryLocation(&pluginInterface); return PluginManager::implicitPluginSearchPaths( #ifndef MAGNUM_BUILD_STATIC libraryLocation ? *libraryLocation : Containers::String{}, #else {}, #endif #ifdef CORRADE_IS_DEBUG_BUILD MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_DIR, #else MAGNUM_PLUGINS_SCENECONVERTER_DIR, #endif #ifdef CORRADE_IS_DEBUG_BUILD "magnum-d/" #else "magnum/" #endif "sceneconverters"_s); } #endif AbstractSceneConverter::AbstractSceneConverter() = default; AbstractSceneConverter::AbstractSceneConverter(PluginManager::Manager& manager): PluginManager::AbstractManagingPlugin{manager} {} AbstractSceneConverter::AbstractSceneConverter(PluginManager::AbstractManager& manager, const Containers::StringView& plugin): PluginManager::AbstractManagingPlugin{manager, plugin} {} AbstractSceneConverter::~AbstractSceneConverter() = default; SceneConverterFeatures AbstractSceneConverter::features() const { const SceneConverterFeatures features = doFeatures(); CORRADE_ASSERT(features, "Trade::AbstractSceneConverter::features(): implementation reported no features", {}); return features; } void AbstractSceneConverter::setFlags(SceneConverterFlags flags) { _flags = flags; doSetFlags(flags); } void AbstractSceneConverter::doSetFlags(SceneConverterFlags) {} void AbstractSceneConverter::addFlags(SceneConverterFlags flags) { setFlags(_flags|flags); } void AbstractSceneConverter::clearFlags(SceneConverterFlags flags) { setFlags(_flags & ~flags); } Containers::Optional AbstractSceneConverter::convert(const MeshData& mesh) { abort(); CORRADE_ASSERT(features() & SceneConverterFeature::ConvertMesh, "Trade::AbstractSceneConverter::convert(): mesh conversion not supported", {}); Containers::Optional out = doConvert(mesh); CORRADE_ASSERT(!out || ( (!out->_indexData.deleter() || out->_indexData.deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || out->_indexData.deleter() == ArrayAllocator::deleter) && (!out->_vertexData.deleter() || out->_vertexData.deleter() == static_cast(Implementation::nonOwnedArrayDeleter) || out->_vertexData.deleter() == ArrayAllocator::deleter) && (!out->_attributes.deleter() || out->_attributes.deleter() == static_cast(Implementation::nonOwnedArrayDeleter))), "Trade::AbstractSceneConverter::convert(): implementation is not allowed to use a custom Array deleter", {}); return out; } Containers::Optional AbstractSceneConverter::doConvert(const MeshData&) { CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convert(): mesh conversion advertised but not implemented", {}); } bool AbstractSceneConverter::convertInPlace(MeshData& mesh) { abort(); CORRADE_ASSERT(features() & SceneConverterFeature::ConvertMeshInPlace, "Trade::AbstractSceneConverter::convertInPlace(): mesh conversion not supported", {}); return doConvertInPlace(mesh); } bool AbstractSceneConverter::doConvertInPlace(MeshData&) { CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convertInPlace(): mesh conversion advertised but not implemented", {}); } #ifndef MAGNUM_BUILD_DEPRECATED Containers::Optional> #else Implementation::SceneConverterOptionalButAlsoArray #endif AbstractSceneConverter::convertToData(const MeshData& mesh) { abort(); 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", {}); /* 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&) { CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convertToData(): mesh conversion advertised but not implemented", {}); } bool AbstractSceneConverter::convertToFile(const MeshData& mesh, const Containers::StringView filename) { abort(); 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; } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported", {}); } #ifdef MAGNUM_BUILD_DEPRECATED bool AbstractSceneConverter::convertToFile(const std::string& filename, const MeshData& mesh) { return convertToFile(mesh, filename); } #endif 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> 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() {} bool AbstractSceneConverter::begin() { abort(); _state.emplace(State::Type::Convert); if(features() >= SceneConverterFeature::ConvertMultiple) { if(!doBegin()) { _state = {}; return false; } return true; } else if(features() & SceneConverterFeature::ConvertMesh) { /* Actual operation performed in doAdd(const MeshData&) */ return true; } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::begin(): feature not supported", {}); } bool 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, got" << _state->meshCount; 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", {}); } bool AbstractSceneConverter::beginData() { abort(); _state.emplace(State::Type::ConvertToData); if(features() >= SceneConverterFeature::ConvertMultipleToData) { if(!doBeginData()) { _state = {}; return false; } return true; } else if(features() >= SceneConverterFeature::ConvertMeshToData) { /* Actual operation performed in doAdd(const MeshData&) */ return true; } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginData(): feature not supported", {}); } bool 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, got" << _state->meshCount; 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", {}); } bool AbstractSceneConverter::beginFile(const Containers::StringView filename) { abort(); _state.emplace(State::Type::ConvertToFile); _state->filename = Containers::String::nullTerminatedGlobalView(filename); if(features() >= SceneConverterFeature::ConvertMultipleToFile) { if(!doBeginFile(_state->filename)) { _state = {}; return false; } return true; } else if(features() >= SceneConverterFeature::ConvertMeshToFile) { /* Actual operation performed in doAdd(const MeshData&) */ return true; } else CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::beginFile(): feature not supported", {}); } bool AbstractSceneConverter::doBeginFile(Containers::StringView) { CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMultipleToData, "Trade::AbstractSceneConverter::beginFile(): feature advertised but not implemented", {}); return 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, got" << _state->meshCount; 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::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(field, name); } void AbstractSceneConverter::doSetSceneFieldName(SceneField, 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, got" << _state->meshCount + 1; 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, const 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(attribute, name); } void AbstractSceneConverter::doSetMeshAttributeName(MeshAttribute, 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* #ifndef CORRADE_STANDARD_ASSERT const messagePrefix #endif , 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* #ifndef CORRADE_STANDARD_ASSERT const messagePrefix #endif , 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(), image.flags()}, 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(), image.flags()}, 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, const 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(), image.flags()}; } 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(), image.flags()}; } 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(), image.flags()}, 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(), image.flags()}, 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, const 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(), image.flags()}; } 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(), image.flags()}; } 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(), image.flags()}, 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(), image.flags()}, 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, const 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(), image.flags()}; } 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(), image.flags()}; } return add(data, name); } Containers::Optional AbstractSceneConverter::add(const Containers::Iterable& imageLevels) { return add(imageLevels, {}); } bool AbstractSceneConverter::addImporterContentsInternal(AbstractImporter& importer, const SceneContents contents, const bool noLevelsIfUnsupported) { CORRADE_ASSERT(isConverting(), "Trade::AbstractSceneConverter::addImporterContents(): no conversion in progress", {}); CORRADE_ASSERT(importer.isOpened(), "Trade::AbstractSceneConverter::addImporterContents(): the importer is not opened", {}); const SceneContents contentsSupported = sceneContentsFor(*this); #ifndef CORRADE_NO_ASSERT const SceneContents contentsPresentExceptLevels = contents & sceneContentsFor(importer); #endif CORRADE_ASSERT(!(contentsPresentExceptLevels & ~contentsSupported), "Trade::AbstractSceneConverter::addImporterContents(): unsupported contents" << Debug::packed << (contentsPresentExceptLevels & ~contentsSupported), {}); /* These are in dependency order -- i.e., images between textures that reference them or scenes before animations that reference them. The actual bound checks (if any) are left on concrete implementations. */ if(contents & SceneContent::Meshes) { Containers::Array levels; for(UnsignedInt i = 0, iMax = importer.meshCount(); i != iMax; ++i) { if(flags() & SceneConverterFlag::Verbose) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): adding mesh" << i << "out of" << iMax; UnsignedInt levelCount = contents & SceneContent::MeshLevels ? importer.meshLevelCount(i) : 1; if(levelCount != 1 && !(contentsSupported & SceneContent::MeshLevels)) { if(noLevelsIfUnsupported) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring extra" << levelCount - 1 << "levels of mesh" << i << "not supported by the converter"; levelCount = 1; /* Not an assert because having to check this in advance could be prohibitively expensive */ } else { Error{} << "Trade::AbstractSceneConverter::addImporterContents(): mesh" << i << "contains" << levelCount << "levels but the converter doesn't support" << SceneConverterFeature::MeshLevels; return false; } } arrayReserve(levels, levelCount); arrayResize(levels, NoInit, 0); /** @todo arrayClear() */ for(UnsignedInt j = 0; j != levelCount; ++j) { if((flags() & SceneConverterFlag::Verbose) && levelCount != 1) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): importing mesh" << i << "level" << j << "out of" << levelCount; Containers::Optional mesh = importer.mesh(i, j); if(!mesh) return false; /* Propagate custom attribute names, skip ones that are empty. Compared to data names this is done always to avoid information loss. */ for(UnsignedInt k = 0; k != mesh->attributeCount(); ++k) { /** @todo have some kind of a map to not have to query the same custom attribute again for each mesh */ const Trade::MeshAttribute name = mesh->attributeName(k); if(!isMeshAttributeCustom(name)) continue; if(const Containers::String nameString = importer.meshAttributeName(name)) { setMeshAttributeName(name, nameString); } } arrayAppend(levels, *std::move(mesh)); } const Containers::String name = contents & SceneContent::Names ? importer.meshName(i) : Containers::String{}; if(levelCount != 1) { if(!add(levels, name)) return false; } else { if(!add(levels[0], name)) return false; } } } if(contents & SceneContent::Images1D) { Containers::Array levels; for(UnsignedInt i = 0, iMax = importer.image1DCount(); i != iMax; ++i) { if(flags() & SceneConverterFlag::Verbose) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): adding 1D image" << i << "out of" << iMax; UnsignedInt levelCount = contents & SceneContent::ImageLevels ? importer.image1DLevelCount(i) : 1; if(levelCount != 1 && !(contentsSupported & SceneContent::ImageLevels)) { if(noLevelsIfUnsupported) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring extra" << levelCount - 1 << "levels of 1D image" << i << "not supported by the converter"; levelCount = 1; /* Not an assert because having to check this in advance could be prohibitively expensive (decoding an arbitrary amount of images twice...) */ } else { Error{} << "Trade::AbstractSceneConverter::addImporterContents(): 1D image" << i << "contains" << levelCount << "levels but the converter doesn't support" << SceneConverterFeature::ImageLevels; return false; } } arrayReserve(levels, levelCount); arrayResize(levels, NoInit, 0); /** @todo arrayClear() */ for(UnsignedInt j = 0; j != levelCount; ++j) { if((flags() & SceneConverterFlag::Verbose) && levelCount != 1) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): importing 1D image" << i << "level" << j << "out of" << levelCount; Containers::Optional image = importer.image1D(i, j); if(!image) return false; if(image->isCompressed() && !(features() & SceneConverterFeature::AddCompressedImages1D)) { Error{} << "Trade::AbstractSceneConverter::addImporterContents(): 1D image" << i << "level" << j << "is compressed but the converter doesn't support" << SceneConverterFeature::AddCompressedImages1D; return false; } if(!image->isCompressed() && !(features() & SceneConverterFeature::AddImages1D)) { Error{} << "Trade::AbstractSceneConverter::addImporterContents(): 1D image" << i << "level" << j << "is uncompressed but the converter doesn't support" << SceneConverterFeature::AddImages1D; return false; } arrayAppend(levels, *std::move(image)); } const Containers::String name = contents & SceneContent::Names ? importer.image1DName(i) : Containers::String{}; if(levelCount != 1) { if(!add(levels, name)) return false; } else { if(!add(levels[0], name)) return false; } } } if(contents & SceneContent::Images2D) { Containers::Array levels; for(UnsignedInt i = 0, iMax = importer.image2DCount(); i != iMax; ++i) { if(flags() & SceneConverterFlag::Verbose) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): adding 2D image" << i << "out of" << iMax; UnsignedInt levelCount = contents & SceneContent::ImageLevels ? importer.image2DLevelCount(i) : 1; if(levelCount != 1 && !(contentsSupported & SceneContent::ImageLevels)) { if(noLevelsIfUnsupported) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring extra" << levelCount - 1 << "levels of 2D image" << i << "not supported by the converter"; levelCount = 1; /* Not an assert because having to check this in advance could be prohibitively expensive (decoding an arbitrary amount of images twice...) */ } else { Error{} << "Trade::AbstractSceneConverter::addImporterContents(): 2D image" << i << "contains" << levelCount << "levels but the converter doesn't support" << SceneConverterFeature::ImageLevels; return false; } } arrayReserve(levels, levelCount); arrayResize(levels, NoInit, 0); /** @todo arrayClear() */ for(UnsignedInt j = 0; j != levelCount; ++j) { if((flags() & SceneConverterFlag::Verbose) && levelCount != 1) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): importing 2D image" << i << "level" << j << "out of" << levelCount; Containers::Optional image = importer.image2D(i, j); if(!image) return false; if(image->isCompressed() && !(features() & SceneConverterFeature::AddCompressedImages2D)) { Error{} << "Trade::AbstractSceneConverter::addImporterContents(): 2D image" << i << "level" << j << "is compressed but the converter doesn't support" << SceneConverterFeature::AddCompressedImages2D; return false; } if(!image->isCompressed() && !(features() & SceneConverterFeature::AddImages2D)) { Error{} << "Trade::AbstractSceneConverter::addImporterContents(): 2D image" << i << "level" << j << "is uncompressed but the converter doesn't support" << SceneConverterFeature::AddImages2D; return false; } arrayAppend(levels, *std::move(image)); } const Containers::String name = contents & SceneContent::Names ? importer.image2DName(i) : Containers::String{}; if(levelCount != 1) { if(!add(levels, name)) return false; } else { if(!add(levels[0], name)) return false; } } } if(contents & SceneContent::Images3D) { Containers::Array levels; for(UnsignedInt i = 0, iMax = importer.image3DCount(); i != iMax; ++i) { if(flags() & SceneConverterFlag::Verbose) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): adding 3D image" << i << "out of" << iMax; UnsignedInt levelCount = contents & SceneContent::ImageLevels ? importer.image3DLevelCount(i) : 1; if(levelCount != 1 && !(contentsSupported & SceneContent::ImageLevels)) { if(noLevelsIfUnsupported) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring extra" << levelCount - 1 << "levels of 3D image" << i << "not supported by the converter"; levelCount = 1; /* Not an assert because having to check this in advance could be prohibitively expensive (decoding an arbitrary amount of images twice...) */ } else { Error{} << "Trade::AbstractSceneConverter::addImporterContents(): 3D image" << i << "contains" << levelCount << "levels but the converter doesn't support" << SceneConverterFeature::ImageLevels; return false; } } arrayReserve(levels, levelCount); arrayResize(levels, NoInit, 0); /** @todo arrayClear() */ for(UnsignedInt j = 0; j != levelCount; ++j) { if((flags() & SceneConverterFlag::Verbose) && levelCount != 1) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): importing 3D image" << i << "level" << j << "out of" << levelCount; Containers::Optional image = importer.image3D(i, j); if(!image) return false; if(image->isCompressed() && !(features() & SceneConverterFeature::AddCompressedImages3D)) { Error{} << "Trade::AbstractSceneConverter::addImporterContents(): 3D image" << i << "level" << j << "is compressed but the converter doesn't support" << SceneConverterFeature::AddCompressedImages3D; return false; } if(!image->isCompressed() && !(features() & SceneConverterFeature::AddImages3D)) { Error{} << "Trade::AbstractSceneConverter::addImporterContents(): 3D image" << i << "level" << j << "is uncompressed but the converter doesn't support" << SceneConverterFeature::AddImages3D; return false; } arrayAppend(levels, *std::move(image)); } const Containers::String name = contents & SceneContent::Names ? importer.image3DName(i) : Containers::String{}; if(levelCount != 1) { if(!add(levels, name)) return false; } else { if(!add(levels[0], name)) return false; } } } if(contents & SceneContent::Textures) { for(UnsignedInt i = 0, iMax = importer.textureCount(); i != iMax; ++i) { if(flags() & SceneConverterFlag::Verbose) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): adding texture" << i << "out of" << iMax; const Containers::Optional texture = importer.texture(i); if(!texture || !add(*texture, contents & SceneContent::Names ? importer.textureName(i) : Containers::String{})) return false; } } if(contents & SceneContent::Materials) { for(UnsignedInt i = 0, iMax = importer.materialCount(); i != iMax; ++i) { if(flags() & SceneConverterFlag::Verbose) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): adding material" << i << "out of" << iMax; const Containers::Optional material = importer.material(i); if(!material || !add(*material, contents & SceneContent::Names ? importer.materialName(i) : Containers::String{})) return false; } } if(contents & SceneContent::Lights) { for(UnsignedInt i = 0, iMax = importer.lightCount(); i != iMax; ++i) { if(flags() & SceneConverterFlag::Verbose) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): adding light" << i << "out of" << iMax; const Containers::Optional light = importer.light(i); if(!light || !add(*light, contents & SceneContent::Names ? importer.lightName(i) : Containers::String{})) return false; } } if(contents & SceneContent::Cameras) { for(UnsignedInt i = 0, iMax = importer.cameraCount(); i != iMax; ++i) { if(flags() & SceneConverterFlag::Verbose) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): adding camera" << i << "out of" << iMax; const Containers::Optional camera = importer.camera(i); if(!camera || !add(*camera, contents & SceneContent::Names ? importer.cameraName(i) : Containers::String{})) return false; } } if(contents & SceneContent::Scenes) { /* Propagate object names, skip ones that are empty */ if(contents & SceneContent::Names) for(UnsignedLong i = 0, iMax = importer.objectCount(); i != iMax; ++i) { if(const Containers::String name = importer.objectName(i)) setObjectName(i, name); } for(UnsignedInt i = 0, iMax = importer.sceneCount(); i != iMax; ++i) { if(flags() & SceneConverterFlag::Verbose) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): adding scene" << i << "out of" << iMax; Containers::Optional scene = importer.scene(i); if(!scene) return false; /* Propagate custom field names, skip ones that are empty. Compared to data names this is done always to avoid information loss. */ for(UnsignedInt j = 0; j != scene->fieldCount(); ++j) { /** @todo have some kind of a map to not have to query the same field again for each scene */ const Trade::SceneField name = scene->fieldName(j); if(!isSceneFieldCustom(name)) continue; if(const Containers::String nameString = importer.sceneFieldName(name)) { setSceneFieldName(name, nameString); } } if(!scene || !add(*scene, contents & SceneContent::Names ? importer.sceneName(i) : Containers::String{})) return false; } const Int defaultScene = importer.defaultScene(); if(defaultScene != -1) setDefaultScene(defaultScene); } if(contents & SceneContent::Skins2D) { for(UnsignedInt i = 0, iMax = importer.skin2DCount(); i != iMax; ++i) { if(flags() & SceneConverterFlag::Verbose) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): adding 2D skin" << i << "out of" << iMax; const Containers::Optional skin = importer.skin2D(i); if(!skin || !add(*skin, contents & SceneContent::Names ? importer.skin2DName(i) : Containers::String{})) return false; } } if(contents & SceneContent::Skins3D) { for(UnsignedInt i = 0, iMax = importer.skin3DCount(); i != iMax; ++i) { if(flags() & SceneConverterFlag::Verbose) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): adding 3D skin" << i << "out of" << iMax; const Containers::Optional skin = importer.skin3D(i); if(!skin || !add(*skin, contents & SceneContent::Names ? importer.skin3DName(i) : Containers::String{})) return false; } } if(contents & SceneContent::Animations) { for(UnsignedInt i = 0, iMax = importer.animationCount(); i != iMax; ++i) { if(flags() & SceneConverterFlag::Verbose) Debug{} << "Trade::AbstractSceneConverter::addImporterContents(): adding animation" << i << "out of" << iMax; const Containers::Optional animation = importer.animation(i); if(!animation || !add(*animation, contents & SceneContent::Names ? importer.animationName(i) : Containers::String{})) return false; } } return true; } bool AbstractSceneConverter::addImporterContents(AbstractImporter& importer, const SceneContents contents) { return addImporterContentsInternal(importer, contents, false); } bool AbstractSceneConverter::addSupportedImporterContents(AbstractImporter& importer, const SceneContents contents) { /* To avoid accidental differences in handling SceneConverterFeatures in sceneContentsFor(const AbstractSceneConverter&) and here, this branches on SceneContents instead of SceneConverterFeatures */ const SceneContents wantedButUnsupported = contents & ~sceneContentsFor(*this); /* To avoid needlessly querying fooCount() several times (which might be expensive in certain unfortunate cases), this basically unwraps the contents of sceneContentsFor(const AbstractImporter&) and adds warnings there. The data kinds are in dependency order, matching the order in addImporterContentsInternal(), so when warnings are printed they're not in an order different from the verbose output for no reason. */ SceneContents used = contents; UnsignedInt count; if((wantedButUnsupported & SceneContent::Meshes) && (count = importer.meshCount())) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring" << count << "meshes not supported by the converter"; used &= ~SceneContent::Meshes; } if((wantedButUnsupported & SceneContent::Images1D) && (count = importer.image1DCount())) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring" << count << "1D images not supported by the converter"; used &= ~SceneContent::Images1D; } if((wantedButUnsupported & SceneContent::Images2D) && (count = importer.image2DCount())) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring" << count << "2D images not supported by the converter"; used &= ~SceneContent::Images2D; } if((wantedButUnsupported & SceneContent::Images3D) && (count = importer.image3DCount())) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring" << count << "3D images not supported by the converter"; used &= ~SceneContent::Images3D; } if((wantedButUnsupported & SceneContent::Textures) && (count = importer.textureCount())) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring" << count << "textures not supported by the converter"; used &= ~SceneContent::Textures; } if((wantedButUnsupported & SceneContent::Materials) && (count = importer.materialCount())) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring" << count << "materials not supported by the converter"; used &= ~SceneContent::Materials; } if((wantedButUnsupported & SceneContent::Lights) && (count = importer.lightCount())) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring" << count << "lights not supported by the converter"; used &= ~SceneContent::Lights; } if((wantedButUnsupported & SceneContent::Cameras) && (count = importer.cameraCount())) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring" << count << "cameras not supported by the converter"; used &= ~SceneContent::Cameras; } if((wantedButUnsupported & SceneContent::Scenes) && (count = importer.sceneCount())) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring" << count << "scenes not supported by the converter"; used &= ~SceneContent::Scenes; } if((wantedButUnsupported & SceneContent::Skins2D) && (count = importer.skin2DCount())) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring" << count << "2D skins not supported by the converter"; used &= ~SceneContent::Skins2D; } if((wantedButUnsupported & SceneContent::Skins3D) && (count = importer.skin3DCount())) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring" << count << "3D skins not supported by the converter"; used &= ~SceneContent::Skins3D; } if((wantedButUnsupported & SceneContent::Animations) && (count = importer.animationCount())) { Warning{} << "Trade::AbstractSceneConverter::addSupportedImporterContents(): ignoring" << count << "animations not supported by the converter"; used &= ~SceneContent::Animations; } /* MeshLevels and ImageLevels handled inside */ return addImporterContentsInternal(importer, used, true); } Debug& operator<<(Debug& debug, const SceneConverterFeature value) { const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; if(!packed) debug << "Trade::SceneConverterFeature" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ #define _c(v) case SceneConverterFeature::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(ConvertMesh) _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 << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const SceneConverterFeatures value) { return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Trade::SceneConverterFeatures{}", { SceneConverterFeature::ConvertMesh, SceneConverterFeature::ConvertMeshInPlace, SceneConverterFeature::ConvertMeshToData, /* Implied by ConvertMeshToData, has to be after */ 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) { debug << "Trade::SceneConverterFlag" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ #define _c(v) case SceneConverterFlag::v: return debug << "::" #v; _c(Quiet) _c(Verbose) #undef _c /* LCOV_EXCL_STOP */ } return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; } Debug& operator<<(Debug& debug, const SceneConverterFlags value) { return Containers::enumSetDebugOutput(debug, value, "Trade::SceneConverterFlags{}", { SceneConverterFlag::Quiet, SceneConverterFlag::Verbose}); } Debug& operator<<(Debug& debug, const SceneContent value) { const bool packed = debug.immediateFlags() >= Debug::Flag::Packed; if(!packed) debug << "Trade::SceneContent" << Debug::nospace; switch(value) { /* LCOV_EXCL_START */ #define _c(v) case SceneContent::v: return debug << (packed ? "" : "::") << Debug::nospace << #v; _c(Scenes) _c(Animations) _c(Lights) _c(Cameras) _c(Skins2D) _c(Skins3D) _c(Meshes) _c(Materials) _c(Textures) _c(Images1D) _c(Images2D) _c(Images3D) _c(MeshLevels) _c(ImageLevels) _c(Names) #undef _c /* LCOV_EXCL_STOP */ } return debug << (packed ? "" : "(") << Debug::nospace << reinterpret_cast(UnsignedInt(value)) << Debug::nospace << (packed ? "" : ")"); } Debug& operator<<(Debug& debug, const SceneContents value) { return Containers::enumSetDebugOutput(debug, value, debug.immediateFlags() >= Debug::Flag::Packed ? "{}" : "Trade::SceneContents{}", { SceneContent::Scenes, SceneContent::Animations, SceneContent::Lights, SceneContent::Cameras, SceneContent::Skins2D, SceneContent::Skins3D, SceneContent::Meshes, SceneContent::Materials, SceneContent::Textures, SceneContent::Images1D, SceneContent::Images2D, SceneContent::Images3D, SceneContent::MeshLevels, SceneContent::ImageLevels, SceneContent::Names}); } }}