diff --git a/.editorconfig b/.editorconfig index 4c86f3ff1..d0764f768 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,6 +5,6 @@ indent_size = 4 trim_trailing_whitespace = true insert_final_newline = true -[*.{css,html,yml,rb}] +[*.{css,html,yml,rb,gltf}] indent_style = space indent_size = 2 diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index 0ea8253b3..c1d7b5f60 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -29,12 +29,23 @@ set(CMAKE_FOLDER "Magnum/SceneTools/Test") if(CORRADE_TARGET_EMSCRIPTEN OR CORRADE_TARGET_ANDROID) set(SCENETOOLS_TEST_DIR ".") + set(SCENETOOLS_TEST_OUTPUT_DIR "write") else() set(SCENETOOLS_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}) + set(SCENETOOLS_TEST_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) endif() +# Executable testing is implemented on Unix platforms only at the moment, so +# don't even provide the filename elsewhere. +if(MAGNUM_WITH_SCENECONVERTER AND CORRADE_TARGET_UNIX) + set(SCENECONVERTER_EXECUTABLE_FILENAME $) +endif() + +# First replace ${} variables, then $<> generator expressions configure_file(${CMAKE_CURRENT_SOURCE_DIR}/configure.h.cmake - ${CMAKE_CURRENT_BINARY_DIR}/configure.h) + ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) +file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/$/configure.h + INPUT ${CMAKE_CURRENT_BINARY_DIR}/configure.h.in) corrade_add_test(SceneToolsCombineTest CombineTest.cpp LIBRARIES MagnumTrade) corrade_add_test(SceneToolsConvertToSingleFun___Test ConvertToSingleFunctionObjectsTest.cpp LIBRARIES MagnumTrade) @@ -44,6 +55,9 @@ corrade_add_test(SceneToolsOrderClusterParentsTest OrderClusterParentsTest.cpp L corrade_add_test(SceneToolsSceneConverterTest SceneConverterTest.cpp LIBRARIES MagnumSceneTools FILES + SceneConverterTestFiles/broken-mesh.obj + SceneConverterTestFiles/broken-scene.gltf + SceneConverterTestFiles/empty.gltf SceneConverterTestFiles/info-animations.txt SceneConverterTestFiles/info-cameras.txt SceneConverterTestFiles/info-images.txt @@ -56,5 +70,18 @@ corrade_add_test(SceneToolsSceneConverterTest SceneConverterTest.cpp SceneConverterTestFiles/info-scenes-objects.txt SceneConverterTestFiles/info-scenes.txt SceneConverterTestFiles/info-skins.txt - SceneConverterTestFiles/info-textures.txt) -target_include_directories(SceneToolsSceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) + SceneConverterTestFiles/info-textures.txt + SceneConverterTestFiles/point.obj + SceneConverterTestFiles/quad-duplicates-fuzzy.obj + SceneConverterTestFiles/quad-duplicates.obj + SceneConverterTestFiles/quad-duplicates.ply + SceneConverterTestFiles/quad-normals-texcoords.obj + SceneConverterTestFiles/quad.obj + SceneConverterTestFiles/quad.ply + SceneConverterTestFiles/two-triangles-transformed.bin + SceneConverterTestFiles/two-triangles-transformed.gltf + SceneConverterTestFiles/two-triangles.obj) +target_include_directories(SceneToolsSceneConverterTest PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/$) +if(MAGNUM_WITH_SCENECONVERTER AND CORRADE_TARGET_UNIX) + add_dependencies(SceneToolsSceneConverterTest magnum-sceneconverter) +endif() diff --git a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp index abf4589e0..05d528d67 100644 --- a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp +++ b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include #include #include #include @@ -34,6 +36,7 @@ #include "Magnum/Math/CubicHermite.h" #include "Magnum/Math/Matrix3.h" #include "Magnum/Math/Matrix4.h" +#include "Magnum/Trade/AbstractSceneConverter.h" #include "Magnum/SceneTools/Implementation/sceneConverterUtilities.h" @@ -59,9 +62,16 @@ struct SceneConverterTest: TestSuite::Tester { void infoImplementationReferenceCount(); void infoImplementationError(); + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + void info(); + void convert(); + void error(); + #endif + Utility::Arguments _infoArgs; }; +using namespace Containers::Literals; using namespace Math::Literals; const struct { @@ -84,6 +94,299 @@ const struct { {"--info", false, false}, }; +#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT +const struct { + const char* name; + Containers::Array args; + const char* expected; +} InfoData[]{ + {"", Containers::array({}), + "info.txt"}, + {"map", Containers::array({ + "--map"}), + /** @todo change to something else once we have a plugin that can + zero-copy pass the imported data */ + "info.txt"}, + {"ignored output file", Containers::array({ + "whatever.ply"}), + "info-ignored-output.txt"}, +}; + +const struct { + const char* name; + Containers::Array args; + const char* requiresImporter; + const char* requiresConverter; + const char* expected; + Containers::String message; +} ConvertData[]{ + {"one mesh", Containers::array({ + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, explicit importer and converter", Containers::array({ + "-I", "ObjImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, map", Containers::array({ + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, options", Containers::array({ + /* It's silly, but since we have option propagation tested in + AnySceneImporter / AnySceneConverter .cpp already, it's enough to + just verify the (nonexistent) options arrive there */ + "-i", "nonexistentOption=13", "-c", "nonexistentConverterOption=26", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Trade::AnySceneImporter::openFile(): option nonexistentOption not recognized by ObjImporter\n" + "Trade::AnySceneConverter::convertToFile(): option nonexistentConverterOption not recognized by StanfordSceneConverter\n"}, + {"one mesh, options, explicit importer and converter", Containers::array({ + /* Same here, since we have option propagation tested in + Magnum/Test/ConverterUtilitiesTest.cpp already, to verify it's + getting called we can just supply nonexistent options */ + "-i", "nonexistentOption=13", "-c", "nonexistentConverterOption=26", + "-I", "ObjImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentOption not recognized by ObjImporter\n" + "Option nonexistentConverterOption not recognized by StanfordSceneConverter\n"}, + {"concatenate meshes without a scene", Containers::array({ + "--concatenate-meshes", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/two-triangles.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad-duplicates.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad-duplicates.ply", + {}}, + {"concatenate meshes with a scene", Containers::array({ + "--concatenate-meshes", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/two-triangles-transformed.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad-duplicates.ply")}), + "GltfImporter", "StanfordSceneConverter", + "quad-duplicates.ply", + {}}, + {"filter attributes", Containers::array({ + /* Only 0 gets picked from here, others ignored */ + "--only-attributes", "17,0,25-36", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-normals-texcoords.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"remove duplicates", Containers::array({ + "--remove-duplicates", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"remove duplicates, verbose", Containers::array({ + /* Forcing the importer and converter to avoid AnySceneImporter / + AnySceneConverter delegation messages */ + "--remove-duplicates", "-v", "-I", "ObjImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Duplicate removal: 6 -> 4 vertices\n"}, + {"remove duplicates fuzzy", Containers::array({ + "--remove-duplicates-fuzzy 1.0e-1", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates-fuzzy.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"remove duplicates fuzzy, verbose", Containers::array({ + /* Forcing the importer and converter to avoid AnySceneImporter / + AnySceneConverter delegation messages */ + "--remove-duplicates-fuzzy 1.0e-1", "-v", "-I", "ObjImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates-fuzzy.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Fuzzy duplicate removal: 6 -> 4 vertices\n"}, + {"one mesh, two converters", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, two converters, explicit last", Containers::array({ + "-C", "MeshOptimizerSceneConverter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + {}}, + {"one mesh, two converters, verbose", Containers::array({ + "-C", "MeshOptimizerSceneConverter", "-v", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + /** @todo this is a no-op, use some other converter that tests also + that the resulting mesh is actually passed further */ + "Trade::AnySceneImporter::openFile(): using ObjImporter\n" + "Trade::MeshOptimizerSceneConverter::convert(): processing stats:\n" + " vertex cache:\n" + " 4 -> 4 transformed vertices\n" + " 1 -> 1 executed warps\n" + " ACMR 2 -> 2\n" + " ATVR 1 -> 1\n" + " vertex fetch:\n" + " 64 -> 64 bytes fetched\n" + " overfetch 1.33333 -> 1.33333\n" + " overdraw:\n" + " 65536 -> 65536 shaded pixels\n" + " 65536 -> 65536 covered pixels\n" + " overdraw 1 -> 1\n" + "Trade::AnySceneConverter::convertToFile(): using StanfordSceneConverter\n"}, + {"one mesh, two converters, explicit last, verbose", Containers::array({ + "-C", "MeshOptimizerSceneConverter", "-C", "StanfordSceneConverter", "-v", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + /* As the importers and converters are specified explicitly, there's + no messages from AnySceneConverter, OTOH as we have more than one -C + option the verbose output includes a progress info */ + "Trade::AnySceneImporter::openFile(): using ObjImporter\n" + "Processing (1/2) with MeshOptimizerSceneConverter...\n" + "Trade::MeshOptimizerSceneConverter::convert(): processing stats:\n" + " vertex cache:\n" + " 4 -> 4 transformed vertices\n" + " 1 -> 1 executed warps\n" + " ACMR 2 -> 2\n" + " ATVR 1 -> 1\n" + " vertex fetch:\n" + " 64 -> 64 bytes fetched\n" + " overfetch 1.33333 -> 1.33333\n" + " overdraw:\n" + " 65536 -> 65536 shaded pixels\n" + " 65536 -> 65536 covered pixels\n" + " overdraw 1 -> 1\n" + "Saving output (2/2) with StanfordSceneConverter...\n"}, + {"one mesh, two converters, options for the first only", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + "-c", "nonexistentMeshOptimizerOption=yes", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n"}, + {"one mesh, two converters, explicit last, options for the first only", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + "-c", "nonexistentMeshOptimizerOption=yes", + "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n"}, + {"one mesh, two converters, options for both", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + "-c", "nonexistentMeshOptimizerOption=yes", + "-c", "nonexistentAnyConverterOption=no", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n" + "Trade::AnySceneConverter::convertToFile(): option nonexistentAnyConverterOption not recognized by StanfordSceneConverter\n"}, + {"one mesh, two converters, explicit last, options for both", Containers::array({ + "-C", "MeshOptimizerSceneConverter", + "-c", "nonexistentMeshOptimizerOption=yes", + "-C", "StanfordSceneConverter", + "-c", "nonexistentStanfordConverterOption=no", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "ObjImporter", "StanfordSceneConverter", + "quad.ply", + "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n" + "Option nonexistentStanfordConverterOption not recognized by StanfordSceneConverter\n"}, +}; + +const struct { + const char* name; + Containers::Array args; + const char* requiresImporter; + const char* requiresConverter; + Containers::String message; +} ErrorData[]{ + {"missing output argument", Containers::array({ + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj")}), + nullptr, nullptr, + /* The output should be optional only for --info, required otherwise. + No need to test anything else as that's handled by Utility::Arguments + already. Testing just a prefix of the message. */ + "Missing command-line argument output\nUsage:\n "}, + {"can't load importer plugin", Containers::array({ + /* Override also the plugin directory for consistent output */ + "--plugin-dir", "nonexistent", "-I", "NonexistentImporter", "whatever.obj", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + nullptr, nullptr, + "PluginManager::Manager::load(): plugin NonexistentImporter is not static and was not found in nonexistent/importers\n" + "Available importer plugins: "}, + {"can't open a file", Containers::array({ + "noexistent.ffs", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "AnySceneImporter", nullptr, + "Trade::AnySceneImporter::openFile(): cannot determine the format of noexistent.ffs\n" + "Cannot open file noexistent.ffs\n"}, + {"can't map a file", Containers::array({ + "noexistent.ffs", "--map", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "AnySceneImporter", nullptr, + "Utility::Path::mapRead(): can't open noexistent.ffs: error 2 (No such file or directory)\n" + "Cannot memory-map file noexistent.ffs\n"}, + {"no meshes found", Containers::array({ + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/empty.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "GltfImporter", nullptr, + Utility::format("No meshes found in {}\n", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/empty.gltf"))}, + {"can't import a mesh", Containers::array({ + "-I", "ObjImporter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/broken-mesh.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", nullptr, + "Trade::ObjImporter::mesh(): wrong index count for point\n" + "Cannot import the mesh\n"}, + {"can't import a mesh for concatenation", Containers::array({ + "-I", "ObjImporter", "--concatenate-meshes", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/broken-mesh.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", nullptr, + "Trade::ObjImporter::mesh(): wrong index count for point\n" + "Cannot import mesh 0\n"}, + {"can't import a scene for concatenation", Containers::array({ + /** @todo change to an OBJ once ObjImporter imports materials (and thus + scenes) */ + "--concatenate-meshes", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/broken-scene.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "GltfImporter", nullptr, + "Trade::GltfImporter::scene(): mesh index 1 in node 0 out of range for 1 meshes\n" + "Cannot import scene 0 for mesh concatenation\n"}, + {"invalid attribute filter", Containers::array({ + "-I", "ObjImporter", "--only-attributes", "LOLNEIN", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", nullptr, + "Utility::parseNumberSequence(): unrecognized character L in LOLNEIN\n"}, + {"can't load converter plugin", Containers::array({ + /* Override also the plugin directory for consistent output, however + then the importer plugin has to be loaded through an absolute file + path (unless using static plugins) */ + "--plugin-dir", "nonexistent", "-I", + #ifndef MAGNUM_BUILD_STATIC + Utility::Path::join(MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR, "ObjImporter" + Trade::AbstractImporter::pluginSuffix()), + #else + "ObjImporter", + #endif + "-C", "NonexistentSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", nullptr, + /* Just a prefix */ + "PluginManager::Manager::load(): plugin NonexistentSceneConverter is not static and was not found in nonexistent/sceneconverters\n" + "Available converter plugins: "}, + {"file coversion failed", Containers::array({ + "-I", "ObjImporter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.fbx")}), + "ObjImporter", "AnySceneConverter", + Utility::format("Trade::AnySceneConverter::convertToFile(): cannot determine the format of {0}\n" + "Cannot save file {0}\n", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.fbx"))}, + {"mesh coversion failed", Containers::array({ + "-I", "ObjImporter", "-C", "MeshOptimizerSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", "MeshOptimizerSceneConverter", + "Trade::MeshOptimizerSceneConverter::convert(): expected a triangle mesh, got MeshPrimitive::Points\n" + "MeshOptimizerSceneConverter cannot convert the mesh\n"}, + {"plugin doesn't support mesh conversion", Containers::array({ + /* Pass the same plugin twice, which means the first instance should + get used for a mesh-to-mesh conversion */ + "-I", "ObjImporter", "-C", "StanfordSceneConverter", "-C", "StanfordSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", "StanfordSceneConverter", + "StanfordSceneConverter doesn't support mesh conversion, only Trade::SceneConverterFeature::ConvertMeshToData\n"}, +}; +#endif + SceneConverterTest::SceneConverterTest() { addTests({&SceneConverterTest::infoImplementationEmpty}); @@ -107,6 +410,17 @@ SceneConverterTest::SceneConverterTest() { addTests({&SceneConverterTest::infoImplementationReferenceCount, &SceneConverterTest::infoImplementationError}); + #ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT + addInstancedTests({&SceneConverterTest::info}, + Containers::arraySize(InfoData)); + + addInstancedTests({&SceneConverterTest::convert}, + Containers::arraySize(ConvertData)); + + addInstancedTests({&SceneConverterTest::error}, + Containers::arraySize(ErrorData)); + #endif + /* A subset of arguments needed by the info printing code */ _infoArgs.addBooleanOption("info") .addBooleanOption("info-scenes") @@ -120,6 +434,9 @@ SceneConverterTest::SceneConverterTest() { .addBooleanOption("info-textures") .addBooleanOption("info-images") .addBooleanOption("bounds"); + + /* Create output dir, if doesn't already exist */ + Utility::Path::make(Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles")); } void SceneConverterTest::infoImplementationEmpty() { @@ -1224,6 +1541,146 @@ void SceneConverterTest::infoImplementationError() { "Object 0: A name\n"); } +#ifndef CORRADE_PLUGINMANAGER_NO_DYNAMIC_PLUGIN_SUPPORT +namespace { + +#ifdef SCENECONVERTER_EXECUTABLE_FILENAME +/** @todo take a StringIterable once it exists */ +Containers::Pair call(Containers::ArrayView arguments) { + /* Create a string view array for the arguments, implicitly pass the + application name and plugin directory override */ + /** @todo drop once StringIterable exists */ + Containers::Array argumentViews{ValueInit, arguments.size() + 3}; + argumentViews[0] = ""_s; + argumentViews[1] = "--plugin-dir"_s; + argumentViews[2] = MAGNUM_PLUGINS_INSTALL_DIR; + for(std::size_t i = 0; i != arguments.size(); ++i) + argumentViews[i + 3] = arguments[i]; + + const Containers::String outputFilename = Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/output.txt"); + /** @todo clean up once Utility::System::execute() with output redirection + exists */ + const bool success = std::system(Utility::format("{} {} > {} 2>&1", + SCENECONVERTER_EXECUTABLE_FILENAME, + " "_s.join(argumentViews), /** @todo handle space escaping here? */ + outputFilename + ).data()) == 0; + + const Containers::Optional output = Utility::Path::readString(outputFilename); + CORRADE_VERIFY(output); + + return {success, std::move(*output)}; +} +#endif + +} + +void SceneConverterTest::info() { + auto&& data = InfoData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef SCENECONVERTER_EXECUTABLE_FILENAME + #ifdef CORRADE_TARGET_UNIX + CORRADE_SKIP("magnum-sceneconverter not built, can't test"); + #else + CORRADE_SKIP("Executable testing implemented only on Unix platforms"); + #endif + #else + /* Check if required plugins can be loaded. Catches also ABI and interface + mismatch errors. */ + PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + if(!(importerManager.load("ObjImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("ObjImporter plugin can't be loaded."); + + Containers::Array args{InPlaceInit, + {"-I", "ObjImporter", "--info", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj")}}; + arrayAppend(args, arrayView(data.args)); /** @todo FFS fix the casts */ + + CORRADE_VERIFY(true); /* capture correct function name */ + + Containers::Pair output = call(args); + CORRADE_COMPARE_AS(output.second(), + Utility::Path::join({SCENETOOLS_TEST_DIR, "SceneConverterTestFiles", data.expected}), + TestSuite::Compare::StringToFile); + CORRADE_VERIFY(output.first()); + #endif +} + +void SceneConverterTest::convert() { + auto&& data = ConvertData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef SCENECONVERTER_EXECUTABLE_FILENAME + #ifdef CORRADE_TARGET_UNIX + CORRADE_SKIP("magnum-sceneconverter not built, can't test"); + #else + CORRADE_SKIP("Executable testing implemented only on Unix platforms"); + #endif + #else + /* Check if required plugins can be loaded. Catches also ABI and interface + mismatch errors. */ + PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + PluginManager::Manager converterManager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + if(data.requiresImporter && !(importerManager.load(data.requiresImporter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresImporter << "plugin can't be loaded."); + if(data.requiresConverter && !(converterManager.load(data.requiresConverter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresConverter << "plugin can't be loaded."); + /* AnySceneImporter & AnySceneConverter are required implicitly for + simplicity */ + if(!(importerManager.load("AnySceneImporter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnySceneImporter plugin can't be loaded."); + if(!(converterManager.load("AnySceneConverter") & PluginManager::LoadState::Loaded)) + CORRADE_SKIP("AnySceneConverter plugin can't be loaded."); + + CORRADE_VERIFY(true); /* capture correct function name */ + + Containers::Pair output = call(data.args); + CORRADE_COMPARE(output.second(), data.message); + CORRADE_VERIFY(output.first()); + + CORRADE_COMPARE_AS(Utility::Path::join({SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles", data.expected}), + Utility::Path::join({SCENETOOLS_TEST_DIR, "SceneConverterTestFiles", data.expected}), + TestSuite::Compare::File); + #endif +} + +void SceneConverterTest::error() { + auto&& data = ErrorData[testCaseInstanceId()]; + setTestCaseDescription(data.name); + + #ifndef SCENECONVERTER_EXECUTABLE_FILENAME + #ifdef CORRADE_TARGET_UNIX + CORRADE_SKIP("magnum-sceneconverter not built, can't test"); + #else + CORRADE_SKIP("Executable testing implemented only on Unix platforms"); + #endif + #else + /* Check if required plugins can be loaded. Catches also ABI and interface + mismatch errors. */ + PluginManager::Manager importerManager{MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR}; + PluginManager::Manager converterManager{MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR}; + if(data.requiresImporter && !(importerManager.load(data.requiresImporter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresImporter << "plugin can't be loaded."); + if(data.requiresConverter && !(converterManager.load(data.requiresConverter) & PluginManager::LoadState::Loaded)) + CORRADE_SKIP(data.requiresConverter << "plugin can't be loaded."); + + CORRADE_VERIFY(true); /* capture correct function name */ + + Containers::Pair output = call(data.args); + /* If the message ends with a \n, assume it's the whole message. Otherwise + it's just a prefix. */ + if(data.message.hasSuffix('\n')) + CORRADE_COMPARE(output.second(), data.message); + else + CORRADE_COMPARE_AS(output.second(), + data.message, + TestSuite::Compare::StringHasPrefix); + /* It should return a non-zero code */ + CORRADE_VERIFY(!output.first()); + #endif +} +#endif + }}}} CORRADE_TEST_MAIN(Magnum::SceneTools::Test::SceneConverterTest) diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-mesh.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-mesh.obj new file mode 100644 index 000000000..0d41da794 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-mesh.obj @@ -0,0 +1,3 @@ +# A point with two indices +v 1 2 3 +p 5 5 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-scene.gltf b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-scene.gltf new file mode 100644 index 000000000..c2e5f1095 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/broken-scene.gltf @@ -0,0 +1,22 @@ +{ + "asset": { + "version": "2.0" + }, + "scenes": [ + { + "nodes": [0] + } + ], + "meshes": [ + { + "primitives": [ + {} + ] + } + ], + "nodes": [ + { + "mesh": 1 + } + ] +} diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/empty.gltf b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/empty.gltf new file mode 100644 index 000000000..8b778ccfb --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/empty.gltf @@ -0,0 +1,5 @@ +{ + "asset": { + "version": "2.0" + } +} diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-ignored-output.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-ignored-output.txt new file mode 100644 index 000000000..ed3609a7e --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info-ignored-output.txt @@ -0,0 +1,6 @@ +Ignoring output file for --info: whatever.ply +Mesh 0: + Level 0: 1 vertices @ Points (0.0 kB) + Position @ Vector3, offset 0, stride 12 + 1 indices @ UnsignedInt, offset 0, stride 4 (0.0 kB) +Total mesh data size: 0.0 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info.txt b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info.txt new file mode 100644 index 000000000..1f10cb3cc --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/info.txt @@ -0,0 +1,5 @@ +Mesh 0: + Level 0: 1 vertices @ Points (0.0 kB) + Position @ Vector3, offset 0, stride 12 + 1 indices @ UnsignedInt, offset 0, stride 4 (0.0 kB) +Total mesh data size: 0.0 kB diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/point.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/point.obj new file mode 100644 index 000000000..d90424cae --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/point.obj @@ -0,0 +1,2 @@ +v 1 2 3 +p 1 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates-fuzzy.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates-fuzzy.obj new file mode 100644 index 000000000..f3f2acd4c --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates-fuzzy.obj @@ -0,0 +1,12 @@ +# 1 4--6 +# |\ \ | +# | \ \| +# 2--3 5 +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +f 1 2 3 +v -0.9 0.9 0 +v 0.9 -0.9 0 +v 1 1 0 +f 4 5 6 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.obj new file mode 100644 index 000000000..9c6d005ff --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.obj @@ -0,0 +1,12 @@ +# 1 4--6 +# |\ \ | +# | \ \| +# 2--3 5 +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +f 1 2 3 +v -1 1 0 +v 1 -1 0 +v 1 1 0 +f 4 5 6 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.ply b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.ply new file mode 100644 index 000000000..aa319b344 Binary files /dev/null and b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-duplicates.ply differ diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-normals-texcoords.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-normals-texcoords.obj new file mode 100644 index 000000000..e96d09630 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad-normals-texcoords.obj @@ -0,0 +1,12 @@ +# 1 1--4 +# |\ \ | +# | \ \| +# 2--3 3 +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +v 1 1 0 +vn 0 0 1 +vt 0 0 +f 1/1/1 2/1/1 3/1/1 +f 1/1/1 3/1/1 4/1/1 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.obj new file mode 100644 index 000000000..a552bb87c --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.obj @@ -0,0 +1,10 @@ +# 1 1--4 +# |\ \ | +# | \ \| +# 2--3 3 +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +v 1 1 0 +f 1 2 3 +f 1 3 4 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.ply b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.ply new file mode 100644 index 000000000..e36655e07 Binary files /dev/null and b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/quad.ply differ diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin new file mode 100644 index 000000000..84f5a85a4 Binary files /dev/null and b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin differ diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin.in b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin.in new file mode 100644 index 000000000..5a64016f2 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.bin.in @@ -0,0 +1,15 @@ +# Like two-triangles.obj, but the second triangle moved 10 units on Y. Convert +# with magnum-plugins/src/MagnumPlugins/GltfImporter/Test/in2bin.py. + +type = "3f3f3f 3f3f3f" +input = [ + -1, 1, 0, + -1, -1, 0, + 1, -1, 0, + + -1, 11, 0, + 1, 9, 0, + 1, 11, 0 +] + +# kate: hl python diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.gltf b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.gltf new file mode 100644 index 000000000..b9baca103 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles-transformed.gltf @@ -0,0 +1,75 @@ +{ + "asset": { + "version": "2.0" + }, + "buffers": [ + { + "uri": "two-triangles-transformed.bin", + "byteLength": 72 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteLength": 36 + }, + { + "buffer": 0, + "byteOffset": 36, + "byteLength": 36 + } + ], + "accessors": [ + { + "bufferView": 0, + "componentType": 5126, + "count": 3, + "type": "VEC3" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 3, + "type": "VEC3" + } + ], + "meshes": [ + { + "primitives": [ + { + "attributes": { + "POSITION": 0 + } + } + ] + }, + { + "primitives": [ + { + "attributes": { + "POSITION": 1 + } + } + ] + } + ], + "nodes": [ + { + "mesh": 0 + }, + { + "name": "Explicit intermediate node to test proper traversal", + "translation": [0, -5, 0], + "children": [2] + }, + { + "mesh": 1, + "translation": [0, -5, 0] + } + ], + "scenes": [ + { + "nodes": [0, 1] + } + ] +} diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles.obj b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles.obj new file mode 100644 index 000000000..66a190eb5 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-triangles.obj @@ -0,0 +1,14 @@ +# 1 4--6 +# |\ \ | +# | \ \| +# 2--3 5 +o First +v -1 1 0 +v -1 -1 0 +v 1 -1 0 +f 1 2 3 +o Second +v -1 1 0 +v 1 -1 0 +v 1 1 0 +f 4 5 6 diff --git a/src/Magnum/SceneTools/Test/configure.h.cmake b/src/Magnum/SceneTools/Test/configure.h.cmake index dcb0c8113..01fccabe8 100644 --- a/src/Magnum/SceneTools/Test/configure.h.cmake +++ b/src/Magnum/SceneTools/Test/configure.h.cmake @@ -24,3 +24,27 @@ */ #define SCENETOOLS_TEST_DIR "${SCENETOOLS_TEST_DIR}" +#define SCENETOOLS_TEST_OUTPUT_DIR "${SCENETOOLS_TEST_OUTPUT_DIR}" +#cmakedefine SCENECONVERTER_EXECUTABLE_FILENAME "${SCENECONVERTER_EXECUTABLE_FILENAME}" + +#ifdef CORRADE_TARGET_WINDOWS +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_DEBUG_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_BINARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_RELEASE_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_BINARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_BINARY_INSTALL_DIR}" +#endif +#else +#ifdef CORRADE_IS_DEBUG_BUILD +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_DEBUG_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_DEBUG_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_DEBUG_LIBRARY_INSTALL_DIR}" +#else +#define MAGNUM_PLUGINS_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_RELEASE_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_IMPORTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_IMPORTER_RELEASE_LIBRARY_INSTALL_DIR}" +#define MAGNUM_PLUGINS_SCENECONVERTER_INSTALL_DIR "${CMAKE_INSTALL_PREFIX}/${MAGNUM_PLUGINS_SCENECONVERTER_RELEASE_LIBRARY_INSTALL_DIR}" +#endif +#endif diff --git a/src/Magnum/SceneTools/sceneconverter.cpp b/src/Magnum/SceneTools/sceneconverter.cpp index 1f7867943..87c5f2410 100644 --- a/src/Magnum/SceneTools/sceneconverter.cpp +++ b/src/Magnum/SceneTools/sceneconverter.cpp @@ -510,7 +510,7 @@ is specified as well, the IDs reference attributes of the first mesh.)") if(i + 1 >= converterCount && (converter->features() & Trade::SceneConverterFeature::ConvertMeshToFile)) { /* No verbose output for just one converter */ if(converterCount > 1 && args.isSet("verbose")) - Debug{} << "Saving output with" << converterName << Debug::nospace << "..."; + Debug{} << "Saving output (" << Debug::nospace << (i+1) << Debug::nospace << "/" << Debug::nospace << converterCount << Debug::nospace << ") with" << converterName << Debug::nospace << "..."; Trade::Implementation::Duration d{conversionTime}; if(!converter->convertToFile(*mesh, args.value("output"))) {