From 682ba2c41cc1beb0ff9832c2fd25d8174dd1b024 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Tue, 20 Sep 2022 03:14:28 +0200 Subject: [PATCH] sceneconverter: initial support for whole-scene conversion. Quite boring commit message for such a FEATURE, eh?? Yes, the docs need updating at least. --- doc/changelog.dox | 10 + src/Magnum/SceneTools/Test/CMakeLists.txt | 6 + .../SceneTools/Test/SceneConverterTest.cpp | 165 +++++-- .../two-quads-duplicates-fuzzy.bin | Bin 0 -> 180 bytes .../two-quads-duplicates-fuzzy.bin.in | 33 ++ .../two-quads-duplicates-fuzzy.gltf | 99 +++++ .../two-quads-duplicates.bin | Bin 0 -> 180 bytes .../two-quads-duplicates.bin.in | 33 ++ .../two-quads-duplicates.gltf | 99 +++++ .../SceneConverterTestFiles/two-quads.bin | Bin 0 -> 144 bytes .../SceneConverterTestFiles/two-quads.gltf | 99 +++++ src/Magnum/SceneTools/sceneconverter.cpp | 409 ++++++++++++------ 12 files changed, 794 insertions(+), 159 deletions(-) create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates-fuzzy.bin create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates-fuzzy.bin.in create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates-fuzzy.gltf create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates.bin create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates.bin.in create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates.gltf create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads.bin create mode 100644 src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads.gltf diff --git a/doc/changelog.dox b/doc/changelog.dox index c72303200..023c8ee41 100644 --- a/doc/changelog.dox +++ b/doc/changelog.dox @@ -220,6 +220,8 @@ See also: - New @ref SceneTools library that'll be a home for various whole-scene optimization algorithms such as hierarchy flattening, redundant node removal and such +- The @ref magnum-sceneconverter "magnum-sceneconverter" utility now supports + whole-scene conversion instead of operating just on single meshes - The @ref magnum-sceneconverter "magnum-sceneconverter" utility gained an experimental `--concatenate-meshes` option, which will flatten the mesh hierarchy and concatenate all meshes together. Note that it doesn't @@ -1141,6 +1143,14 @@ See also: `--only-mesh-attributes`, `--remove-duplicate-vertices` and `--remove-duplicate-vertices-fuzzy`, respectively, to make it clear they affect meshes and not other scene content +- The @ref magnum-sceneconverter "magnum-sceneconverter" + `--mesh-level` option now requires `--mesh` to be specified as well, + instead of being ignored if `--mesh` isn't present +- Because the @ref magnum-sceneconverter "magnum-sceneconverter" + `--only-mesh-attributes` option takes attribute IDs, it can only be used in + combination with `--mesh` or `--concatenate-meshes`. This restriction might + be eventually lifted again once it's possible to specify attributes by + name. - Due to the rework of @ref Shaders::PhongGL to support directional and attenuated point lights, the original behavior of unattenuated point lights isn't available anymore. For backwards compatibility, light positions diff --git a/src/Magnum/SceneTools/Test/CMakeLists.txt b/src/Magnum/SceneTools/Test/CMakeLists.txt index c896d66b6..e55538ff3 100644 --- a/src/Magnum/SceneTools/Test/CMakeLists.txt +++ b/src/Magnum/SceneTools/Test/CMakeLists.txt @@ -86,6 +86,12 @@ corrade_add_test(SceneToolsSceneConverterTest SceneConverterTest.cpp SceneConverterTestFiles/quad.gltf SceneConverterTestFiles/quad.obj SceneConverterTestFiles/quad.ply + SceneConverterTestFiles/two-quads-duplicates-fuzzy.bin + SceneConverterTestFiles/two-quads-duplicates-fuzzy.gltf + SceneConverterTestFiles/two-quads-duplicates.bin + SceneConverterTestFiles/two-quads-duplicates.gltf + SceneConverterTestFiles/two-quads.bin + SceneConverterTestFiles/two-quads.gltf SceneConverterTestFiles/two-triangles-transformed.bin SceneConverterTestFiles/two-triangles-transformed.gltf SceneConverterTestFiles/two-triangles.obj) diff --git a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp index e2cbdd89e..f6a990388 100644 --- a/src/Magnum/SceneTools/Test/SceneConverterTest.cpp +++ b/src/Magnum/SceneTools/Test/SceneConverterTest.cpp @@ -151,7 +151,7 @@ const struct { "ObjImporter", "StanfordSceneConverter", "quad.ply", nullptr, "Trade::AnySceneImporter::openFile(): option nonexistentOption not recognized by ObjImporter\n" - "Trade::AnySceneConverter::convertToFile(): option nonexistentConverterOption not recognized by StanfordSceneConverter\n"}, + "Trade::AnySceneConverter::beginFile(): 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 @@ -163,6 +163,14 @@ const struct { "quad.ply", nullptr, "Option nonexistentOption not recognized by ObjImporter\n" "Option nonexistentConverterOption not recognized by StanfordSceneConverter\n"}, + {"two meshes + scene", Containers::array({ + /* Removing the generator identifier to have the file fully roundtrip */ + "-c", "generator=", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/two-quads.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/two-quads.gltf")}), + "GltfImporter", "GltfSceneConverter", + /* There should be a minimal difference compared to the original */ + "two-quads.gltf", "two-quads.bin", + {}}, {"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")}), @@ -175,54 +183,105 @@ const struct { "GltfImporter", "StanfordSceneConverter", "quad-duplicates.ply", nullptr, {}}, - {"filter mesh attributes", Containers::array({ + /** @todo drop --mesh once it's not needed anymore again, then add a + multi-mesh variant */ + {"one mesh, filter mesh attributes", Containers::array({ /* Only 0 gets picked from here, others ignored */ - "--only-mesh-attributes", "17,0,25-36", + "--mesh", "0", "--only-mesh-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", nullptr, + {}}, + {"concatenate meshes, filter mesh attributes", Containers::array({ + "--concatenate-meshes", "--only-mesh-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", nullptr, {}}, - {"remove duplicate vertices", Containers::array({ + {"one implicit mesh, remove vertex duplicates", Containers::array({ "--remove-duplicate-vertices", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-duplicates.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), "ObjImporter", "StanfordSceneConverter", "quad.ply", nullptr, {}}, - {"remove duplicate vertices, verbose", Containers::array({ + {"one implicit mesh, remove duplicate vertices, verbose", Containers::array({ /* Forcing the importer and converter to avoid AnySceneImporter / AnySceneConverter delegation messages */ "--remove-duplicate-vertices", "-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", nullptr, + "Mesh 0 duplicate removal: 6 -> 4 vertices\n"}, + {"one selected mesh, remove duplicate vertices, verbose", Containers::array({ + /* Forcing the importer and converter to avoid AnySceneImporter / + AnySceneConverter delegation messages */ + "--mesh", "1", "--remove-duplicate-vertices", "-v", "-I", "GltfImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/two-quads-duplicates.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "GltfImporter", "StanfordSceneConverter", + /* The second mesh in the glTF is deliberately the same as in + quad-duplicates.obj, so this produces the same file */ + "quad.ply", nullptr, "Duplicate removal: 6 -> 4 vertices\n"}, - {"remove duplicate vertices fuzzy", Containers::array({ + {"two meshes + scene, remove duplicate vertices, verbose", Containers::array({ + /* Forcing the importer and converter to avoid AnySceneImporter / + AnySceneConverter delegation messages */ + "--remove-duplicate-vertices", "-v", "-I", "GltfImporter", "-C", "GltfSceneConverter", + /* Removing the generator identifier for a smaller file */ + "-c", "generator=", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/two-quads-duplicates.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/two-quads.gltf")}), + "GltfImporter", "GltfSceneConverter", + /* There should be a minimal difference compared to the original */ + "two-quads.gltf", "two-quads.bin", + "Mesh 0 duplicate removal: 5 -> 4 vertices\n" + "Mesh 1 duplicate removal: 6 -> 4 vertices\n"}, + {"one implicit mesh, remove duplicate vertices fuzzy", Containers::array({ "--remove-duplicate-vertices-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", nullptr, {}}, - {"remove duplicate vertices fuzzy, verbose", Containers::array({ + {"one implicit mesh, remove duplicate vertices fuzzy, verbose", Containers::array({ /* Forcing the importer and converter to avoid AnySceneImporter / AnySceneConverter delegation messages */ - "--remove-duplicate-vertices-fuzzy 1.0e-1", "-v", "-I", "ObjImporter", "-C", "StanfordSceneConverter", + "--remove-duplicate-vertices-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", nullptr, + "Mesh 0 fuzzy duplicate removal: 6 -> 4 vertices\n"}, + {"one selected mesh, remove duplicate vertices fuzzy, verbose", Containers::array({ + /* Forcing the importer and converter to avoid AnySceneImporter / + AnySceneConverter delegation messages */ + "--mesh 1", "--remove-duplicate-vertices-fuzzy", "1.0e-1", "-v", "-I", "GltfImporter", "-C", "StanfordSceneConverter", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/two-quads-duplicates-fuzzy.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), + "GltfImporter", "StanfordSceneConverter", + /* The second mesh in the glTF is deliberately the same as in + quad-duplicates-fuzzy.obj, so this produces the same file */ + "quad.ply", nullptr, "Fuzzy duplicate removal: 6 -> 4 vertices\n"}, - {"one mesh, two converters", Containers::array({ + {"two meshes + scene, remove duplicate vertices fuzzy, verbose", Containers::array({ + /* Forcing the importer and converter to avoid AnySceneImporter / + AnySceneConverter delegation messages */ + "--remove-duplicate-vertices-fuzzy", "1.0e-1", "-v", "-I", "GltfImporter", "-C", "GltfSceneConverter", + /* Removing the generator identifier for a smaller file */ + "-c", "generator=", + Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/two-quads-duplicates-fuzzy.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/two-quads.gltf")}), + "GltfImporter", "GltfSceneConverter", + "two-quads.gltf", "two-quads.bin", + "Mesh 0 fuzzy duplicate removal: 5 -> 4 vertices\n" + "Mesh 1 fuzzy duplicate removal: 6 -> 4 vertices\n"}, + {"one implicit mesh, two converters", Containers::array({ "-C", "MeshOptimizerSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-strip.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.gltf")}), "GltfImporter", "GltfSceneConverter", "quad.gltf", "quad.bin", {}}, - {"one mesh, two converters, explicit last", Containers::array({ + {"one implicit mesh, two converters, explicit last", Containers::array({ "-C", "MeshOptimizerSceneConverter", "-C", "GltfSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-strip.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.gltf")}), "GltfImporter", "GltfSceneConverter", "quad.gltf", "quad.bin", {}}, - {"one mesh, two converters, verbose", Containers::array({ + {"one implicit mesh, two converters, verbose", Containers::array({ "-C", "MeshOptimizerSceneConverter", "-v", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-strip.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.gltf")}), "GltfImporter", "GltfSceneConverter", @@ -245,8 +304,8 @@ const struct { " 65536 -> 65536 shaded pixels\n" " 65536 -> 65536 covered pixels\n" " overdraw 1 -> 1\n" - "Trade::AnySceneConverter::convertToFile(): using GltfSceneConverter\n"}, - {"one mesh, two converters, explicit last, verbose", Containers::array({ + "Trade::AnySceneConverter::beginFile(): using GltfSceneConverter\n"}, + {"one implicit mesh, two converters, explicit last, verbose", Containers::array({ "-C", "MeshOptimizerSceneConverter", "-C", "GltfSceneConverter", "-v", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-strip.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.gltf")}), "GltfImporter", "GltfSceneConverter", @@ -270,14 +329,14 @@ const struct { " 65536 -> 65536 covered pixels\n" " overdraw 1 -> 1\n" "Saving output (2/2) with GltfSceneConverter...\n"}, - {"one mesh, two converters, options for the first only", Containers::array({ + {"one implicit mesh, two converters, options for the first only", Containers::array({ "-C", "MeshOptimizerSceneConverter", "-c", "nonexistentMeshOptimizerOption=yes", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/quad-strip.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/quad.ply")}), "GltfImporter", "GltfSceneConverter", "quad.ply", nullptr, "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n"}, - {"one mesh, two converters, explicit last, options for the first only", Containers::array({ + {"one implicit mesh, two converters, explicit last, options for the first only", Containers::array({ "-C", "MeshOptimizerSceneConverter", "-c", "nonexistentMeshOptimizerOption=yes", "-C", "StanfordSceneConverter", @@ -285,7 +344,7 @@ const struct { "GltfImporter", "GltfSceneConverter", "quad.ply", nullptr, "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n"}, - {"one mesh, two converters, options for both", Containers::array({ + {"one implicit mesh, two converters, options for both", Containers::array({ "-C", "MeshOptimizerSceneConverter", "-c", "nonexistentMeshOptimizerOption=yes", "-c", "nonexistentAnyConverterOption=no", @@ -293,8 +352,8 @@ const struct { "GltfImporter", "GltfSceneConverter", "quad.ply", nullptr, "Option nonexistentMeshOptimizerOption not recognized by MeshOptimizerSceneConverter\n" - "Trade::AnySceneConverter::convertToFile(): option nonexistentAnyConverterOption not recognized by GltfSceneConverter\n"}, - {"one mesh, two converters, explicit last, options for both", Containers::array({ + "Trade::AnySceneConverter::beginFile(): option nonexistentAnyConverterOption not recognized by GltfSceneConverter\n"}, + {"one implicit mesh, two converters, explicit last, options for both", Containers::array({ "-C", "MeshOptimizerSceneConverter", "-c", "nonexistentMeshOptimizerOption=yes", "-C", "StanfordSceneConverter", @@ -311,7 +370,7 @@ const struct { "ObjImporter", "StanfordSceneConverter", "quad.ply", nullptr, "Trade::AnySceneImporter::openFile(): using ObjImporter\n" - "Duplicate removal: 6 -> 4 vertices\n" + "Mesh 0 duplicate removal: 6 -> 4 vertices\n" /** @todo this only verifies that the result of duplicate removal is properly passed to MeshOptimizer, but not that the MeshOptimizer output is properly passed to StanfordSceneConverter -- needs to @@ -330,7 +389,7 @@ const struct { " 65536 -> 65536 shaded pixels\n" " 65536 -> 65536 covered pixels\n" " overdraw 1 -> 1\n" - "Trade::AnySceneConverter::convertToFile(): using StanfordSceneConverter\n"}, + "Trade::AnySceneConverter::beginFile(): using StanfordSceneConverter\n"}, }; const struct { @@ -347,6 +406,18 @@ const struct { 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 "}, + {"--mesh and --concatenate-meshes", Containers::array({ + "--mesh", "0", "--concatenate-meshes", "a", "b"}), + nullptr, nullptr, + "The --mesh and --concatenate-meshes options are mutually exclusive\n"}, + {"--mesh-level but no --mesh", Containers::array({ + "--mesh-level", "0", "a", "b"}), + nullptr, nullptr, + "The --mesh-level option can only be used with --mesh\n"}, + {"--only-mesh-attributes but no --mesh", Containers::array({ + "--only-mesh-attributes", "0", "a", "b"}), + nullptr, nullptr, + "The --only-mesh-attributes option can only be used with --mesh or --concatenate-meshes\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")}), @@ -363,12 +434,13 @@ const struct { "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({ + {"no meshes found for concatenation", Containers::array({ + "--concatenate-meshes", 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")}), + {"can't import a single mesh", Containers::array({ + "-I", "ObjImporter", "--mesh", "0", 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"}, @@ -384,8 +456,14 @@ const struct { "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-mesh-attributes", "LOLNEIN", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/point.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + {"can't import a mesh for per-mesh processing", Containers::array({ + "-I", "ObjImporter", "--remove-duplicate-vertices", 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"}, + {"invalid mesh attribute filter", Containers::array({ + /** @todo drop --mesh once it's not needed anymore again */ + "-I", "ObjImporter", "--mesh", "0", "--only-mesh-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({ @@ -403,22 +481,39 @@ const struct { /* 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({ + {"file coversion begin 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({ + Utility::format("Trade::AnySceneConverter::beginFile(): cannot determine the format of {0}\n" + "Cannot begin conversion of file {0}\n", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.fbx"))}, + {"file coversion end failed", Containers::array({ + "-I", "GltfImporter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/empty.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "GltfImporter", "StanfordSceneConverter", + Utility::format("Trade::AbstractSceneConverter::endFile(): the converter requires exactly one mesh, got 0\n" + "Cannot end conversion of file {0}\n", Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply"))}, + /** @todo importer conversion begin failed, once there's a plugin for which + begin() can fail */ + {"importer coversion end failed", Containers::array({ + "-I", "GltfImporter", "-C", "MeshOptimizerSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/empty.gltf"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "GltfImporter", "MeshOptimizerSceneConverter", + "Trade::AbstractSceneConverter::end(): the converter requires exactly one mesh, got 0\n" + "Cannot end importer conversion\n"}, + {"can't add importer contents", Containers::array({ + "-I", "ObjImporter", "-C", "StanfordSceneConverter", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/broken-mesh.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", "StanfordSceneConverter", + "Trade::ObjImporter::mesh(): wrong index count for point\n" + "Cannot add importer contents\n"}, + {"can't add processed meshes", Containers::array({ + "-I", "ObjImporter", "-C", "StanfordSceneConverter", "--remove-duplicate-vertices", Utility::Path::join(SCENETOOLS_TEST_DIR, "SceneConverterTestFiles/two-triangles.obj"), Utility::Path::join(SCENETOOLS_TEST_OUTPUT_DIR, "SceneConverterTestFiles/whatever.ply")}), + "ObjImporter", "StanfordSceneConverter", + "Trade::AbstractSceneConverter::add(): the converter requires exactly one mesh, got 2\n" + "Cannot add mesh 1\n"}, + {"plugin doesn't support importer 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"}, + "StanfordSceneConverter doesn't support importer conversion, only Trade::SceneConverterFeature::ConvertMeshToData\n"}, }; #endif diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates-fuzzy.bin b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates-fuzzy.bin new file mode 100644 index 0000000000000000000000000000000000000000..b2039ce4d635c1905e9e50764797bb07ecb3ec3b GIT binary patch literal 180 zcmYj}0SbUH3<5Q-gvGzf$E$TYe*j1Qy{X2Q&41qm`V?1x}5+a8Gx R5(o0Z;>c`dH6S)v4FCwO6h8m} literal 0 HcmV?d00001 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates.bin.in b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates.bin.in new file mode 100644 index 000000000..a6a91130a --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates.bin.in @@ -0,0 +1,33 @@ +type = "6I 3f3f3f 3f3f 6I 3f3f3f 3f3f3f" +input = [ + # 2 2--4 + # |\ \ | + # | \ \| + # 0--1 3 + + 0, 1, 2, 2, 3, 4, + + -10, -10, 10, + 10, -10, 10, + -10, 10, 10, + + 10, -10, 10, + 10, 10, 10, + + # 2 3--5 + # |\ \ | + # | \ \| + # 0--1 4 + + 0, 1, 2, 3, 4, 5, + + -1, -1, 0, + 1, -1, 0, + -1, 1, 0, + + -1, 1, 0, + 1, -1, 0, + 1, 1, 0, +] + +# kate: hl python diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates.gltf b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates.gltf new file mode 100644 index 000000000..7e7ecfe66 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads-duplicates.gltf @@ -0,0 +1,99 @@ +{ + "asset": { + "version": "2.0" + }, + "buffers": [ + { + "uri": "two-quads-duplicates.bin", + "byteLength": 180 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 24 + }, + { + "buffer": 0, + "byteOffset": 24, + "byteLength": 60 + }, + { + "buffer": 0, + "byteOffset": 84, + "byteLength": 24 + }, + { + "buffer": 0, + "byteOffset": 108, + "byteLength": 72 + } + ], + "accessors": [ + { + "bufferView": 0, + "componentType": 5125, + "count": 6, + "type": "SCALAR" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 5, + "type": "VEC3" + }, + { + "bufferView": 2, + "componentType": 5125, + "count": 6, + "type": "SCALAR" + }, + { + "bufferView": 3, + "componentType": 5126, + "count": 6, + "type": "VEC3" + } + ], + "meshes": [ + { + "primitives": [ + { + "indices": 0, + "attributes": { + "POSITION": 1 + } + } + ] + }, + { + "primitives": [ + { + "indices": 2, + "attributes": { + "POSITION": 3 + } + } + ] + } + ], + "nodes": [ + { + "translation": [0, 0, 1], + "mesh": 0 + }, + { + "children": [2] + }, + { + "scale": [0.1, 0.1, 0.1], + "mesh": 1 + } + ], + "scenes": [ + { + "nodes": [0, 1] + } + ] +} diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads.bin b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads.bin new file mode 100644 index 0000000000000000000000000000000000000000..1ff34ac4b844ef656618df0e388281da8c63a0ae GIT binary patch literal 144 zcmZQzU|?VbVkRgCv6+FGfkELQ6gvWG5C-v)X_z>a#$`^!eg*~*27v~9BsNGK$cKxA F*#K%04wnD` literal 0 HcmV?d00001 diff --git a/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads.gltf b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads.gltf new file mode 100644 index 000000000..a89213076 --- /dev/null +++ b/src/Magnum/SceneTools/Test/SceneConverterTestFiles/two-quads.gltf @@ -0,0 +1,99 @@ +{ + "asset": { + "version": "2.0" + }, + "buffers": [ + { + "uri": "two-quads.bin", + "byteLength": 144 + } + ], + "bufferViews": [ + { + "buffer": 0, + "byteOffset": 0, + "byteLength": 24 + }, + { + "buffer": 0, + "byteOffset": 24, + "byteLength": 48 + }, + { + "buffer": 0, + "byteOffset": 72, + "byteLength": 24 + }, + { + "buffer": 0, + "byteOffset": 96, + "byteLength": 48 + } + ], + "accessors": [ + { + "bufferView": 0, + "componentType": 5125, + "count": 6, + "type": "SCALAR" + }, + { + "bufferView": 1, + "componentType": 5126, + "count": 4, + "type": "VEC3" + }, + { + "bufferView": 2, + "componentType": 5125, + "count": 6, + "type": "SCALAR" + }, + { + "bufferView": 3, + "componentType": 5126, + "count": 4, + "type": "VEC3" + } + ], + "meshes": [ + { + "primitives": [ + { + "indices": 0, + "attributes": { + "POSITION": 1 + } + } + ] + }, + { + "primitives": [ + { + "indices": 2, + "attributes": { + "POSITION": 3 + } + } + ] + } + ], + "nodes": [ + { + "translation": [0, 0, 1], + "mesh": 0 + }, + { + "children": [2] + }, + { + "scale": [0.1, 0.1, 0.1], + "mesh": 1 + } + ], + "scenes": [ + { + "nodes": [0, 1] + } + ] +} diff --git a/src/Magnum/SceneTools/sceneconverter.cpp b/src/Magnum/SceneTools/sceneconverter.cpp index edc62a1e3..d9bca5572 100644 --- a/src/Magnum/SceneTools/sceneconverter.cpp +++ b/src/Magnum/SceneTools/sceneconverter.cpp @@ -33,11 +33,13 @@ #include #include "Magnum/MeshTools/Concatenate.h" +#include "Magnum/MeshTools/Reference.h" #include "Magnum/MeshTools/RemoveDuplicates.h" #include "Magnum/MeshTools/Transform.h" #include "Magnum/SceneTools/FlattenMeshHierarchy.h" #include "Magnum/Trade/AbstractImporter.h" #include "Magnum/Trade/MeshData.h" +#include "Magnum/Trade/AbstractImageConverter.h" #include "Magnum/Trade/AbstractSceneConverter.h" #include "Magnum/Implementation/converterUtilities.h" @@ -142,18 +144,17 @@ Arguments: given IDs in the output. See @ref Utility::String::parseNumberSequence() for syntax description. - `--remove-duplicate-vertices` --- remove duplicate vertices using - @ref MeshTools::removeDuplicates(const Trade::MeshData&) after import + @ref MeshTools::removeDuplicates(const Trade::MeshData&) in all meshes + after import - `--remove-duplicate-vertices-fuzzy EPSILON` --- remove duplicate vertices using @ref MeshTools::removeDuplicatesFuzzy(const Trade::MeshData&, Float, Double) - after import + in all meshes after import - `-i`, `--importer-options key=val,key2=val2,…` --- configuration options to pass to the importer - `-c`, `--converter-options key=val,key2=val2,…` --- configuration options to pass to the converter(s) -- `--mesh ID` --- mesh to import (default: `0`), ignored if - `--concatenate-meshes` is specified -- `--mesh-level INDEX` --- mesh level to import (default: `0`), ignored if - `--concatenate-meshes` is specified +- `--mesh ID` --- convert just a single mesh instead of the whole scene +- `--mesh-level LEVEL` --- level to select for single-mesh conversion - `--concatenate-meshes` -- flatten mesh hierarchy and concatenate them all together @m_class{m-label m-warning} **experimental** - `--info-animations` --- print into about animations in the input file and @@ -241,12 +242,12 @@ int main(int argc, char** argv) { .addBooleanOption("map").setHelp("map", "memory-map the input for zero-copy import (works only for standalone files)") #endif .addOption("only-mesh-attributes").setHelp("only-mesh-attributes", "include only mesh attributes of given IDs in the output", "N1,N2-N3…") - .addBooleanOption("remove-duplicate-vertices").setHelp("remove-duplicate-vertices", "remove duplicate vertices in the mesh after import") - .addOption("remove-duplicate-vertices-fuzzy").setHelp("remove-duplicate-vertices-fuzzy", "remove duplicate vertices with fuzzy comparison in the mesh after import", "EPSILON") + .addBooleanOption("remove-duplicate-vertices").setHelp("remove-duplicate-vertices", "remove duplicate vertices in all meshes after import") + .addOption("remove-duplicate-vertices-fuzzy").setHelp("remove-duplicate-vertices-fuzzy", "remove duplicate vertices with fuzzy comparison in all meshes after import", "EPSILON") .addOption('i', "importer-options").setHelp("importer-options", "configuration options to pass to the importer", "key=val,key2=val2,…") .addArrayOption('c', "converter-options").setHelp("converter-options", "configuration options to pass to the converter(s)", "key=val,key2=val2,…") - .addOption("mesh", "0").setHelp("mesh", "mesh to import, ignored if --concatenate-meshes is specified", "ID") - .addOption("mesh-level", "0").setHelp("mesh-level", "mesh level to import, ignored if --concatenate-meshes is specified", "INDEX") + .addOption("mesh").setHelp("mesh", "convert just a single mesh instead of the whole scene, ignored if --concatenate-meshes is specified", "ID") + .addOption("mesh-level").setHelp("mesh-level", "level to select for single-mesh conversion", "index") .addBooleanOption("concatenate-meshes").setHelp("concatenate-meshes", "flatten mesh hierarchy and concatenate them all together") .addBooleanOption("info-animations").setHelp("info-animations", "print info about animations in the input file and exit") .addBooleanOption("info-images").setHelp("info-images", "print info about images in the input file and exit") @@ -333,16 +334,38 @@ the first mesh.)") if(isInfoRequested(args)) Warning{} << "Ignoring output file for --info:" << args.value("output"); } + if(args.isSet("concatenate-meshes") && args.value("mesh")) { + Error{} << "The --mesh and --concatenate-meshes options are mutually exclusive"; + return 1; + } + if(args.value("mesh-level") && !args.value("mesh")) { + Error{} << "The --mesh-level option can only be used with --mesh"; + return 1; + } + /** @todo remove this once only-attributes can work with attribute names + and thus for more meshes */ + if(args.value("only-mesh-attributes") && !args.value("mesh") && !args.isSet("concatenate-meshes")) { + Error{} << "The --only-mesh-attributes option can only be used with --mesh or --concatenate-meshes"; + return 1; + } /* Importer manager */ PluginManager::Manager importerManager{ args.value("plugin-dir").empty() ? Containers::String{} : Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractImporter::pluginSearchPaths().back()).second())}; - /* Scene converter manager */ + /* Image converter manager for potential dependencies. Needs to be + constructed before the scene converter manager for proper destruction + order. */ + PluginManager::Manager imageConverterManager{ + args.value("plugin-dir").empty() ? Containers::String{} : + Utility::Path::join(args.value("plugin-dir"), Trade::AbstractImageConverter::pluginSearchPaths().back())}; + + /* Scene converter manager, register the image converter manager with it */ PluginManager::Manager converterManager{ args.value("plugin-dir").empty() ? Containers::String{} : Utility::Path::join(args.value("plugin-dir"), Utility::Path::split(Trade::AbstractSceneConverter::pluginSearchPaths().back()).second())}; + converterManager.registerExternalManager(imageConverterManager); Containers::Pointer importer = importerManager.loadAndInstantiate(args.value("importer")); if(!importer) { @@ -354,14 +377,19 @@ the first mesh.)") if(args.isSet("verbose")) importer->addFlags(Trade::ImporterFlag::Verbose); Implementation::setOptions(*importer, "AnySceneImporter", args.value("importer-options")); - /* Wow, C++, you suck. This implicitly initializes to random shit?! */ - std::chrono::high_resolution_clock::duration importTime{}; + /* Wow, C++, you suck. This implicitly initializes to random shit?! + + Also, because of addSupportedImporterContents() it's not really possible + to distinguish between time spent importing and time spent converting. + So it's lumped into a single variable. Steps that are really just + conversion are measured separately. */ + std::chrono::high_resolution_clock::duration importConversionTime{}; /* Open the file or map it if requested */ #if defined(CORRADE_TARGET_UNIX) || (defined(CORRADE_TARGET_WINDOWS) && !defined(CORRADE_TARGET_WINDOWS_RT)) Containers::Optional> mapped; if(args.isSet("map")) { - Trade::Implementation::Duration d{importTime}; + Trade::Implementation::Duration d{importConversionTime}; if(!(mapped = Utility::Path::mapRead(args.value("input"))) || !importer->openMemory(*mapped)) { Error() << "Cannot memory-map file" << args.value("input"); return 3; @@ -369,7 +397,7 @@ the first mesh.)") } else #endif { - Trade::Implementation::Duration d{importTime}; + Trade::Implementation::Duration d{importConversionTime}; if(!importer->openFile(args.value("input"))) { Error() << "Cannot open file" << args.value("input"); return 3; @@ -378,122 +406,180 @@ the first mesh.)") /* Print file info, if requested */ if(isInfoRequested(args)) { - const bool error = SceneTools::Implementation::printInfo(useColor, useColor24, args, *importer, importTime); + const bool error = SceneTools::Implementation::printInfo(useColor, useColor24, args, *importer, importConversionTime); if(args.isSet("profile")) { - Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast(importTime).count())/1.0e3f << "seconds"; + Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast(importConversionTime).count())/1.0e3f << "seconds"; } return error ? 1 : 0; } - if(!importer->meshCount()) { - Error{} << "No meshes found in" << args.value("input"); - return 1; - } - /* Wow, C++, you suck. This implicitly initializes to random shit?! */ std::chrono::high_resolution_clock::duration conversionTime{}; - Containers::Optional mesh; - - /* Concatenate input meshes, if requested */ - if(args.isSet("concatenate-meshes")) { - Containers::Array meshes; - arrayReserve(meshes, importer->meshCount()); - for(std::size_t i = 0, iMax = importer->meshCount(); i != iMax; ++i) { - Trade::Implementation::Duration d{importTime}; - Containers::Optional meshToConcatenate = importer->mesh(i); - if(!meshToConcatenate) { - Error{} << "Cannot import mesh" << i; + /* Take a single mesh or concatenate all meshes together, if requested. + After that, the importer is changed to one that contains just a single + mesh... */ + bool singleMesh = false; + if(args.isSet("concatenate-meshes") || args.value("mesh")) { + singleMesh = true; + /* ... and subsequent conversion deals with just meshes, throwing away + materials and everything else (if present). */ + + Containers::Optional mesh; + + /* Concatenate all meshes together */ + if(args.isSet("concatenate-meshes")) { + if(!importer->meshCount()) { + Error{} << "No meshes found in" << args.value("input"); return 1; } - arrayAppend(meshes, *std::move(meshToConcatenate)); - } - - /* If there's a scene, use it to flatten mesh hierarchy. If not, assume - all meshes are in the root. */ - /** @todo make it possible to choose the scene */ - if(importer->defaultScene() != -1) { - Containers::Optional scene; - { - Trade::Implementation::Duration d{importTime}; - if(!(scene = importer->scene(importer->defaultScene()))) { - Error{} << "Cannot import scene" << importer->defaultScene() << "for mesh concatenation"; + Containers::Array meshes; + arrayReserve(meshes, importer->meshCount()); + /** @todo handle mesh levels here, once any plugin is capable of + importing them */ + for(std::size_t i = 0, iMax = importer->meshCount(); i != iMax; ++i) { + Trade::Implementation::Duration d{importConversionTime}; + Containers::Optional meshToConcatenate = importer->mesh(i); + if(!meshToConcatenate) { + Error{} << "Cannot import mesh" << i; return 1; } + + arrayAppend(meshes, *std::move(meshToConcatenate)); + } + + /* If there's a scene, use it to flatten mesh hierarchy. If not, + assume all meshes are in the root. */ + /** @todo make it possible to choose the scene */ + if(importer->defaultScene() != -1) { + Containers::Optional scene; + { + Trade::Implementation::Duration d{importConversionTime}; + if(!(scene = importer->scene(importer->defaultScene()))) { + Error{} << "Cannot import scene" << importer->defaultScene() << "for mesh concatenation"; + return 1; + } + } + + Containers::Array flattenedMeshes; + { + Trade::Implementation::Duration d{conversionTime}; + /** @todo once there are 2D scenes, check the scene is 3D */ + for(const Containers::Triple& meshTransformation: SceneTools::flattenMeshHierarchy3D(*scene)) + arrayAppend(flattenedMeshes, MeshTools::transform3D(meshes[meshTransformation.first()], meshTransformation.third())); + } + meshes = std::move(flattenedMeshes); } - Containers::Array flattenedMeshes; { Trade::Implementation::Duration d{conversionTime}; - /** @todo once there are 2D scenes, check the scene is 3D */ - for(const Containers::Triple& meshTransformation: SceneTools::flattenMeshHierarchy3D(*scene)) - arrayAppend(flattenedMeshes, MeshTools::transform3D(meshes[meshTransformation.first()], meshTransformation.third())); + /** @todo this will assert if the meshes have incompatible primitives + (such as some triangles, some lines), or if they have + loops/strips/fans -- handle that explicitly */ + mesh = MeshTools::concatenate(meshes); } - meshes = std::move(flattenedMeshes); - } - /* Concatenate all meshes together */ - { - Trade::Implementation::Duration d{conversionTime}; - /** @todo this will assert if the meshes have incompatible primitives - (such as some triangles, some lines), or if they have - loops/strips/fans -- handle that explicitly */ - mesh = MeshTools::concatenate(meshes); + /* Otherwise import just one */ + } else { + Trade::Implementation::Duration d{importConversionTime}; + if(!(mesh = importer->mesh(args.value("mesh"), args.value("mesh-level")))) { + Error{} << "Cannot import the mesh"; + return 4; + } } - /* Otherwise import just one */ - } else { - Trade::Implementation::Duration d{importTime}; - if(!(mesh = importer->mesh(args.value("mesh"), args.value("mesh-level")))) { - Error{} << "Cannot import the mesh"; - return 4; + /* Filter mesh attributes, if requested */ + /** @todo move outside of the --mesh / --concatenate-meshes branch once + it's possible to filter attributes by name */ + if(Containers::StringView onlyAttributes = args.value("only-mesh-attributes")) { + const Containers::Optional> only = Utility::String::parseNumberSequence(onlyAttributes, 0, mesh->attributeCount()); + if(!only) return 2; + + /** @todo use MeshTools::filterOnlyAttributes() once it has a + rvalue overload that transfers ownership */ + Containers::Array attributes; + arrayReserve(attributes, only->size()); + for(UnsignedInt i: *only) + arrayAppend(attributes, mesh->attributeData(i)); + + const Trade::MeshIndexData indices{mesh->indices()}; + const UnsignedInt vertexCount = mesh->vertexCount(); + mesh = Trade::MeshData{mesh->primitive(), + mesh->releaseIndexData(), indices, + mesh->releaseVertexData(), std::move(attributes), + vertexCount}; } - } - /* Filter mesh attributes, if requested */ - if(const Containers::StringView onlyMeshAttributes = args.value("only-mesh-attributes")) { - const Containers::Optional> only = Utility::String::parseNumberSequence(onlyMeshAttributes, 0, mesh->attributeCount()); - if(!only) return 2; - - /** @todo use MeshTools::filterOnlyAttributes() once it has a rvalue - overload that transfers ownership */ - Containers::Array attributes; - arrayReserve(attributes, only->size()); - for(UnsignedInt i: *only) - arrayAppend(attributes, mesh->attributeData(i)); - - const Trade::MeshIndexData indices{mesh->indices()}; - const UnsignedInt vertexCount = mesh->vertexCount(); - mesh = Trade::MeshData{mesh->primitive(), - mesh->releaseIndexData(), indices, - mesh->releaseVertexData(), std::move(attributes), - vertexCount}; - } + /* Create an importer instance that contains just the single mesh for + further step, without any other data. Simpler than having to + special-case the single-mesh case in all following steps. */ + struct SingleMeshImporter: Trade::AbstractImporter { + explicit SingleMeshImporter(Trade::MeshData&& mesh): mesh{std::move(mesh)} {} - /* Remove duplicate vertices, if requested */ - if(args.isSet("remove-duplicate-vertices")) { - const UnsignedInt beforeVertexCount = mesh->vertexCount(); - { - Trade::Implementation::Duration d{conversionTime}; - mesh = MeshTools::removeDuplicates(*std::move(mesh)); - } - if(args.isSet("verbose")) - Debug{} << "Duplicate removal:" << beforeVertexCount << "->" << mesh->vertexCount() << "vertices"; + Trade::ImporterFeatures doFeatures() const override { return {}; } /* LCOV_EXCL_LINE */ + bool doIsOpened() const override { return true; } + void doClose() override {} /* LCOV_EXCL_LINE */ + + UnsignedInt doMeshCount() const override { return 1; } + Containers::Optional doMesh(UnsignedInt, UnsignedInt) override { + return MeshTools::reference(mesh); + } + + Trade::MeshData mesh; + }; + + importer.emplace(*std::move(mesh)); } - /* Remove duplicate vertices with fuzzy comparison, if requested */ - /** @todo accept two values for float and double fuzzy comparison */ - if(args.value("remove-duplicate-vertices-fuzzy")) { - const UnsignedInt beforeVertexCount = mesh->vertexCount(); - { - Trade::Implementation::Duration d{conversionTime}; - mesh = MeshTools::removeDuplicatesFuzzy(*std::move(mesh), args.value("remove-duplicate-vertices-fuzzy")); + /* Operations to perform on all meshes in the importer. If there are any, + meshes are supplied manually to the converter from the array below. */ + Containers::Array meshes; + if(args.isSet("remove-duplicate-vertices") || + args.value("remove-duplicate-vertices-fuzzy")) + { + for(UnsignedInt i = 0; i != importer->meshCount(); ++i) { + Containers::Optional mesh; + { + /** @todo handle mesh levels here, once any plugin is capable + of importing them */ + Trade::Implementation::Duration d{importConversionTime}; + if(!(mesh = importer->mesh(i))) { + Error{} << "Cannot import mesh" << i; + return 1; + } + } + + const UnsignedInt beforeVertexCount = mesh->vertexCount(); + const bool fuzzy = !!args.value("remove-duplicate-vertices-fuzzy"); + + /** @todo accept two values for float and double fuzzy comparison, + or maybe also different for positions, normals and texcoords? + ugh... */ + if(fuzzy) { + Trade::Implementation::Duration d{conversionTime}; + mesh = MeshTools::removeDuplicatesFuzzy(*std::move(mesh), args.value("remove-duplicate-vertices-fuzzy")); + } else { + Trade::Implementation::Duration d{conversionTime}; + mesh = MeshTools::removeDuplicates(*std::move(mesh)); + } + + if(args.isSet("verbose")) { + Debug d; + /* Mesh index 0 would be confusing in case of + --concatenate-meshes and plain wrong with --mesh, so + don't even print it */ + if(singleMesh) + d << (fuzzy ? "Fuzzy duplicate removal:" : "Duplicate removal:"); + else + d << "Mesh" << i << (fuzzy ? "fuzzy duplicate removal:" : "duplicate removal:"); + d << beforeVertexCount << "->" << mesh->vertexCount() << "vertices"; + } + + arrayAppend(meshes, *std::move(mesh)); } - if(args.isSet("verbose")) - Debug{} << "Fuzzy duplicate removal:" << beforeVertexCount << "->" << mesh->vertexCount() << "vertices"; } /* Assume there's always one passed --converter option less, and the last @@ -516,43 +602,118 @@ the first mesh.)") if(i < args.arrayValueCount("converter-options")) Implementation::setOptions(*converter, "AnySceneConverter", args.arrayValue("converter-options", i)); - /* This is the last --converter (or the implicit AnySceneConverter at - the end), output to a file and exit the loop */ - if(i + 1 >= converterCount && ((converter->features() & Trade::SceneConverterFeature::ConvertMeshToFile) || converter->features() >= (Trade::SceneConverterFeature::ConvertMultipleToFile|Trade::SceneConverterFeature::AddMeshes))) { - /* No verbose output for just one converter */ - if(converterCount > 1 && args.isSet("verbose")) - Debug{} << "Saving output (" << Debug::nospace << (i+1) << Debug::nospace << "/" << Debug::nospace << converterCount << Debug::nospace << ") with" << converterName << Debug::nospace << "..."; + /* Decide if this is the last converter, capable of saving to a file */ + const bool isLastConverter = i + 1 >= converterCount && (converter->features() & (Trade::SceneConverterFeature::ConvertMeshToFile|Trade::SceneConverterFeature::ConvertMultipleToFile)); - Trade::Implementation::Duration d{conversionTime}; - if(!converter->convertToFile(*mesh, args.value("output"))) { - Error{} << "Cannot save file" << args.value("output"); - return 5; + /* No verbose output for just one converter */ + if(converterCount > 1 && args.isSet("verbose")) { + if(isLastConverter) { + Debug{} << "Saving output (" << Debug::nospace << (i+1) << Debug::nospace << "/" << Debug::nospace << converterCount << Debug::nospace << ") with" << converterName << Debug::nospace << "..."; + } else { + CORRADE_INTERNAL_ASSERT(i < converterCount); + Debug{} << "Processing (" << Debug::nospace << (i+1) << Debug::nospace << "/" << Debug::nospace << converterCount << Debug::nospace << ") with" << converterName << Debug::nospace << "..."; } + } - break; + /* This is the last --converter (or the implicit AnySceneConverter at + the end), output to a file */ + if(isLastConverter) { + { + Trade::Implementation::Duration d{conversionTime}; + if(!converter->beginFile(args.value("output"))) { + Error{} << "Cannot begin conversion of file" << args.value("output"); + return 1; + } + } /* This is not the last converter, expect that it's capable of - ConvertMesh */ + converting to an importer instance (or a MeshData wrapped in an + importer instance) */ } else { - CORRADE_INTERNAL_ASSERT(i < converterCount); - if(converterCount > 1 && args.isSet("verbose")) - Debug{} << "Processing (" << Debug::nospace << (i+1) << Debug::nospace << "/" << Debug::nospace << converterCount << Debug::nospace << ") with" << converterName << Debug::nospace << "..."; - - if(!(converter->features() & Trade::SceneConverterFeature::ConvertMesh)) { - Error{} << converterName << "doesn't support mesh conversion, only" << converter->features(); + if(!(converter->features() & (Trade::SceneConverterFeature::ConvertMesh|Trade::SceneConverterFeature::ConvertMultiple))) { + Error{} << converterName << "doesn't support importer conversion, only" << converter->features(); return 6; } + { + Trade::Implementation::Duration d{conversionTime}; + if(!converter->begin()) { + Error{} << "Cannot begin importer conversion"; + return 1; + } + } + } + + /* Contents to convert, by default all of them */ + /** @todo make it possible to filter this on the command line, once the + converters receive this for SceneData, MaterialData and TextureData + as well */ + Trade::SceneContents contents = ~Trade::SceneContents{}; + + /* If there are any loose meshes from previous conversion steps, add + them directly instead, and clear the array so the next iteration (if + any) takes them from the importer instead */ + if(meshes) { + if(!(Trade::sceneContentsFor(*converter) & Trade::SceneContent::Meshes)) { + Warning{} << "Ignoring" << meshes.size() << "meshes not supported by the converter"; + } else for(UnsignedInt i = 0; i != meshes.size(); ++i) { + Trade::Implementation::Duration d{conversionTime}; + if(!converter->add(meshes[i])) { + Error{} << "Cannot add mesh" << i; + return 1; + } + } + + /* Ensure the meshes are not added by addSupportedImporterContents() + below. Do this also in case the converter actually doesn't + support mesh addition, as it would otherwise cause two warnings + about the same thing being printed. */ + contents &= ~Trade::SceneContent::Meshes; + + /* Delete the list to avoid adding them again for the next + converter (at which point they would be stale) */ + /** @todo this line is untested, needs two chained conversion steps + that each change the output to verify the old meshes don't get + reused in the next step again */ + meshes = {}; + } + + { + Trade::Implementation::Duration d{importConversionTime}; + if(!converter->addSupportedImporterContents(*importer, contents)) { + Error{} << "Cannot add importer contents"; + return 5; + } + } + + /* This is the last --converter (or the implicit AnySceneConverter at + the end), end the file and exit the loop */ + if(isLastConverter) { + Trade::Implementation::Duration d{conversionTime}; + if(!converter->endFile()) { + Error{} << "Cannot end conversion of file" << args.value("output"); + return 5; + } + + break; + + /* This is not the last converter, save the resulting importer instance + for the next loop iteration. By design, the importer should not + depend on any data from the converter instance, only on the + converter plugin, so we should be fine replacing the converter with + a different one in the next iteration and keeping just the importer + returned from it. */ + } else { Trade::Implementation::Duration d{conversionTime}; - if(!(mesh = converter->convert(*mesh))) { - Error{} << converterName << "cannot convert the mesh"; - return 7; + if(!(importer = converter->end())) { + Error{} << "Cannot end importer conversion"; + return 1; } } } if(args.isSet("profile")) { - Debug{} << "Import took" << UnsignedInt(std::chrono::duration_cast(importTime).count())/1.0e3f << "seconds, conversion" + Debug{} << "Import and conversion took" << UnsignedInt(std::chrono::duration_cast(importConversionTime).count())/1.0e3f << "seconds, conversion" << UnsignedInt(std::chrono::duration_cast(conversionTime).count())/1.0e3f << "seconds"; } }