diff --git a/src/ObjCommon/XModel/Gltf/JsonGltf.h b/src/ObjCommon/XModel/Gltf/JsonGltf.h index 5922df88..16eb8104 100644 --- a/src/ObjCommon/XModel/Gltf/JsonGltf.h +++ b/src/ObjCommon/XModel/Gltf/JsonGltf.h @@ -1,6 +1,7 @@ #pragma once #include "Json/JsonExtension.h" +#include #include #include #include @@ -20,14 +21,16 @@ namespace gltf class JsonNode { public: - std::string name; - std::vector translation; - std::vector rotation; - std::vector scale; - std::vector children; + std::optional name; + std::optional> translation; + std::optional> rotation; + std::optional> scale; + std::optional> children; + std::optional skin; + std::optional mesh; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNode, name, translation, rotation, scale, children); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonNode, name, translation, rotation, scale, children, skin, mesh); class JsonBuffer { @@ -166,7 +169,7 @@ namespace gltf unsigned output; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAnimationSampler, input); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonAnimationSampler, input, interpolation, output); class JsonAnimation { @@ -238,19 +241,30 @@ namespace gltf NLOHMANN_DEFINE_TYPE_EXTENSION(JsonSkin, inverseBindMatrices, skeleton, joints); + class JsonScene + { + public: + std::vector nodes; + std::optional name; + }; + + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonScene, nodes, name); + class JsonRoot { public: - std::vector accessors; - std::vector animations; + std::optional> accessors; + std::optional> animations; JsonAsset asset; - std::vector buffers; - std::vector bufferViews; - std::vector materials; - std::vector meshes; - std::vector nodes; - std::vector skins; + std::optional> buffers; + std::optional> bufferViews; + std::optional> materials; + std::optional> meshes; + std::optional> nodes; + std::optional> skins; + std::optional scene; + std::optional> scenes; }; - NLOHMANN_DEFINE_TYPE_EXTENSION(JsonRoot, asset); + NLOHMANN_DEFINE_TYPE_EXTENSION(JsonRoot, accessors, animations, asset, buffers, bufferViews, materials, meshes, nodes, skins, scene, scenes); } // namespace gltf diff --git a/src/ObjWriting/XModel/Gltf/GltfWriter.cpp b/src/ObjWriting/XModel/Gltf/GltfWriter.cpp index 3aa03f9b..01d37ac1 100644 --- a/src/ObjWriting/XModel/Gltf/GltfWriter.cpp +++ b/src/ObjWriting/XModel/Gltf/GltfWriter.cpp @@ -13,6 +13,9 @@ namespace class GltfWriterImpl final : public gltf::Writer { + static constexpr auto NODE_INDEX_MESH = 0u; + static constexpr auto NODE_FIRST_INDEX_BONES = 1u; + public: GltfWriterImpl(const Output* output, std::string gameName, std::string zoneName) : m_output(output), @@ -24,8 +27,12 @@ namespace void Write(std::ostream& stream) override { JsonRoot gltf; - gltf.asset.version = GLTF_VERSION_STRING; - gltf.asset.generator = GLTF_GENERATOR; + CreateJsonAsset(gltf.asset); + CreateMeshNode(gltf); + CreateMesh(gltf); + CreateSkeletonNodes(gltf); + CreateSkin(gltf); + CreateScene(gltf); const json jRoot = gltf; m_output->EmitJson(jRoot); @@ -33,6 +40,109 @@ namespace } private: + static void CreateJsonAsset(JsonAsset& asset) + { + asset.version = GLTF_VERSION_STRING; + asset.generator = GLTF_GENERATOR; + } + + void CreateMeshNode(JsonRoot& gltf) const + { + JsonNode meshNode; + + // We only have one mesh + meshNode.mesh = 0u; + + // Only add skin if the model has bones + if (!m_bones.empty()) + { + // We only have one skin + meshNode.skin = 0u; + } + + if (!gltf.nodes.has_value()) + gltf.nodes.emplace(); + + gltf.nodes->emplace_back(std::move(meshNode)); + } + + void CreateMesh(JsonRoot& gltf) const + { + if (!gltf.meshes.has_value()) + gltf.meshes.emplace(); + + JsonMesh mesh; + + gltf.meshes->emplace_back(std::move(mesh)); + } + + static unsigned CreateNodeIndexFromBoneIndex(const unsigned boneIndex) + { + return boneIndex + NODE_FIRST_INDEX_BONES; + } + + void CreateSkeletonNodes(JsonRoot& gltf) const + { + if (m_bones.empty()) + return; + + if (!gltf.nodes.has_value()) + gltf.nodes.emplace(); + + for (const auto [boneIndex, bone] : std::views::enumerate(m_bones)) + { + JsonNode boneNode; + + boneNode.name = bone.name; + boneNode.translation = std::to_array(bone.globalOffset); + boneNode.rotation = std::to_array({bone.globalRotation.m_x, bone.globalRotation.m_y, bone.globalRotation.m_z, bone.globalRotation.m_w}); + + const auto isParentOf = [this, boneIndex](const unsigned b) + { + return m_bones[b].parentIndex == boneIndex; + }; + auto children = std::ranges::iota_view(0u, m_bones.size()) | std::views::filter(isParentOf) + | std::views::transform(CreateNodeIndexFromBoneIndex) | std::ranges::to>(); + if (!children.empty()) + boneNode.children = std::move(children); + + gltf.nodes->emplace_back(std::move(boneNode)); + } + } + + void CreateSkin(JsonRoot& gltf) const + { + if (m_bones.empty()) + return; + + JsonSkin skin; + skin.joints = + std::ranges::iota_view(0u, m_bones.size()) | std::views::transform(CreateNodeIndexFromBoneIndex) | std::ranges::to>(); + + if (!gltf.skins.has_value()) + gltf.skins.emplace(); + gltf.skins->emplace_back(std::move(skin)); + } + + void CreateScene(JsonRoot& gltf) const + { + JsonScene scene; + + // Only add skin if the model has bones + if (m_bones.empty()) + scene.nodes.emplace_back(NODE_INDEX_MESH); + else + scene.nodes.emplace_back(m_skeleton_node); + + if (!gltf.scenes.has_value()) + gltf.scenes.emplace(); + + gltf.scenes->emplace_back(std::move(scene)); + gltf.scene = 0u; + } + + unsigned m_skeleton_node = 0u; + const Output* m_output; std::string m_game_name; std::string m_zone_name;