diff --git a/tests/tester.cc b/tests/tester.cc index 168f249..4ab6ecf 100644 --- a/tests/tester.cc +++ b/tests/tester.cc @@ -926,3 +926,108 @@ TEST_CASE("zero-sized-bin-chunk-glb", "[issue-440]") { REQUIRE(true == ret); } + +TEST_CASE("serialize-node-emitter", "[KHR_audio]") { + // Stream to serialize to + std::stringstream os; + + { + tinygltf::Model m; + // Create a default audio emitter + m.audioEmitters.resize(1); + // Create a single node + m.nodes.resize(1); + // The node references the single emitter + m.nodes[0].emitter = 0; + // Create a single scene + m.scenes.resize(1); + // Make the scene reference the single node + m.scenes[0].nodes.push_back(0); + + // Serialize model to output stream + tinygltf::TinyGLTF ctx; + bool ret = ctx.WriteGltfSceneToStream(&m, os, false, false); + REQUIRE(true == ret); + } + + { + tinygltf::Model m; + tinygltf::TinyGLTF ctx; + // Parse the serialized model + bool ok = ctx.LoadASCIIFromString(&m, nullptr, nullptr, os.str().c_str(), os.str().size(), ""); + REQUIRE(true == ok); + + // Make sure the single scene is there + REQUIRE(1 == m.scenes.size()); + // Make sure all three nodes are there + REQUIRE(1 == m.nodes.size()); + // Make sure the single root node of the scene is there + REQUIRE(1 == m.scenes[0].nodes.size()); + REQUIRE(0 == m.scenes[0].nodes[0]); + // Retrieve the scene root node + const tinygltf::Node& node = m.nodes[m.scenes[0].nodes[0]]; + // Make sure the single root node has both lod nodes + REQUIRE(0 == node.emitter); + } +} + +TEST_CASE("serialize-lods", "[lods]") { + // Stream to serialize to + std::stringstream os; + + { + tinygltf::Model m; + + m.nodes.resize(3); + // Add Node 1 and Node 2 as lods to Node 0 + m.nodes[0].lods.push_back(1); + m.nodes[0].lods.push_back(2); + + // Add Material 1 and Material 2 as lods to Material 0 + m.materials.resize(3); + m.materials[0].lods.push_back(1); + m.materials[0].lods.push_back(2); + + tinygltf::Scene scene; + // Scene uses Node 0 as root node + scene.nodes.push_back(0); + // Add scene to the model + m.scenes.push_back(scene); + + // Serialize model to output stream + tinygltf::TinyGLTF ctx; + bool ret = ctx.WriteGltfSceneToStream(&m, os, false, false); + REQUIRE(true == ret); + } + + { + tinygltf::Model m; + tinygltf::TinyGLTF ctx; + // Parse the serialized model + bool ok = ctx.LoadASCIIFromString(&m, nullptr, nullptr, os.str().c_str(), os.str().size(), ""); + REQUIRE(true == ok); + + // Make sure all three materials are there + REQUIRE(3 == m.materials.size()); + // Make sure the first material has both lod materials + REQUIRE(2 == m.materials[0].lods.size()); + // Make sure the order is still the same after serialization and deserialization + CHECK(1 == m.materials[0].lods[0]); + CHECK(2 == m.materials[0].lods[1]); + + // Make sure the single scene is there + REQUIRE(1 == m.scenes.size()); + // Make sure all three nodes are there + REQUIRE(3 == m.nodes.size()); + // Make sure the single root node of the scene is there + REQUIRE(1 == m.scenes[0].nodes.size()); + REQUIRE(0 == m.scenes[0].nodes[0]); + // Retrieve the scene root node + const tinygltf::Node& node = m.nodes[m.scenes[0].nodes[0]]; + // Make sure the single root node has both lod nodes + REQUIRE(2 == node.lods.size()); + // Make sure the order is still the same after serialization and deserialization + CHECK(1 == node.lods[0]); + CHECK(2 == node.lods[1]); + } +} diff --git a/tiny_gltf.h b/tiny_gltf.h index 7c7227c..7ef28f4 100644 --- a/tiny_gltf.h +++ b/tiny_gltf.h @@ -764,6 +764,7 @@ struct Material { std::string alphaMode{"OPAQUE"}; // default "OPAQUE" double alphaCutoff{0.5}; // default 0.5 bool doubleSided{false}; // default false + std::vector lods; // level of detail materials (MSFT_lod) PbrMetallicRoughness pbrMetallicRoughness; @@ -1018,6 +1019,7 @@ class Node { int mesh{-1}; int light{-1}; // light source index (KHR_lights_punctual) int emitter{-1}; // audio emitter index (KHR_audio) + std::vector lods; // level of detail nodes (MSFT_lod) std::vector children; std::vector rotation; // length must be 0 or 4 std::vector scale; // length must be 0 or 3 @@ -5165,6 +5167,24 @@ static bool ParseNode(Node *node, std::string *err, const detail::json &o, } node->emitter = emitter; + node->lods.clear(); + if (node->extensions.count("MSFT_lod") != 0) { + auto const &msft_lod_ext = node->extensions["MSFT_lod"]; + if (msft_lod_ext.Has("ids")) { + auto idsArr = msft_lod_ext.Get("ids"); + for (size_t i = 0; i < idsArr.ArrayLen(); ++i) { + node->lods.emplace_back(idsArr.Get(i).GetNumberAsInt()); + } + } else { + if (err) { + *err += + "Node has extension MSFT_lod, but does not reference " + "other nodes via their ids.\n"; + } + return false; + } + } + return true; } @@ -5364,6 +5384,24 @@ static bool ParseMaterial(Material *material, std::string *err, std::string *war ParseExtrasAndExtensions(material, err, o, store_original_json_for_extras_and_extensions); + material->lods.clear(); + if (material->extensions.count("MSFT_lod") != 0) { + auto const &msft_lod_ext = material->extensions["MSFT_lod"]; + if (msft_lod_ext.Has("ids")) { + auto idsArr = msft_lod_ext.Get("ids"); + for (size_t i = 0; i < idsArr.ArrayLen(); ++i) { + material->lods.emplace_back(idsArr.Get(i).GetNumberAsInt()); + } + } else { + if (err) { + *err += + "Material has extension MSFT_lod, but does not reference " + "other materials via their ids.\n"; + } + return false; + } + } + return true; } @@ -7579,11 +7617,40 @@ static void SerializeGltfMaterial(const Material &material, detail::json &o) { } SerializeParameterMap(material.additionalValues, o); -#else - #endif SerializeExtrasAndExtensions(material, o); + + // MSFT_lod + if (!material.lods.empty()) { + detail::json_iterator it; + if (!detail::FindMember(o, "extensions", it)) { + detail::json extensions; + detail::JsonSetObject(extensions); + detail::JsonAddMember(o, "extensions", std::move(extensions)); + detail::FindMember(o, "extensions", it); + } + auto &extensions = detail::GetValue(it); + if (!detail::FindMember(extensions, "MSFT_lod", it)) { + detail::json lod; + detail::JsonSetObject(lod); + detail::JsonAddMember(extensions, "MSFT_lod", std::move(lod)); + detail::FindMember(extensions, "MSFT_lod", it); + } + SerializeNumberArrayProperty("ids", material.lods, detail::GetValue(it)); + } else { + detail::json_iterator ext_it; + if (detail::FindMember(o, "extensions", ext_it)) { + auto &extensions = detail::GetValue(ext_it); + detail::json_iterator lp_it; + if (detail::FindMember(extensions, "MSFT_lod", lp_it)) { + detail::Erase(extensions, lp_it); + } + if (detail::IsEmpty(extensions)) { + detail::Erase(o, ext_it); + } + } + } } static void SerializeGltfMesh(const Mesh &mesh, detail::json &o) { @@ -7806,7 +7873,7 @@ static void SerializeGltfNode(const Node &node, detail::json &o) { detail::json audio; detail::JsonSetObject(audio); detail::JsonAddMember(extensions, "KHR_audio", std::move(audio)); - detail::FindMember(o, "KHR_audio", it); + detail::FindMember(extensions, "KHR_audio", it); } SerializeNumberProperty("emitter", node.emitter, detail::GetValue(it)); } else { @@ -7823,6 +7890,37 @@ static void SerializeGltfNode(const Node &node, detail::json &o) { } } + // MSFT_lod + if (!node.lods.empty()) { + detail::json_iterator it; + if (!detail::FindMember(o, "extensions", it)) { + detail::json extensions; + detail::JsonSetObject(extensions); + detail::JsonAddMember(o, "extensions", std::move(extensions)); + detail::FindMember(o, "extensions", it); + } + auto &extensions = detail::GetValue(it); + if (!detail::FindMember(extensions, "MSFT_lod", it)) { + detail::json lod; + detail::JsonSetObject(lod); + detail::JsonAddMember(extensions, "MSFT_lod", std::move(lod)); + detail::FindMember(extensions, "MSFT_lod", it); + } + SerializeNumberArrayProperty("ids", node.lods, detail::GetValue(it)); + } else { + detail::json_iterator ext_it; + if (detail::FindMember(o, "extensions", ext_it)) { + auto &extensions = detail::GetValue(ext_it); + detail::json_iterator lp_it; + if (detail::FindMember(extensions, "MSFT_lod", lp_it)) { + detail::Erase(extensions, lp_it); + } + if (detail::IsEmpty(extensions)) { + detail::Erase(o, ext_it); + } + } + } + if (!node.name.empty()) SerializeStringProperty("name", node.name, o); SerializeNumberArrayProperty("children", node.children, o); }