diff --git a/CMakeLists.txt b/CMakeLists.txt index 48b06611c..da7d6f852 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -356,6 +356,10 @@ set(MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_BINA set(MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}/importers) set(MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/importers) set(MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}/importers) +set(MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}/sceneconverters) +set(MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}/sceneconverters) +set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/sceneconverters) +set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}/sceneconverters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}/audioimporters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}/audioimporters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/audioimporters) @@ -394,6 +398,7 @@ if(MAGNUM_PLUGINS_DIR) set(MAGNUM_PLUGINS_FONTCONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/fontconverters) set(MAGNUM_PLUGINS_IMAGECONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/imageconverters) set(MAGNUM_PLUGINS_IMPORTER_DIR ${MAGNUM_PLUGINS_DIR}/importers) + set(MAGNUM_PLUGINS_SCENECONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/sceneconverters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_DIR ${MAGNUM_PLUGINS_DIR}/audioimporters) endif() if(MAGNUM_PLUGINS_DEBUG_DIR) @@ -402,12 +407,14 @@ if(MAGNUM_PLUGINS_DEBUG_DIR) set(MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/imageconverters) set(MAGNUM_PLUGINS_IMPORTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/importers) set(MAGNUM_PLUGINS_FONT_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/fonts) + set(MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/sceneconverters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/audioimporters) endif() if(MAGNUM_PLUGINS_RELEASE_DIR) set(MAGNUM_PLUGINS_FONTCONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/fontconverters) set(MAGNUM_PLUGINS_IMAGECONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/imageconverters) set(MAGNUM_PLUGINS_IMPORTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/importers) + set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/sceneconverters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/audioimporters) endif() diff --git a/doc/changelog.dox b/doc/changelog.dox index 37f9f1eac..e2a8a149f 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -270,6 +270,7 @@ See also: - New @ref magnum-sceneconverter "magnum-sceneconverter" tool, similar to @ref magnum-imageconverter "magnum-imageconverter" but suited for general scene formats +- New @ref Trade::AbstractSceneConverter plugin interface - Ability to import image mip levels via an additional parameter in @ref Trade::AbstractImporter::image2D(), @ref Trade::AbstractImporter::image2DLevelCount() and similar APIs for 1D diff --git a/doc/plugins.dox b/doc/plugins.dox index fd3602f95..a2d746e79 100644 --- a/doc/plugins.dox +++ b/doc/plugins.dox @@ -64,6 +64,9 @@ of given type. Magnum provides these plugin interfaces: - @ref Trade::AbstractImageConverter --- conversion among various image formats. See `*ImageConverter` classes in the @ref Trade namespace for available image converter plugins. +- @ref Trade::AbstractSceneConverter --- conversion among various scene + formats, mesh optimization etc. See `*SceneConverter` classes in the + @ref Trade namespace for available scene converter plugins. - @ref Text::AbstractFont --- font loading and glyph layout. See `*Font` classes in the @ref Text namespace for available font plugins. - @ref Text::AbstractFontConverter --- font and glyph cache conversion. See diff --git a/modules/FindMagnum.cmake b/modules/FindMagnum.cmake index 30161db04..ea82c72d9 100644 --- a/modules/FindMagnum.cmake +++ b/modules/FindMagnum.cmake @@ -32,6 +32,8 @@ # font converter plugins # MAGNUM_PLUGINS_IMAGECONVERTER[|_DEBUG|_RELEASE]_DIR - Directory with dynamic # image converter plugins +# MAGNUM_PLUGINS_SCENECONVERTER[|_DEBUG|_RELEASE]_DIR - Directory with dynamic +# scene converter plugins # MAGNUM_PLUGINS_IMPORTER[|_DEBUG|_RELEASE]_DIR - Directory with dynamic # importer plugins # MAGNUM_PLUGINS_AUDIOIMPORTER[|_DEBUG|_RELEASE]_DIR - Directory with dynamic @@ -175,6 +177,10 @@ # plugin binary installation directory # MAGNUM_PLUGINS_IMPORTER_[DEBUG|RELEASE]_LIBRARY_INSTALL_DIR - Importer # plugin library installation directory +# MAGNUM_PLUGINS_SCENECONVERTER_[DEBUG|RELEASE]_BINARY_INSTALL_DIR - Scene +# converter plugin binary installation directory +# MAGNUM_PLUGINS_SCENECONVERTER_[DEBUG|RELEASE]_LIBRARY_INSTALL_DIR - Scene +# converter plugin library installation directory # MAGNUM_PLUGINS_AUDIOIMPORTER_[DEBUG|RELEASE]_BINARY_INSTALL_DIR - Audio # importer plugin binary installation directory # MAGNUM_PLUGINS_AUDIOIMPORTER_[DEBUG|RELEASE]_LIBRARY_INSTALL_DIR - Audio @@ -456,7 +462,7 @@ set(_MAGNUM_ObjImporter_DEPENDENCIES MeshTools) # and below foreach(_component ${_MAGNUM_PLUGIN_COMPONENT_LIST}) if(_component MATCHES ".+AudioImporter") list(APPEND _MAGNUM_${_component}_DEPENDENCIES Audio) - elseif(_component MATCHES ".+(Importer|ImageConverter)") + elseif(_component MATCHES ".+(Importer|ImageConverter|SceneConverter)") list(APPEND _MAGNUM_${_component}_DEPENDENCIES Trade) elseif(_component MATCHES ".+(Font|FontConverter)") list(APPEND _MAGNUM_${_component}_DEPENDENCIES Text TextureTools) @@ -547,6 +553,10 @@ foreach(_component ${Magnum_FIND_COMPONENTS}) elseif(_component MATCHES ".+ImageConverter$") set(_MAGNUM_${_COMPONENT}_PATH_SUFFIX imageconverters) + # SceneConverter plugin specific name suffixes + elseif(_component MATCHES ".+SceneConverter$") + set(_MAGNUM_${_COMPONENT}_PATH_SUFFIX sceneconverters) + # FontConverter plugin specific name suffixes elseif(_component MATCHES ".+FontConverter$") set(_MAGNUM_${_COMPONENT}_PATH_SUFFIX fontconverters) @@ -1115,6 +1125,10 @@ set(MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_BINA set(MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}/importers) set(MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/importers) set(MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}/importers) +set(MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}/sceneconverters) +set(MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}/sceneconverters) +set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}/sceneconverters) +set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/sceneconverters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}/audioimporters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_LIBRARY_INSTALL_DIR ${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}/audioimporters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_RELEASE_BINARY_INSTALL_DIR ${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}/audioimporters) @@ -1141,6 +1155,7 @@ if(MAGNUM_PLUGINS_DIR) set(MAGNUM_PLUGINS_FONTCONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/fontconverters) set(MAGNUM_PLUGINS_IMAGECONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/imageconverters) set(MAGNUM_PLUGINS_IMPORTER_DIR ${MAGNUM_PLUGINS_DIR}/importers) + set(MAGNUM_PLUGINS_SCENECONVERTER_DIR ${MAGNUM_PLUGINS_DIR}/sceneconverters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_DIR ${MAGNUM_PLUGINS_DIR}/audioimporters) endif() if(MAGNUM_PLUGINS_DEBUG_DIR) @@ -1149,11 +1164,13 @@ if(MAGNUM_PLUGINS_DEBUG_DIR) set(MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/imageconverters) set(MAGNUM_PLUGINS_IMPORTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/importers) set(MAGNUM_PLUGINS_FONT_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/fonts) + set(MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/sceneconverters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_DEBUG_DIR ${MAGNUM_PLUGINS_DEBUG_DIR}/audioimporters) endif() if(MAGNUM_PLUGINS_RELEASE_DIR) set(MAGNUM_PLUGINS_FONTCONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/fontconverters) set(MAGNUM_PLUGINS_IMAGECONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/imageconverters) set(MAGNUM_PLUGINS_IMPORTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/importers) + set(MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/sceneconverters) set(MAGNUM_PLUGINS_AUDIOIMPORTER_RELEASE_DIR ${MAGNUM_PLUGINS_RELEASE_DIR}/audioimporters) endif() diff --git a/src/Magnum/Trade/AbstractSceneConverter.cpp b/src/Magnum/Trade/AbstractSceneConverter.cpp new file mode 100644 index 000000000..959afd340 --- /dev/null +++ b/src/Magnum/Trade/AbstractSceneConverter.cpp @@ -0,0 +1,198 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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 "Magnum/Trade/ArrayAllocator.h" +#include "Magnum/Trade/MeshData.h" + +#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT +#include "Magnum/Trade/configure.h" +#endif + +namespace Magnum { namespace Trade { + +std::string AbstractSceneConverter::pluginInterface() { + return "cz.mosra.magnum.Trade.AbstractSceneConverter/0.1"; +} + +#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT +std::vector AbstractSceneConverter::pluginSearchPaths() { + return PluginManager::implicitPluginSearchPaths( + #ifndef MAGNUM_BUILD_STATIC + Utility::Directory::libraryLocation(&pluginInterface), + #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"); +} +#endif + +AbstractSceneConverter::AbstractSceneConverter() = default; + +AbstractSceneConverter::AbstractSceneConverter(PluginManager::Manager& manager): PluginManager::AbstractManagingPlugin{manager} {} + +AbstractSceneConverter::AbstractSceneConverter(PluginManager::AbstractManager& manager, const std::string& plugin): PluginManager::AbstractManagingPlugin{manager, plugin} {} + +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) {} + +Containers::Optional AbstractSceneConverter::convert(const MeshData& mesh) { + 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() == Implementation::nonOwnedArrayDeleter || out->_indexData.deleter() == ArrayAllocator::deleter) && + (!out->_vertexData.deleter() || out->_vertexData.deleter() == Implementation::nonOwnedArrayDeleter || out->_vertexData.deleter() == ArrayAllocator::deleter) && + (!out->_attributes.deleter() || out->_attributes.deleter() == reinterpret_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) { + 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", {}); +} + +Containers::Array AbstractSceneConverter::convertToData(const MeshData& mesh) { + CORRADE_ASSERT(features() & SceneConverterFeature::ConvertMeshToData, + "Trade::AbstractSceneConverter::convertToData(): mesh conversion not supported", {}); + + Containers::Array out = doConvertToData(mesh); + CORRADE_ASSERT(!out || !out.deleter() || out.deleter() == Implementation::nonOwnedArrayDeleter || out.deleter() == ArrayAllocator::deleter, + "Trade::AbstractSceneConverter::convertToData(): implementation is not allowed to use a custom Array deleter", {}); + return out; +} + +Containers::Array AbstractSceneConverter::doConvertToData(const MeshData&) { + CORRADE_ASSERT_UNREACHABLE("Trade::AbstractSceneConverter::convertToData(): mesh conversion advertised but not implemented", {}); +} + +bool AbstractSceneConverter::convertToFile(const std::string& filename, const MeshData& mesh) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToFile, + "Trade::AbstractSceneConverter::convertToFile(): mesh conversion not supported", {}); + + return doConvertToFile(filename, mesh); +} + +bool AbstractSceneConverter::doConvertToFile(const std::string& filename, const MeshData& mesh) { + CORRADE_ASSERT(features() >= SceneConverterFeature::ConvertMeshToData, "Trade::AbstractSceneConverter::convertToFile(): mesh conversion advertised but not implemented", false); + + const auto data = doConvertToData(mesh); + /* No deleter checks as it doesn't matter here */ + if(!data) return false; + + /* Open file */ + if(!Utility::Directory::write(filename, data)) { + Error() << "Trade::AbstractSceneConverter::convertToFile(): cannot write to file" << filename; + return false; + } + + return true; +} + +Debug& operator<<(Debug& debug, const SceneConverterFeature value) { + debug << "Trade::SceneConverterFeature" << Debug::nospace; + + switch(value) { + /* LCOV_EXCL_START */ + #define _c(v) case SceneConverterFeature::v: return debug << "::" #v; + _c(ConvertMesh) + _c(ConvertMeshInPlace) + _c(ConvertMeshToData) + _c(ConvertMeshToFile) + #undef _c + /* LCOV_EXCL_STOP */ + } + + return debug << "(" << Debug::nospace << reinterpret_cast(UnsignedByte(value)) << Debug::nospace << ")"; +} + +Debug& operator<<(Debug& debug, const SceneConverterFeatures value) { + return Containers::enumSetDebugOutput(debug, value, "Trade::SceneConverterFeatures{}", { + SceneConverterFeature::ConvertMesh, + SceneConverterFeature::ConvertMeshInPlace, + SceneConverterFeature::ConvertMeshToData, + /* Implied by ConvertMeshToData, has to be after */ + SceneConverterFeature::ConvertMeshToFile}); +} + +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(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::Verbose}); +} + +}} diff --git a/src/Magnum/Trade/AbstractSceneConverter.h b/src/Magnum/Trade/AbstractSceneConverter.h new file mode 100644 index 000000000..623972e05 --- /dev/null +++ b/src/Magnum/Trade/AbstractSceneConverter.h @@ -0,0 +1,323 @@ +#ifndef Magnum_Trade_AbstractSceneConverter_h +#define Magnum_Trade_AbstractSceneConverter_h +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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. +*/ + +/** @file + * @brief Class @ref Magnum::Trade::AbstractSceneConverter, enum @ref Magnum::Trade::SceneConverterFeature, enum set @ref Magnum::Trade::SceneConverterFeatures + * @m_since_latest + */ + +#include + +#include "Magnum/Magnum.h" +#include "Magnum/Trade/Trade.h" +#include "Magnum/Trade/visibility.h" + +namespace Magnum { namespace Trade { + +/** +@brief Features supported by a scene converter +@m_since_latest + +@see @ref SceneConverterFeatures, @ref AbstractSceneConverter::features() +*/ +enum class SceneConverterFeature: UnsignedByte { + /** + * Convert a mesh with + * @ref AbstractSceneConverter::convert(const MeshData&). + */ + ConvertMesh = 1 << 0, + + /** + * Convert a mesh in-place with + * @ref AbstractSceneConverter::convertInPlace(MeshData&). + */ + ConvertMeshInPlace = 1 << 1, + + /** + * Converting a mesh to a file with + * @ref AbstractSceneConverter::convertToFile(const std::string&, const MeshData&). + */ + ConvertMeshToFile = 1 << 2, + + /** + * Converting a mesh to raw data with + * @ref AbstractSceneConverter::convertToData(const MeshData&). Implies + * @ref SceneConverterFeature::ConvertMeshToFile. + */ + ConvertMeshToData = ConvertMeshToFile|(1 << 3) +}; + +/** +@brief Features supported by a scene converter +@m_since_latest + +@see @ref AbstractSceneConverter::features() +*/ +typedef Containers::EnumSet SceneConverterFeatures; + +CORRADE_ENUMSET_OPERATORS(SceneConverterFeatures) + +/** @debugoperatorenum{SceneConverterFeature} */ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneConverterFeature value); + +/** @debugoperatorenum{SceneConverterFeatures} */ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneConverterFeatures value); + +/** +@brief Scene converter flag +@m_since_latest + +@see @ref SceneConverterFlags, @ref AbstractSceneConverter::setFlags() +*/ +enum class SceneConverterFlag: UnsignedByte { + /** + * Print verbose diagnostic during import. By default the importer only + * prints messages on error or when some operation might cause unexpected + * data modification or loss. + */ + Verbose = 1 << 0 + + /** @todo Y flip */ +}; + +/** +@brief Scene converter flags +@m_since_latest + +@see @ref AbstractImporter::setFlags() +*/ +typedef Containers::EnumSet SceneConverterFlags; + +CORRADE_ENUMSET_OPERATORS(SceneConverterFlags) + +/** +@debugoperatorenum{SceneConverterFlag} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneConverterFlag value); + +/** +@debugoperatorenum{SceneConverterFlags} +@m_since_latest +*/ +MAGNUM_TRADE_EXPORT Debug& operator<<(Debug& debug, SceneConverterFlags value); + +/** +@brief Base for scene converter plugins +@m_since_latest + +Provides functionality for converting meshes and other scene data between +various formats or performing optimizations and other operations on them. See +@ref plugins for more information and `*SceneConverter` classes in the +@ref Trade namespace for available scene converter plugins. + +@section Trade-AbstractSceneConverter-data-dependency Data dependency + +The instances returned from various functions *by design* have no dependency on +the importer instance and neither on the dynamic plugin module. In other words, +you don't need to keep the importer instance (or the plugin manager instance) +around in order to have the `*Data` instances valid. Moreover, all +@ref Corrade::Containers::Array instances returned through @ref MeshData and +others are only allowed to have default deleters --- this is to avoid potential +dangling function pointer calls when destructing such instances after the +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. + +You don't need to do most of the redundant sanity checks, these things are +checked by the implementation: + +- The function @ref doConvert(const MeshData&) is called only if + @ref SceneConverterFeature::ConvertMesh is supported. +- The function @ref doConvertInPlace(MeshData&) is called only if + @ref SceneConverterFeature::ConvertMeshInPlace is supported. +- The function @ref doConvertToData(const MeshData&) is called only if + @ref SceneConverterFeature::ConvertMeshToData is supported. +- The function @ref doConvertToFile(const std::string&, const MeshData&) is + called only if @ref SceneConverterFeature::ConvertMeshToFile is supported. + +@m_class{m-block m-warning} + +@par Dangling function pointers on plugin unload + As @ref Trade-AbstractSceneConverter-data-dependency "mentioned above", + @ref Corrade::Containers::Array instances returned from plugin + implementations are not allowed to use anything else than the default + deleter or the deleter used by @ref Trade::ArrayAllocator, otherwise this + 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. +*/ +class MAGNUM_TRADE_EXPORT AbstractSceneConverter: public PluginManager::AbstractManagingPlugin { + public: + /** + * @brief Plugin interface + * + * @code{.cpp} + * "cz.mosra.magnum.Trade.AbstractSceneConverter/0.1" + * @endcode + */ + static std::string pluginInterface(); + + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + /** + * @brief Plugin search paths + * + * Looks into `magnum/sceneconverters/` or `magnum-d/sceneconverters/` + * next to the dynamic @ref Trade library, next to the executable and + * elsewhere according to the rules documented in + * @ref Corrade::PluginManager::implicitPluginSearchPaths(). The search + * directory can be also hardcoded using the `MAGNUM_PLUGINS_DIR` CMake + * variables, see @ref building for more information. + * + * Not defined on platforms without + * @ref CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT "dynamic plugin support". + */ + static std::vector pluginSearchPaths(); + #endif + + /** @brief Default constructor */ + explicit AbstractSceneConverter(); + + /** @brief Constructor with access to plugin manager */ + explicit AbstractSceneConverter(PluginManager::Manager& manager); + + /** @brief Plugin manager constructor */ + explicit AbstractSceneConverter(PluginManager::AbstractManager& manager, const std::string& plugin); + + /** @brief Features supported by this converter */ + SceneConverterFeatures features() const; + + /** @brief Converter flags */ + SceneConverterFlags flags() const { return _flags; } + + /** + * @brief Set converter flags + * + * Some flags can be set only if the converter supports particular + * features, see documentation of each @ref SceneConverterFlag for more + * information. By default no flags are set. + */ + void setFlags(SceneConverterFlags flags); + + /** + * @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. + * @see @ref features(), @ref convertInPlace(MeshData&) + */ + Containers::Optional convert(const MeshData& mesh); + + /** + * @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. Returns @cpp true @ce if the operation succeeded. On + * failure the function prints an error message and returns + * @cpp false @ce, @p mesh is guaranteed to stay unchanged. + * @see @ref features(), @ref convert(const MeshData&) + */ + bool convertInPlace(MeshData& mesh); + + /** + * @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 the function prints an error message and returns + * @cpp nullptr @ce. + * @see @ref features(), @ref convertToFile() + */ + Containers::Array convertToData(const MeshData& mesh); + + /** + * @brief Convert a mesh to a file + * + * Available only if @ref SceneConverterFeature::ConvertMeshToFile or + * @ref SceneConverterFeature::ConvertMeshToData is supported. Returns + * @cpp true @ce on success, prints an error message and returns + * @cpp false @ce otherwise. + * @see @ref features(), @ref convertToData() + */ + bool convertToFile(const std::string& filename, const MeshData& mesh); + + private: + /** + * @brief Implementation of @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 of @ref convert(const MeshData&) */ + virtual Containers::Optional doConvert(const MeshData& mesh); + + /** @brief Implementation of @ref convertInPlace(MeshData&) */ + virtual bool doConvertInPlace(MeshData& mesh); + + /** @brief Implementation of @ref convertToData(const MeshData&) */ + virtual Containers::Array doConvertToData(const MeshData& mesh); + + /** + * @brief Implementation of @ref convertToFile(const std::string&, const MeshData&) + * + * If @ref SceneConverterFeature::ConvertMeshToData is supported, + * default implementation calls @ref doConvertToData(const MeshData&) + * and saves the result to given file. + */ + virtual bool doConvertToFile(const std::string& filename, const MeshData& mesh); + + SceneConverterFlags _flags; +}; + +}} + +#endif diff --git a/src/Magnum/Trade/CMakeLists.txt b/src/Magnum/Trade/CMakeLists.txt index e41287968..27c4ac30c 100644 --- a/src/Magnum/Trade/CMakeLists.txt +++ b/src/Magnum/Trade/CMakeLists.txt @@ -38,6 +38,7 @@ set(MagnumTrade_SRCS set(MagnumTrade_GracefulAssert_SRCS AbstractImageConverter.cpp AbstractImporter.cpp + AbstractSceneConverter.cpp AnimationData.cpp CameraData.cpp ImageData.cpp @@ -50,6 +51,7 @@ set(MagnumTrade_HEADERS AbstractImporter.h AbstractImageConverter.h AbstractMaterialData.h + AbstractSceneConverter.h AnimationData.h ArrayAllocator.h CameraData.h diff --git a/src/Magnum/Trade/MeshData.h b/src/Magnum/Trade/MeshData.h index 858f92225..d3946bf28 100644 --- a/src/Magnum/Trade/MeshData.h +++ b/src/Magnum/Trade/MeshData.h @@ -1780,6 +1780,7 @@ class MAGNUM_TRADE_EXPORT MeshData { the restriction is pointless when used outside of plugin implementations. */ friend AbstractImporter; + friend AbstractSceneConverter; /* Internal helper that doesn't assert, unlike attributeId() */ UnsignedInt attributeFor(MeshAttribute name, UnsignedInt id) const; diff --git a/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp new file mode 100644 index 000000000..67953e489 --- /dev/null +++ b/src/Magnum/Trade/Test/AbstractSceneConverterTest.cpp @@ -0,0 +1,484 @@ +/* + This file is part of Magnum. + + Copyright © 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, 2019 + 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 +#include +#include +#include +#include +#include +#include + +#include "Magnum/Math/Vector3.h" +#include "Magnum/Trade/ArrayAllocator.h" +#include "Magnum/Trade/AbstractSceneConverter.h" +#include "Magnum/Trade/MeshData.h" + +#include "configure.h" + +namespace Magnum { namespace Trade { namespace Test { namespace { + +struct AbstractSceneConverterTest: TestSuite::Tester { + explicit AbstractSceneConverterTest(); + + void featuresNone(); + + void setFlags(); + void setFlagsNotImplemented(); + + void thingNotSupported(); + + void convertMesh(); + void convertMeshNotImplemented(); + void convertMeshNonOwningDeleters(); + void convertMeshGrowableDeleters(); + void convertMeshCustomIndexDataDeleter(); + void convertMeshCustomVertexDataDeleter(); + void convertMeshCustomAttributeDataDeleter(); + + void convertMeshInPlace(); + void convertMeshInPlaceNotImplemented(); + + void convertMeshToData(); + void convertMeshToDataNotImplemented(); + void convertMeshToDataCustomDeleter(); + + void convertMeshToFile(); + void convertMeshToFileThroughData(); + void convertMeshToFileThroughDataNotWritable(); + void convertMeshToFileNotImplemented(); + + void debugFeature(); + void debugFeatures(); + void debugFlag(); + void debugFlags(); +}; + +AbstractSceneConverterTest::AbstractSceneConverterTest() { + addTests({&AbstractSceneConverterTest::featuresNone, + + &AbstractSceneConverterTest::setFlags, + &AbstractSceneConverterTest::setFlagsNotImplemented, + + &AbstractSceneConverterTest::thingNotSupported, + + &AbstractSceneConverterTest::convertMesh, + &AbstractSceneConverterTest::convertMeshNotImplemented, + &AbstractSceneConverterTest::convertMeshNonOwningDeleters, + &AbstractSceneConverterTest::convertMeshGrowableDeleters, + &AbstractSceneConverterTest::convertMeshCustomIndexDataDeleter, + &AbstractSceneConverterTest::convertMeshCustomVertexDataDeleter, + &AbstractSceneConverterTest::convertMeshCustomAttributeDataDeleter, + + &AbstractSceneConverterTest::convertMeshInPlace, + &AbstractSceneConverterTest::convertMeshInPlaceNotImplemented, + + &AbstractSceneConverterTest::convertMeshToData, + &AbstractSceneConverterTest::convertMeshToDataNotImplemented, + &AbstractSceneConverterTest::convertMeshToDataCustomDeleter, + + &AbstractSceneConverterTest::convertMeshToFile, + &AbstractSceneConverterTest::convertMeshToFileThroughData, + &AbstractSceneConverterTest::convertMeshToFileThroughDataNotWritable, + &AbstractSceneConverterTest::convertMeshToFileNotImplemented, + + &AbstractSceneConverterTest::debugFeature, + &AbstractSceneConverterTest::debugFeatures, + &AbstractSceneConverterTest::debugFlag, + &AbstractSceneConverterTest::debugFlags}); + + /* Create testing dir */ + Utility::Directory::mkpath(TRADE_TEST_OUTPUT_DIR); +} + +void AbstractSceneConverterTest::featuresNone() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return {}; } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.features(); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::features(): implementation reported no features\n"); +} + +void AbstractSceneConverterTest::setFlags() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + /* Assuming this bit is unused */ + return SceneConverterFeature(1 << 7); + } + void doSetFlags(SceneConverterFlags flags) override { + _flags = flags; + } + + SceneConverterFlags _flags; + } converter; + + CORRADE_COMPARE(converter.flags(), SceneConverterFlags{}); + CORRADE_COMPARE(converter._flags, SceneConverterFlags{}); + converter.setFlags(SceneConverterFlag::Verbose); + CORRADE_COMPARE(converter.flags(), SceneConverterFlag::Verbose); + CORRADE_COMPARE(converter._flags, SceneConverterFlag::Verbose); +} + +void AbstractSceneConverterTest::setFlagsNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + /* Assuming this bit is unused */ + return SceneConverterFeature(1 << 7); + } + } converter; + + CORRADE_COMPARE(converter.flags(), SceneConverterFlags{}); + converter.setFlags(SceneConverterFlag::Verbose); + CORRADE_COMPARE(converter.flags(), SceneConverterFlag::Verbose); + /* Should just work, no need to implement the function */ +} + +void AbstractSceneConverterTest::thingNotSupported() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { + /* Assuming this bit is unused */ + return SceneConverterFeature(1 << 7); + } + } converter; + + MeshData mesh{MeshPrimitive::Triangles, 3}; + + std::ostringstream out; + Error redirectError{&out}; + converter.convert(mesh); + converter.convertInPlace(mesh); + converter.convertToData(mesh); + converter.convertToFile(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"), mesh); + 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"); +} + +void AbstractSceneConverterTest::convertMesh() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; } + + Containers::Optional doConvert(const MeshData& mesh) override { + if(mesh.primitive() == MeshPrimitive::Triangles) + return MeshData{MeshPrimitive::Lines, mesh.vertexCount()*2}; + return {}; + } + } converter; + + Containers::Optional out = converter.convert(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_VERIFY(out); + CORRADE_COMPARE(out->primitive(), MeshPrimitive::Lines); + CORRADE_COMPARE(out->vertexCount(), 12); +} + +void AbstractSceneConverterTest::convertMeshNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.convert(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convert(): mesh conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::convertMeshNonOwningDeleters() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; } + + Containers::Optional doConvert(const MeshData&) override { + return MeshData{MeshPrimitive::Triangles, + Containers::Array{indexData, 1, Implementation::nonOwnedArrayDeleter}, MeshIndexData{MeshIndexType::UnsignedByte, indexData}, + Containers::Array{nullptr, 0, Implementation::nonOwnedArrayDeleter}, + meshAttributeDataNonOwningArray(attributes)}; + } + + char indexData[1]; + MeshAttributeData attributes[1]{ + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr} + }; + } converter; + + Containers::Optional out = converter.convert(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_VERIFY(out); + CORRADE_COMPARE(static_cast(out->indexData()), converter.indexData); +} + +void AbstractSceneConverterTest::convertMeshGrowableDeleters() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; } + + Containers::Optional doConvert(const MeshData&) override { + Containers::Array indexData; + Containers::arrayAppend(indexData, '\xab'); + Containers::Array vertexData; + Containers::arrayAppend(vertexData, Vector3{}); + MeshIndexData indices{MeshIndexType::UnsignedByte, indexData}; + MeshAttributeData positions{MeshAttribute::Position, Containers::arrayView(vertexData)}; + + return MeshData{MeshPrimitive::Triangles, + std::move(indexData), indices, + Containers::arrayAllocatorCast(std::move(vertexData)), {positions}}; + } + + char indexData[1]; + MeshAttributeData attributes[1]{ + MeshAttributeData{MeshAttribute::Position, VertexFormat::Vector3, nullptr} + }; + } converter; + + Containers::Optional out = converter.convert(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_VERIFY(out); + CORRADE_COMPARE(out->indexData()[0], '\xab'); + CORRADE_COMPARE(out->vertexData().size(), 12); +} + +void AbstractSceneConverterTest::convertMeshCustomIndexDataDeleter() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; } + + Containers::Optional doConvert(const MeshData&) override { + return MeshData{MeshPrimitive::Triangles, Containers::Array{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}, 1}; + } + + char data[1]; + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.convert(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::convert(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractSceneConverterTest::convertMeshCustomVertexDataDeleter() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; } + + Containers::Optional doConvert(const MeshData&) override { + return MeshData{MeshPrimitive::Triangles, Containers::Array{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}, 1}; + } + + char data[1]; + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.convert(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::convert(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractSceneConverterTest::convertMeshCustomAttributeDataDeleter() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMesh; } + + Containers::Optional doConvert(const MeshData&) override { + return MeshData{MeshPrimitive::Triangles, Containers::Array{data, 1, [](char*, std::size_t) {}}, MeshIndexData{MeshIndexType::UnsignedByte, data}, 1}; + } + + char data[1]; + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.convert(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_COMPARE(out.str(), + "Trade::AbstractSceneConverter::convert(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractSceneConverterTest::convertMeshInPlace() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshInPlace; } + + bool doConvertInPlace(MeshData& mesh) override { + auto indices = mesh.mutableIndices(); + for(std::size_t i = 0; i != indices.size()/2; ++i) + std::swap(indices[i], indices[indices.size() - i -1]); + return true; + } + } converter; + + UnsignedInt indices[]{1, 2, 3, 4, 2, 0}; + MeshData mesh{MeshPrimitive::Triangles, + DataFlag::Mutable, indices, MeshIndexData{indices}, 5}; + CORRADE_VERIFY(converter.convertInPlace(mesh)); + CORRADE_COMPARE_AS(mesh.indices(), + Containers::arrayView({0, 2, 4, 3, 2, 1}), + TestSuite::Compare::Container); +} + +void AbstractSceneConverterTest::convertMeshInPlaceNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshInPlace; } + } converter; + + MeshData mesh{MeshPrimitive::Triangles, 3}; + + std::ostringstream out; + Error redirectError{&out}; + converter.convertInPlace(mesh); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convertInPlace(): mesh conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::convertMeshToData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToData; } + + Containers::Array doConvertToData(const MeshData& mesh) override { + return Containers::Array{nullptr, mesh.vertexCount()}; + } + } converter; + + Containers::Array data = converter.convertToData(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_COMPARE(data.size(), 6); +} + +void AbstractSceneConverterTest::convertMeshToDataNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToData; } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.convertToData(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convertToData(): mesh conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::convertMeshToDataCustomDeleter() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToData; } + + Containers::Array doConvertToData(const MeshData&) override { + return Containers::Array{data, 1, [](char*, std::size_t) {}}; + } + + char data[1]; + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.convertToData(MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convertToData(): implementation is not allowed to use a custom Array deleter\n"); +} + +void AbstractSceneConverterTest::convertMeshToFile() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToFile; } + + bool doConvertToFile(const std::string& filename, const MeshData& mesh) override { + return Utility::Directory::write(filename, Containers::arrayView( {char(mesh.vertexCount())})); + } + } converter; + + /* Remove previous file */ + Utility::Directory::rm(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out")); + + CORRADE_VERIFY(converter.convertToFile(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"), MeshData{MeshPrimitive::Triangles, 0xef})); + CORRADE_COMPARE_AS(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"), + "\xef", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::convertMeshToFileThroughData() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToData; } + + Containers::Array doConvertToData(const Magnum::Trade::MeshData & mesh) override { + return Containers::array({char(mesh.vertexCount())}); + } + } converter; + + /* Remove previous file */ + Utility::Directory::rm(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out")); + + CORRADE_VERIFY(converter.convertToFile(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"), MeshData{MeshPrimitive::Triangles, 0xef})); + CORRADE_COMPARE_AS(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"), + "\xef", TestSuite::Compare::FileToString); +} + +void AbstractSceneConverterTest::convertMeshToFileThroughDataNotWritable() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToData; } + + Containers::Array doConvertToData(const Magnum::Trade::MeshData & mesh) override { + return Containers::array({char(mesh.vertexCount())}); + } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + CORRADE_VERIFY(!converter.convertToFile("/some/path/that/does/not/exist", MeshData{MeshPrimitive::Triangles, 0xef})); + CORRADE_COMPARE(out.str(), + "Utility::Directory::write(): can't open /some/path/that/does/not/exist\n" + "Trade::AbstractSceneConverter::convertToFile(): cannot write to file /some/path/that/does/not/exist\n"); +} + +void AbstractSceneConverterTest::convertMeshToFileNotImplemented() { + struct: AbstractSceneConverter { + SceneConverterFeatures doFeatures() const override { return SceneConverterFeature::ConvertMeshToFile; } + } converter; + + std::ostringstream out; + Error redirectError{&out}; + converter.convertToFile(Utility::Directory::join(TRADE_TEST_OUTPUT_DIR, "mesh.out"), MeshData{MeshPrimitive::Triangles, 6}); + CORRADE_COMPARE(out.str(), "Trade::AbstractSceneConverter::convertToFile(): mesh conversion advertised but not implemented\n"); +} + +void AbstractSceneConverterTest::debugFeature() { + std::ostringstream out; + + Debug{&out} << SceneConverterFeature::ConvertMeshInPlace << SceneConverterFeature(0xf0); + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFeature::ConvertMeshInPlace Trade::SceneConverterFeature(0xf0)\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::debugFlag() { + std::ostringstream out; + + Debug{&out} << SceneConverterFlag::Verbose << SceneConverterFlag(0xf0); + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFlag::Verbose Trade::SceneConverterFlag(0xf0)\n"); +} + +void AbstractSceneConverterTest::debugFlags() { + std::ostringstream out; + + Debug{&out} << (SceneConverterFlag::Verbose|SceneConverterFlag(0xf0)) << SceneConverterFlags{}; + CORRADE_COMPARE(out.str(), "Trade::SceneConverterFlag::Verbose|Trade::SceneConverterFlag(0xf0) Trade::SceneConverterFlags{}\n"); +} + +}}}} + +CORRADE_TEST_MAIN(Magnum::Trade::Test::AbstractSceneConverterTest) diff --git a/src/Magnum/Trade/Test/CMakeLists.txt b/src/Magnum/Trade/Test/CMakeLists.txt index f89f44e45..244b018be 100644 --- a/src/Magnum/Trade/Test/CMakeLists.txt +++ b/src/Magnum/Trade/Test/CMakeLists.txt @@ -42,6 +42,10 @@ corrade_add_test(TradeAbstractImporterTest AbstractImporterTest.cpp FILES file.bin) target_include_directories(TradeAbstractImporterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) +corrade_add_test(TradeAbstractSceneConverterTest AbstractSceneConverterTest.cpp + LIBRARIES MagnumTradeTestLib) +target_include_directories(TradeAbstractSceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + corrade_add_test(TradeAnimationDataTest AnimationDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeCameraDataTest CameraDataTest.cpp LIBRARIES MagnumTradeTestLib) corrade_add_test(TradeDataTest DataTest.cpp LIBRARIES MagnumTrade) @@ -62,6 +66,7 @@ set_property(TARGET set_target_properties( TradeAbstractImageConverterTest TradeAbstractImporterTest + TradeAbstractSceneConverterTest TradeAnimationDataTest TradeCameraDataTest TradeImageDataTest diff --git a/src/Magnum/Trade/Trade.h b/src/Magnum/Trade/Trade.h index d4df385bd..e146d5ef1 100644 --- a/src/Magnum/Trade/Trade.h +++ b/src/Magnum/Trade/Trade.h @@ -42,6 +42,7 @@ namespace Magnum { namespace Trade { #ifndef DOXYGEN_GENERATING_OUTPUT class AbstractImageConverter; class AbstractImporter; +class AbstractSceneConverter; #ifdef MAGNUM_BUILD_DEPRECATED typedef CORRADE_DEPRECATED("use InputFileCallbackPolicy instead") InputFileCallbackPolicy ImporterFileCallbackPolicy; diff --git a/src/Magnum/Trade/configure.h.cmake b/src/Magnum/Trade/configure.h.cmake index 024c1178f..c969a20cb 100644 --- a/src/Magnum/Trade/configure.h.cmake +++ b/src/Magnum/Trade/configure.h.cmake @@ -27,3 +27,5 @@ #define MAGNUM_PLUGINS_IMPORTER_DEBUG_DIR "${MAGNUM_PLUGINS_IMPORTER_DEBUG_DIR}" #define MAGNUM_PLUGINS_IMAGECONVERTER_DIR "${MAGNUM_PLUGINS_IMAGECONVERTER_DIR}" #define MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_DIR "${MAGNUM_PLUGINS_IMAGECONVERTER_DEBUG_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_DIR "${MAGNUM_PLUGINS_SCENECONVERTER_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_DIR "${MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_DIR}"