diff --git a/include/fastgltf/core.hpp b/include/fastgltf/core.hpp index d56e1cb08..34c440e1e 100644 --- a/include/fastgltf/core.hpp +++ b/include/fastgltf/core.hpp @@ -274,6 +274,11 @@ namespace fastgltf { * Calls fastgltf::validate for the passed asset before writing. */ ValidateAsset = 1 << 1, + + /** + * Pretty-prints the outputted JSON. This option is ignored for binary glTFs. + */ + PrettyPrintJson = 1 << 2, }; // clang-format on @@ -776,6 +781,11 @@ namespace fastgltf { void setUserPointer(void* pointer) noexcept; }; + /** + * This converts a compacted JSON string into a more readable pretty format. + */ + void prettyPrintJson(std::string& json); + template struct ExportResult { T output; @@ -786,6 +796,10 @@ namespace fastgltf { /** * A exporter for serializing one or more glTF files into JSON and GLB forms. + * + * @note This does not write anything to any files. This class only serializes data + * into memory structures, which can then be used to manually write them to disk. + * If you want to let fastgltf handle the file writing, too, use fastgltf::FileExporter. */ class Exporter { protected: diff --git a/include/fastgltf/types.hpp b/include/fastgltf/types.hpp index 983894995..02ea2b02b 100644 --- a/include/fastgltf/types.hpp +++ b/include/fastgltf/types.hpp @@ -32,6 +32,7 @@ #include #include #include +#include #include #include #include diff --git a/src/fastgltf.cpp b/src/fastgltf.cpp index e256c6603..0c20ae520 100644 --- a/src/fastgltf.cpp +++ b/src/fastgltf.cpp @@ -3526,6 +3526,40 @@ void fg::Parser::setUserPointer(void* pointer) noexcept { #pragma endregion #pragma region Exporter +void fg::prettyPrintJson(std::string& json) { + std::size_t i = 0; + std::size_t depth = 0; + auto insertNewline = [&i, &depth, &json]() { + json.insert(i, 1, '\n'); + json.insert(i + 1, depth, '\t'); + i += 1 + depth; + }; + + while (i < json.size()) { + if (json[i] == '"') { + // Skip to the end of the string + do { + ++i; + } while (json[i] != '"' && json[i - 1] != '\\'); + } + + if (json[i] == '{' || json[i] == '[') { + ++depth; + ++i; // Insert \n after the character + insertNewline(); + } else if (json[i] == '}' || json[i] == ']') { + --depth; + insertNewline(); + ++i; // Insert \n before the character + } else if (json[i] == ',') { + ++i; // Insert \n after the character + insertNewline(); + } else { + ++i; + } + } +} + void fg::Exporter::setBufferPath(std::filesystem::path folder) { if (!folder.is_relative()) { return; @@ -3537,7 +3571,7 @@ void fg::Exporter::setImagePath(std::filesystem::path folder) { if (!folder.is_relative()) { return; } - bufferFolder = std::move(folder); + imageFolder = std::move(folder); } void fg::Exporter::writeAccessors(const Asset& asset, std::string& json) { @@ -3895,7 +3929,7 @@ void fg::Exporter::writeMaterials(const Asset& asset, std::string& json) { } } - if (it->alphaCutoff != 0.5f) { + if (it->alphaMode == AlphaMode::Mask && it->alphaCutoff != 0.5f) { if (json.back() != ',') json += ','; json += R"("alphaCutoff":)" + std::to_string(it->alphaCutoff); } @@ -4313,6 +4347,10 @@ fg::Expected> fg::Exporter::writeGLTF(const Asset& outputString += "}"; + if (hasBit(options, ExportOptions::PrettyPrintJson)) { + prettyPrintJson(outputString); + } + if (errorCode != Error::None) { return Expected> { errorCode }; } diff --git a/tests/write_tests.cpp b/tests/write_tests.cpp index 30b549096..4becf0ee3 100644 --- a/tests/write_tests.cpp +++ b/tests/write_tests.cpp @@ -1,3 +1,5 @@ +#include + #include #include @@ -13,7 +15,7 @@ TEST_CASE("Test simple glTF composition", "[write-tests]") { asset.bufferViews.emplace_back(std::move(bufferView)); fastgltf::Exporter exporter; - auto result = exporter.writeGLTF(&asset); + auto result = exporter.writeGLTF(asset); REQUIRE(result.error() == fastgltf::Error::None); REQUIRE(!result.get().output.empty()); } @@ -24,18 +26,18 @@ TEST_CASE("Read glTF, write it, and then read it again and validate", "[write-te REQUIRE(cubeJsonData->loadFromFile(cubePath / "Cube.gltf")); fastgltf::Parser parser; - auto cube = parser.loadGLTF(cubeJsonData.get(), cubePath); + auto cube = parser.loadGltfJson(cubeJsonData.get(), cubePath); REQUIRE(cube.error() == fastgltf::Error::None); REQUIRE(fastgltf::validate(cube.get()) == fastgltf::Error::None); fastgltf::Exporter exporter; - auto expected = exporter.writeGLTF(&(cube.get())); + auto expected = exporter.writeGLTF(cube.get()); REQUIRE(expected.error() == fastgltf::Error::None); fastgltf::GltfDataBuffer cube2JsonData; cube2JsonData.copyBytes(reinterpret_cast(expected.get().output.data()), expected.get().output.size()); - auto cube2 = parser.loadGLTF(&cube2JsonData, cubePath); + auto cube2 = parser.loadGltfJson(&cube2JsonData, cubePath); REQUIRE(cube2.error() == fastgltf::Error::None); REQUIRE(fastgltf::validate(cube2.get()) == fastgltf::Error::None); } @@ -48,10 +50,10 @@ TEST_CASE("Try writing a glTF with all buffers and images", "[write-tests]") { fastgltf::Parser parser; auto options = fastgltf::Options::LoadExternalBuffers | fastgltf::Options::LoadExternalImages; - auto cube = parser.loadGLTF(&gltfDataBuffer, cubePath, options); + auto cube = parser.loadGltfJson(&gltfDataBuffer, cubePath, options); REQUIRE(cube.error() == fastgltf::Error::None); fastgltf::FileExporter exporter; - auto error = exporter.writeGLTF(&cube.get(), path / "export" / "cube.gltf"); + auto error = exporter.writeGLTF(cube.get(), path / "export" / "cube.gltf", fastgltf::ExportOptions::PrettyPrintJson); REQUIRE(error == fastgltf::Error::None); }