From 116bf7699b41057d84dc187f225cafdf2e68409f Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Sun, 24 Mar 2024 21:50:22 +0100 Subject: [PATCH] Added support of saving and loading of embedded blend tree resources. --- src/AnimGraph/AnimGraphBlendTreeResource.h | 2 +- src/AnimGraph/AnimGraphEditor.cc | 6 +- src/AnimGraph/AnimGraphNodes.h | 4 + src/AnimGraph/AnimGraphResource.cc | 334 +++++++++++++-------- src/AnimGraph/AnimGraphResource.h | 106 +++---- tests/AnimGraphResourceTests.cc | 286 +++++++++++++----- 6 files changed, 483 insertions(+), 255 deletions(-) diff --git a/src/AnimGraph/AnimGraphBlendTreeResource.h b/src/AnimGraph/AnimGraphBlendTreeResource.h index 4385032..3715be0 100644 --- a/src/AnimGraph/AnimGraphBlendTreeResource.h +++ b/src/AnimGraph/AnimGraphBlendTreeResource.h @@ -21,7 +21,7 @@ struct AnimNode; struct AnimNodeResource { std::string m_name; - std::string m_type_name; + std::string m_node_type_name; AnimNode* m_anim_node = nullptr; NodeDescriptorBase* m_socket_accessor = nullptr; float m_position[2] = {0.f, 0.f}; diff --git a/src/AnimGraph/AnimGraphEditor.cc b/src/AnimGraph/AnimGraphEditor.cc index 4a4df15..982408c 100644 --- a/src/AnimGraph/AnimGraphEditor.cc +++ b/src/AnimGraph/AnimGraphEditor.cc @@ -162,7 +162,7 @@ void SkinnedMeshWidget(SkinnedMesh* skinned_mesh) { void AnimGraphEditorRenderSidebar( AnimGraphBlendTreeResource& graph_resource, AnimNodeResource& node_resource) { - ImGui::Text("[%s]", node_resource.m_type_name.c_str()); + ImGui::Text("[%s]", node_resource.m_node_type_name.c_str()); char node_name_buffer[256]; memset(node_name_buffer, 0, sizeof(node_name_buffer)); @@ -307,7 +307,7 @@ void AnimGraphEditorUpdate(ax::NodeEditor::EditorContext* context) { ImVec2(node_resource.m_position[0], node_resource.m_position[1])); } ax::NodeEditor::BeginNode(node_id); - ImGui::Text("%s", node_resource.m_type_name.c_str()); + ImGui::Text("%s", node_resource.m_node_type_name.c_str()); // Inputs std::vector& node_inputs = @@ -485,7 +485,7 @@ void LegacyAnimGraphEditorUpdate() { } else if (&node_resource == &sGraphGresource.getGraphInputNode()) { ImGui::TextUnformatted("Graph Inputs"); } else { - ImGui::TextUnformatted(node_resource.m_type_name.c_str()); + ImGui::TextUnformatted(node_resource.m_node_type_name.c_str()); } ImNodes::EndNodeTitleBar(); diff --git a/src/AnimGraph/AnimGraphNodes.h b/src/AnimGraph/AnimGraphNodes.h index 0c199b7..8d960fc 100644 --- a/src/AnimGraph/AnimGraphNodes.h +++ b/src/AnimGraph/AnimGraphNodes.h @@ -262,6 +262,8 @@ static inline AnimNode* AnimNodeFactory(const std::string& name) { result = new LockTranslationNode; } else if (name == "BlendTree") { result = new BlendTreeNode; + } else if (name == "BlendTreeSockets") { + result = new BlendTreeNode; } else if (name == "MathAddNode") { result = new MathAddNode; } else if (name == "MathFloatToVec3Node") { @@ -292,6 +294,8 @@ static inline NodeDescriptorBase* AnimNodeDescriptorFactory( return CreateNodeDescriptor(node); } else if (node_type_name == "BlendTree") { return CreateNodeDescriptor(node); + } else if (node_type_name == "BlendTreeSockets") { + return CreateNodeDescriptor(node); } else if (node_type_name == "MathAddNode") { return CreateNodeDescriptor(node); } else if (node_type_name == "MathFloatToVec3Node") { diff --git a/src/AnimGraph/AnimGraphResource.cc b/src/AnimGraph/AnimGraphResource.cc index 0eacb82..64e2595 100644 --- a/src/AnimGraph/AnimGraphResource.cc +++ b/src/AnimGraph/AnimGraphResource.cc @@ -14,6 +14,10 @@ using json = nlohmann::json; +// forward declarations +static json sAnimGraphResourceBlendTreeToJson(const AnimGraphResource& anim_graph_resource); +static bool sAnimGraphResourceBlendTreeFromJson(const json& json_data, AnimGraphResource* result_graph_resource); + // // Socket <-> json // @@ -125,20 +129,24 @@ Socket sJsonToSocket(const json& json_data) { // AnimGraphNode <-> json // json sAnimGraphNodeToJson( - const AnimNodeResource& node, + const AnimNodeResource* node, size_t node_index, const std::vector& connections) { json result; - result["name"] = node.m_name; + result["name"] = node->m_name; result["type"] = "AnimNodeResource"; - result["node_type"] = node.m_type_name; + result["node_type"] = node->m_node_type_name; for (size_t j = 0; j < 2; j++) { - result["position"][j] = node.m_position[j]; + result["position"][j] = node->m_position[j]; } - for (const auto& socket : node.m_socket_accessor->m_inputs) { + if (node->m_node_type_name == "BlendTree") { + + } + + for (const auto& socket : node->m_socket_accessor->m_inputs) { if (socket.m_type == SocketType::SocketTypeAnimation) { continue; } @@ -157,28 +165,37 @@ json sAnimGraphNodeToJson( } } - for (auto& property : node.m_socket_accessor->m_properties) { + for (auto& property : node->m_socket_accessor->m_properties) { result["properties"][property.m_name] = sSocketToJson(property); } return result; } -AnimNodeResource sAnimGraphNodeFromJson( +AnimNodeResource* sAnimGraphNodeFromJson( const json& json_node, size_t node_index) { - AnimNodeResource result; - result.m_name = json_node["name"]; - result.m_type_name = json_node["node_type"]; - result.m_position[0] = json_node["position"][0]; - result.m_position[1] = json_node["position"][1]; + std::string node_type = json_node["node_type"]; - result.m_anim_node = AnimNodeFactory(result.m_type_name); - result.m_socket_accessor = - AnimNodeDescriptorFactory(result.m_type_name, result.m_anim_node); + if (node_type == "BlendTree") { + AnimGraphResource* result = new AnimGraphResource(); + sAnimGraphResourceBlendTreeFromJson(json_node, result); + return result; + } - for (auto& property : result.m_socket_accessor->m_properties) { + AnimNodeResource* result; + result = new AnimNodeResource(); + result->m_name = json_node["name"]; + result->m_node_type_name = node_type; + result->m_position[0] = json_node["position"][0]; + result->m_position[1] = json_node["position"][1]; + + result->m_anim_node = AnimNodeFactory(result->m_node_type_name); + result->m_socket_accessor = + AnimNodeDescriptorFactory(result->m_node_type_name, result->m_anim_node); + + for (auto& property : result->m_socket_accessor->m_properties) { property = sJsonToSocket(json_node["properties"][property.m_name]); } @@ -187,10 +204,10 @@ AnimNodeResource sAnimGraphNodeFromJson( assert(json_node["inputs"][j].contains("name")); std::string input_name = json_node["inputs"][j]["name"]; Socket* input_socket = - result.m_socket_accessor->GetInputSocket(input_name.c_str()); + result->m_socket_accessor->GetInputSocket(input_name.c_str()); if (input_socket == nullptr) { std::cerr << "Could not find input socket with name " << input_name - << " for node type " << result.m_type_name << std::endl; + << " for node type " << result->m_node_type_name << std::endl; abort(); } *input_socket = sJsonToSocket(json_node["inputs"][j]); @@ -230,6 +247,160 @@ BlendTreeConnectionResource sAnimGraphConnectionFromJson( return connection; } +static json sAnimGraphResourceBlendTreeToJson(const AnimGraphResource& anim_graph_resource) { + json result; + + result["name"] = anim_graph_resource.m_name; + result["type"] = "AnimNodeResource"; + result["node_type"] = "BlendTree"; + + const BlendTreeResource& blend_tree_resource = anim_graph_resource.m_blend_tree_resource; + + for (size_t i = 0; i < blend_tree_resource.m_nodes.size(); i++) { + const AnimNodeResource* node = blend_tree_resource.m_nodes[i]; + + if (node->m_node_type_name == "BlendTree") { + const AnimGraphResource* graph_resource = dynamic_cast(node); + result["nodes"][i] = sAnimGraphResourceBlendTreeToJson(*graph_resource); + } else { + result["nodes"][i] = + sAnimGraphNodeToJson(node, i, blend_tree_resource.m_connections); + } + } + + for (size_t i = 0; i < blend_tree_resource.m_connections.size(); i++) { + const BlendTreeConnectionResource& connection = + blend_tree_resource.m_connections[i]; + result["connections"][i] = sAnimGraphConnectionToJson(connection); + } + + // Graph inputs and outputs + { + const AnimNodeResource* graph_output_node = + blend_tree_resource.m_nodes[0]; + const std::vector graph_inputs = + graph_output_node->m_socket_accessor->m_inputs; + for (size_t i = 0; i < graph_inputs.size(); i++) { + result["nodes"][0]["inputs"][i] = sSocketToJson(graph_inputs[i]); + } + + const AnimNodeResource* graph_input_node = blend_tree_resource.m_nodes[1]; + const std::vector graph_outputs = + graph_input_node->m_socket_accessor->m_outputs; + for (size_t i = 0; i < graph_outputs.size(); i++) { + result["nodes"][1]["outputs"][i] = sSocketToJson(graph_outputs[i]); + } + } + return result; +} + +static bool sAnimGraphResourceBlendTreeFromJson(const json& json_data, AnimGraphResource* result_graph_resource) { + BlendTreeResource& blend_tree_resource = result_graph_resource->m_blend_tree_resource; + + result_graph_resource->m_graph_type_name = "BlendTree"; + result_graph_resource->m_node_type_name = "BlendTree"; + result_graph_resource->m_name = json_data["name"]; + + // Load nodes + for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) { + const json& json_node = json_data["nodes"][i]; + if (json_node["type"] != "AnimNodeResource") { + std::cerr + << "Invalid json object. Expected type 'AnimNodeResource' but got '" + << json_node["type"] << "'." << std::endl; + return false; + } + + AnimNodeResource* node = sAnimGraphNodeFromJson(json_node, i); + blend_tree_resource.m_nodes.push_back(node); + } + + // Graph outputs + const json& graph_outputs = json_data["nodes"][0]["inputs"]; + for (const auto& graph_output : graph_outputs) { + AnimNodeResource* graph_node = blend_tree_resource.m_nodes[0]; + graph_node->m_socket_accessor->m_inputs.push_back( + sJsonToSocket(graph_output)); + } + + // Graph inputs (optional) + if (json_data["nodes"][1].contains("outputs")) { + const json& graph_inputs = json_data["nodes"][1]["outputs"]; + for (const auto& graph_input : graph_inputs) { + AnimNodeResource* graph_node = blend_tree_resource.m_nodes[1]; + graph_node->m_socket_accessor->m_outputs.push_back( + sJsonToSocket(graph_input)); + } + } + + // Load connections + for (const auto& json_connection : json_data["connections"]) { + if (json_connection["type"] != "AnimGraphConnectionResource") { + std::cerr << "Invalid json object. Expected type " + "'AnimGraphConnectionResource' " + "but got '" + << json_connection["type"] << "'." << std::endl; + return false; + } + + BlendTreeConnectionResource connection = + sAnimGraphConnectionFromJson(json_connection); + blend_tree_resource.m_connections.push_back(connection); + } + + return true; +} + +bool BlendTreeResource::ConnectSockets ( + const AnimNodeResource* source_node, + const std::string& source_socket_name, + const AnimNodeResource* target_node, + const std::string& target_socket_name) { + size_t source_node_index = GetNodeIndex(source_node); + size_t target_node_index = GetNodeIndex(target_node); + + if (source_node_index >= m_nodes.size() + || target_node_index >= m_nodes.size()) { + std::cerr << "Cannot connect nodes: could not find nodes." << std::endl; + return false; + } + + Socket* source_socket; + Socket* target_socket; + + if (target_node->m_node_type_name == "BlendTree") { + const AnimGraphResource* target_graph_resource = dynamic_cast(target_node); + AnimNodeResource* graph_output_node = target_graph_resource->m_blend_tree_resource.GetGraphInputNode(); + target_socket = graph_output_node->m_socket_accessor->GetOutputSocket(target_socket_name.c_str()); + } else { + target_socket = + target_node->m_socket_accessor->GetInputSocket(target_socket_name.c_str()); + } + + if (source_node->m_node_type_name == "BlendTree") { + const AnimGraphResource* source_graph_resource = dynamic_cast(source_node); + AnimNodeResource* graph_output_node = source_graph_resource->m_blend_tree_resource.GetGraphOutputNode(); + source_socket = graph_output_node->m_socket_accessor->GetInputSocket(source_socket_name.c_str()); + } else { + source_socket = + source_node->m_socket_accessor->GetOutputSocket(source_socket_name.c_str()); + } + + if (source_socket == nullptr || target_socket == nullptr) { + std::cerr << "Cannot connect nodes: could not find sockets." << std::endl; + return false; + } + + BlendTreeConnectionResource connection; + connection.source_node_index = source_node_index; + connection.source_socket_name = source_socket_name; + connection.target_node_index = target_node_index; + connection.target_socket_name = target_socket_name; + m_connections.push_back(connection); + + return true; +} + bool AnimGraphResource::LoadFromFile(const char* filename) { std::ifstream input_file; input_file.open(filename); @@ -253,7 +424,7 @@ bool AnimGraphResource::LoadFromFile(const char* filename) { } if (json_data["node_type"] == "BlendTree") { - return LoadBlendTreeResourceFromJson(json_data); + return sAnimGraphResourceBlendTreeFromJson(json_data, this); } else if (json_data["node_type"] == "StateMachine") { return LoadStateMachineResourceFromJson(json_data); } @@ -265,69 +436,14 @@ bool AnimGraphResource::LoadFromFile(const char* filename) { return false; } -bool AnimGraphResource::LoadBlendTreeResourceFromJson(nlohmann::json const& json_data) { - m_blend_tree_resource.Reset(); - m_type = "BlendTree"; - m_name = json_data["name"]; - - // Load nodes - for (size_t i = 0, n = json_data["nodes"].size(); i < n; i++) { - const json& json_node = json_data["nodes"][i]; - if (json_node["type"] != "AnimNodeResource") { - std::cerr - << "Invalid json object. Expected type 'AnimNodeResource' but got '" - << json_node["type"] << "'." << std::endl; - return false; - } - - AnimNodeResource node = sAnimGraphNodeFromJson(json_node, i); - m_blend_tree_resource.m_nodes.push_back(node); - } - - // Graph outputs - const json& graph_outputs = json_data["nodes"][0]["inputs"]; - for (const auto& graph_output : graph_outputs) { - AnimNodeResource& graph_node = m_blend_tree_resource.m_nodes[0]; - graph_node.m_socket_accessor->m_inputs.push_back( - sJsonToSocket(graph_output)); - } - - // Graph inputs (optional) - if (json_data["nodes"][1].contains("outputs")) { - const json& graph_inputs = json_data["nodes"][1]["outputs"]; - for (const auto& graph_input : graph_inputs) { - AnimNodeResource& graph_node = m_blend_tree_resource.m_nodes[1]; - graph_node.m_socket_accessor->m_outputs.push_back( - sJsonToSocket(graph_input)); - } - } - - // Load connections - for (const auto& json_connection : json_data["connections"]) { - if (json_connection["type"] != "AnimGraphConnectionResource") { - std::cerr << "Invalid json object. Expected type " - "'AnimGraphConnectionResource' " - "but got '" - << json_connection["type"] << "'." << std::endl; - return false; - } - - BlendTreeConnectionResource connection = - sAnimGraphConnectionFromJson(json_connection); - m_blend_tree_resource.m_connections.push_back(connection); - } - - return true; -} - bool AnimGraphResource::SaveToFile(const char* filename) const { - if (m_type == "BlendTree") { + if (m_graph_type_name == "BlendTree") { return SaveBlendTreeResourceToFile(filename); - } else if (m_type == "StateMachine") { + } else if (m_graph_type_name == "StateMachine") { return SaveStateMachineResourceToFile(filename); } - std::cerr << "Invalid AnimGraphResource type: " << m_type << "." << std::endl; + std::cerr << "Invalid AnimGraphResource type: " << m_graph_type_name << "." << std::endl; return false; } @@ -336,39 +452,8 @@ bool AnimGraphResource::SaveBlendTreeResourceToFile( const char* filename) const { json result; - result["name"] = m_name; - result["type"] = "AnimNodeResource"; - result["node_type"] = "BlendTree"; - - for (size_t i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) { - const AnimNodeResource& node = m_blend_tree_resource.m_nodes[i]; - result["nodes"][i] = - sAnimGraphNodeToJson(node, i, m_blend_tree_resource.m_connections); - } - - for (size_t i = 0; i < m_blend_tree_resource.m_connections.size(); i++) { - const BlendTreeConnectionResource& connection = - m_blend_tree_resource.m_connections[i]; - result["connections"][i] = sAnimGraphConnectionToJson(connection); - } - - // Graph inputs and outputs - { - const AnimNodeResource& graph_output_node = - m_blend_tree_resource.m_nodes[0]; - const std::vector graph_inputs = - graph_output_node.m_socket_accessor->m_inputs; - for (size_t i = 0; i < graph_inputs.size(); i++) { - result["nodes"][0]["inputs"][i] = sSocketToJson(graph_inputs[i]); - } - - const AnimNodeResource& graph_input_node = m_blend_tree_resource.m_nodes[1]; - const std::vector graph_outputs = - graph_input_node.m_socket_accessor->m_outputs; - for (size_t i = 0; i < graph_outputs.size(); i++) { - result["nodes"][1]["outputs"][i] = sSocketToJson(graph_outputs[i]); - } - } + result = sAnimGraphResourceBlendTreeToJson( + *this); std::ofstream output_file; output_file.open(filename); @@ -380,9 +465,9 @@ bool AnimGraphResource::SaveBlendTreeResourceToFile( void AnimGraphResource::CreateBlendTreeInstance( AnimGraphBlendTree& result) const { - if (m_type != "BlendTree") { + if (m_node_type_name != "BlendTree") { std::cerr << "Invalid AnimGraphResource. Expected type 'BlendTree' but got '" - << m_type << "'." << std::endl; + << m_graph_type_name << "'." << std::endl; return; } @@ -396,11 +481,10 @@ void AnimGraphResource::CreateBlendTreeInstance( void AnimGraphResource::CreateBlendTreeRuntimeNodeInstances( AnimGraphBlendTree& result) const { - for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) { - const AnimNodeResource& node_resource = m_blend_tree_resource.m_nodes[i]; - AnimNode* node = AnimNodeFactory(node_resource.m_type_name); - node->m_name = node_resource.m_name; - node->m_node_type_name = node_resource.m_type_name; + for (auto node_resource : m_blend_tree_resource.m_nodes) { + AnimNode* node = AnimNodeFactory(node_resource->m_node_type_name); + node->m_name = node_resource->m_name; + node->m_node_type_name = node_resource->m_node_type_name; result.m_nodes.push_back(node); // runtime node connections @@ -414,9 +498,9 @@ void AnimGraphResource::PrepareBlendTreeIOData( instance.m_node_descriptor = AnimNodeDescriptorFactory("BlendTree", instance.m_nodes[0]); instance.m_node_descriptor->m_outputs = - m_blend_tree_resource.m_nodes[1].m_socket_accessor->m_outputs; + m_blend_tree_resource.m_nodes[1]->m_socket_accessor->m_outputs; instance.m_node_descriptor->m_inputs = - m_blend_tree_resource.m_nodes[0].m_socket_accessor->m_inputs; + m_blend_tree_resource.m_nodes[0]->m_socket_accessor->m_inputs; // // graph inputs @@ -466,9 +550,9 @@ void AnimGraphResource::PrepareBlendTreeIOData( // TODO: instead of every connection, only create data blocks for the source sockets and make sure every source socket gets allocated once. size_t connection_data_storage_size = 0; for (const auto& connection : m_blend_tree_resource.m_connections) { - const AnimNodeResource& source_node = + const AnimNodeResource* source_node = m_blend_tree_resource.m_nodes[connection.source_node_index]; - Socket* source_socket = source_node.m_socket_accessor->GetOutputSocket( + Socket* source_socket = source_node->m_socket_accessor->GetOutputSocket( connection.source_socket_name.c_str()); connection_data_storage_size += source_socket->m_type_size; } @@ -483,7 +567,7 @@ void AnimGraphResource::PrepareBlendTreeIOData( nullptr); for (int i = 0; i < m_blend_tree_resource.m_nodes.size(); i++) { instance_node_descriptors[i] = AnimNodeDescriptorFactory( - m_blend_tree_resource.m_nodes[i].m_type_name, + m_blend_tree_resource.m_nodes[i]->m_node_type_name, instance.m_nodes[i]); } @@ -579,13 +663,13 @@ void AnimGraphResource::PrepareBlendTreeIOData( void AnimGraphResource::SetRuntimeNodeProperties( AnimGraphBlendTree& result) const { for (int i = 2; i < m_blend_tree_resource.m_nodes.size(); i++) { - const AnimNodeResource& node_resource = m_blend_tree_resource.m_nodes[i]; + const AnimNodeResource* node_resource = m_blend_tree_resource.m_nodes[i]; NodeDescriptorBase* node_instance_accessor = - AnimNodeDescriptorFactory(node_resource.m_type_name, result.m_nodes[i]); + AnimNodeDescriptorFactory(node_resource->m_node_type_name, result.m_nodes[i]); std::vector& resource_properties = - node_resource.m_socket_accessor->m_properties; + node_resource->m_socket_accessor->m_properties; for (const auto& property : resource_properties) { const std::string& name = property.m_name; diff --git a/src/AnimGraph/AnimGraphResource.h b/src/AnimGraph/AnimGraphResource.h index b36488d..39ba194 100644 --- a/src/AnimGraph/AnimGraphResource.h +++ b/src/AnimGraph/AnimGraphResource.h @@ -14,22 +14,16 @@ struct AnimGraphBlendTree; struct AnimGraphStateMachine; struct AnimNodeResource { + virtual ~AnimNodeResource() = default; + std::string m_name; - std::string m_type_name; + std::string m_node_type_name; AnimNode* m_anim_node = nullptr; NodeDescriptorBase* m_socket_accessor = nullptr; float m_position[2] = {0.f, 0.f}; }; -static inline AnimNodeResource AnimNodeResourceFactory( - const std::string& node_type_name) { - AnimNodeResource result; - result.m_type_name = node_type_name; - result.m_anim_node = AnimNodeFactory(node_type_name); - result.m_socket_accessor = - AnimNodeDescriptorFactory(node_type_name, result.m_anim_node); - return result; -} +static inline AnimNodeResource* AnimNodeResourceFactory(const std::string& node_type_name); struct BlendTreeConnectionResource { size_t source_node_index = -1; @@ -39,7 +33,7 @@ struct BlendTreeConnectionResource { }; struct BlendTreeResource { - std::vector m_nodes; + std::vector m_nodes; std::vector m_connections; ~BlendTreeResource() { CleanupNodes(); @@ -52,27 +46,28 @@ struct BlendTreeResource { } void CleanupNodes() { - for (AnimNodeResource& node_resource : m_nodes) { - delete node_resource.m_anim_node; - delete node_resource.m_socket_accessor; + for (AnimNodeResource* node_resource : m_nodes) { + delete node_resource->m_anim_node; + delete node_resource->m_socket_accessor; + delete node_resource; } m_nodes.clear(); } void InitGraphConnectors() { - m_nodes.push_back(AnimNodeResourceFactory("BlendTree")); - m_nodes[0].m_name = "Outputs"; - m_nodes.push_back(AnimNodeResourceFactory("BlendTree")); - m_nodes[1].m_name = "Inputs"; + m_nodes.push_back(AnimNodeResourceFactory("BlendTreeSockets")); + m_nodes[0]->m_name = "Outputs"; + m_nodes.push_back(AnimNodeResourceFactory("BlendTreeSockets")); + m_nodes[1]->m_name = "Inputs"; } - AnimNodeResource& GetGraphOutputNode() { return m_nodes[0]; } - AnimNodeResource& GetGraphInputNode() { return m_nodes[1]; } + [[nodiscard]] AnimNodeResource* GetGraphOutputNode() const { return m_nodes[0]; } + [[nodiscard]] AnimNodeResource* GetGraphInputNode() const { return m_nodes[1]; } - size_t GetNodeIndex(const AnimNodeResource& node_resource) const { + size_t GetNodeIndex(const AnimNodeResource* node_resource) const { for (size_t i = 0, n = m_nodes.size(); i < n; i++) { - if (&m_nodes[i] == &node_resource) { + if (m_nodes[i] == node_resource) { return i; } } @@ -81,38 +76,10 @@ struct BlendTreeResource { } bool ConnectSockets ( - const AnimNodeResource& source_node, + const AnimNodeResource* source_node, const std::string& source_socket_name, - const AnimNodeResource& target_node, - const std::string& target_socket_name) { - size_t source_node_index = GetNodeIndex(source_node); - size_t target_node_index = GetNodeIndex(target_node); - - if (source_node_index >= m_nodes.size() - || target_node_index >= m_nodes.size()) { - std::cerr << "Cannot connect nodes: could not find nodes." << std::endl; - return false; - } - - Socket* source_socket = - source_node.m_socket_accessor->GetOutputSocket(source_socket_name.c_str()); - Socket* target_socket = - target_node.m_socket_accessor->GetInputSocket(target_socket_name.c_str()); - - if (source_socket == nullptr || target_socket == nullptr) { - std::cerr << "Cannot connect nodes: could not find sockets." << std::endl; - return false; - } - - BlendTreeConnectionResource connection; - connection.source_node_index = source_node_index; - connection.source_socket_name = source_socket_name; - connection.target_node_index = target_node_index; - connection.target_socket_name = target_socket_name; - m_connections.push_back(connection); - - return true; - } + const AnimNodeResource* target_node, + const std::string& target_socket_name); std::vector GetConstantNodeInputs( std::vector& instance_node_descriptors) const { @@ -125,7 +92,7 @@ struct BlendTreeResource { Socket& input = instance_node_descriptors[i]->m_inputs[j]; if (*input.m_reference.ptr_ptr == nullptr) { - memcpy(&input.m_value, &m_nodes[i].m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue)); + memcpy(&input.m_value, &m_nodes[i]->m_socket_accessor->m_inputs[j].m_value, sizeof(Socket::SocketValue)); result.push_back(&input); } } @@ -147,8 +114,8 @@ struct StateMachineResource { std::vector m_transitions; }; -struct AnimGraphResource { - std::string m_type; +struct AnimGraphResource: AnimNodeResource { + std::string m_graph_type_name; std::string m_name; BlendTreeResource m_blend_tree_resource; @@ -163,7 +130,6 @@ struct AnimGraphResource { private: // BlendTree bool SaveBlendTreeResourceToFile(const char* filename) const; - bool LoadBlendTreeResourceFromJson(nlohmann::json const& json_data); void CreateBlendTreeRuntimeNodeInstances(AnimGraphBlendTree& result) const; void PrepareBlendTreeIOData(AnimGraphBlendTree& instance) const; void SetRuntimeNodeProperties(AnimGraphBlendTree& result) const; @@ -172,4 +138,30 @@ struct AnimGraphResource { bool LoadStateMachineResourceFromJson(nlohmann::json const& json_data); }; +static inline AnimNodeResource* AnimNodeResourceFactory( + const std::string& node_type_name) { + AnimNodeResource* result; + + if (node_type_name == "BlendTree") { + AnimGraphResource* blend_tree_resource = new AnimGraphResource(); + blend_tree_resource->m_blend_tree_resource.InitGraphConnectors(); + result = blend_tree_resource; + } else { + result = new AnimNodeResource(); + } + + result->m_node_type_name = node_type_name; + + if (node_type_name == "BlendTreeSockets") { + result->m_anim_node = AnimNodeFactory("BlendTree"); + result->m_socket_accessor = new NodeDescriptorBase(); + } else { + result->m_anim_node = AnimNodeFactory(node_type_name); + result->m_socket_accessor = + AnimNodeDescriptorFactory(node_type_name, result->m_anim_node); + } + + return result; +} + #endif //ANIMTESTBED_ANIMGRAPHRESOURCE_H diff --git a/tests/AnimGraphResourceTests.cc b/tests/AnimGraphResourceTests.cc index d325d7e..bc50d48 100644 --- a/tests/AnimGraphResourceTests.cc +++ b/tests/AnimGraphResourceTests.cc @@ -55,24 +55,23 @@ TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") { TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") { AnimGraphResource graph_resource; graph_resource.m_name = "AnimSamplerBlendTree"; - graph_resource.m_type = "BlendTree"; + graph_resource.m_graph_type_name = "BlendTree"; BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource; - blend_tree_resource.Reset(); blend_tree_resource.InitGraphConnectors(); // Prepare graph inputs and outputs blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("AnimSampler")); size_t walk_node_index = blend_tree_resource.m_nodes.size() - 1; - AnimNodeResource& walk_node = blend_tree_resource.m_nodes[walk_node_index]; - walk_node.m_name = "WalkAnim"; - walk_node.m_socket_accessor->SetPropertyValue( + AnimNodeResource* walk_node = blend_tree_resource.m_nodes[walk_node_index]; + walk_node->m_name = "WalkAnim"; + walk_node->m_socket_accessor->SetPropertyValue( "Filename", std::string("media/Walking-loop.ozz")); - AnimNodeResource& graph_node = blend_tree_resource.m_nodes[0]; - graph_node.m_socket_accessor->RegisterInput("GraphOutput", nullptr); + AnimNodeResource* graph_node = blend_tree_resource.m_nodes[0]; + graph_node->m_socket_accessor->RegisterInput("GraphOutput", nullptr); blend_tree_resource.ConnectSockets( walk_node, @@ -80,10 +79,10 @@ TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") { blend_tree_resource.GetGraphOutputNode(), "GraphOutput"); - graph_resource.SaveToFile("AnimSamplerBlendTree.json"); + graph_resource.SaveToFile("TestGraphAnimSamplerBlendTree.json"); AnimGraphResource graph_resource_loaded; - graph_resource_loaded.LoadFromFile("AnimSamplerBlendTree.json"); + graph_resource_loaded.LoadFromFile("TestGraphAnimSamplerBlendTree.json"); AnimGraphBlendTree anim_graph_blend_tree; graph_resource_loaded.CreateBlendTreeInstance(anim_graph_blend_tree); @@ -96,8 +95,8 @@ TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") { REQUIRE(anim_graph_blend_tree.Init(graph_context)); REQUIRE(anim_graph_blend_tree.m_nodes.size() == 3); - REQUIRE(anim_graph_blend_tree.m_nodes[0]->m_node_type_name == "BlendTree"); - REQUIRE(anim_graph_blend_tree.m_nodes[1]->m_node_type_name == "BlendTree"); + REQUIRE(anim_graph_blend_tree.m_nodes[0]->m_node_type_name == "BlendTreeSockets"); + REQUIRE(anim_graph_blend_tree.m_nodes[1]->m_node_type_name == "BlendTreeSockets"); REQUIRE(anim_graph_blend_tree.m_nodes[2]->m_node_type_name == "AnimSampler"); // connections within the graph @@ -139,7 +138,7 @@ TEST_CASE("AnimSamplerGraph", "[AnimGraphResource]") { TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") { AnimGraphResource graph_resource; graph_resource.m_name = "AnimSamplerSpeedScaleGraph"; - graph_resource.m_type = "BlendTree"; + graph_resource.m_graph_type_name = "BlendTree"; BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource; blend_tree_resource.Reset(); @@ -152,22 +151,22 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") { blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("SpeedScale")); size_t speed_scale_node_index = blend_tree_resource.m_nodes.size() - 1; - AnimNodeResource& walk_node = blend_tree_resource.m_nodes[walk_node_index]; - walk_node.m_name = "WalkAnim"; - walk_node.m_socket_accessor->SetPropertyValue( + AnimNodeResource* walk_node = blend_tree_resource.m_nodes[walk_node_index]; + walk_node->m_name = "WalkAnim"; + walk_node->m_socket_accessor->SetPropertyValue( "Filename", std::string("media/Walking-loop.ozz")); - AnimNodeResource& speed_scale_node = + AnimNodeResource* speed_scale_node = blend_tree_resource.m_nodes[speed_scale_node_index]; - speed_scale_node.m_name = "SpeedScale"; + speed_scale_node->m_name = "SpeedScale"; float speed_scale_value = 1.35f; - speed_scale_node.m_socket_accessor->SetInputValue( + speed_scale_node->m_socket_accessor->SetInputValue( "SpeedScale", speed_scale_value); - AnimNodeResource& graph_node = blend_tree_resource.m_nodes[0]; - graph_node.m_socket_accessor->RegisterInput("GraphOutput", nullptr); + AnimNodeResource* graph_node = blend_tree_resource.GetGraphOutputNode(); + graph_node->m_socket_accessor->RegisterInput("GraphOutput", nullptr); blend_tree_resource.ConnectSockets(walk_node, "Output", speed_scale_node, "Input"); @@ -177,16 +176,15 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") { blend_tree_resource.GetGraphOutputNode(), "GraphOutput"); - graph_resource.SaveToFile("AnimSamplerSpeedScaleGraph.animgraph.json"); + graph_resource.SaveToFile("TestGraphAnimSamplerSpeedScaleGraph.animgraph.json"); AnimGraphResource graph_resource_loaded; graph_resource_loaded.LoadFromFile( - "AnimSamplerSpeedScaleGraph.animgraph.json"); + "TestGraphAnimSamplerSpeedScaleGraph.animgraph.json"); BlendTreeResource& blend_tree_resource_loaded = graph_resource_loaded.m_blend_tree_resource; Socket* speed_scale_resource_loaded_input = - blend_tree_resource_loaded.m_nodes[speed_scale_node_index] - .m_socket_accessor->GetInputSocket("SpeedScale"); + blend_tree_resource_loaded.m_nodes[speed_scale_node_index]->m_socket_accessor->GetInputSocket("SpeedScale"); REQUIRE(speed_scale_resource_loaded_input != nullptr); REQUIRE_THAT( @@ -203,7 +201,7 @@ TEST_CASE("AnimSamplerSpeedScaleGraph", "[AnimGraphResource]") { TEST_CASE("Blend2Graph", "[AnimGraphResource]") { AnimGraphResource graph_resource; graph_resource.m_name = "WalkRunBlendGraph"; - graph_resource.m_type = "BlendTree"; + graph_resource.m_graph_type_name = "BlendTree"; BlendTreeResource& blend_tree_resource = graph_resource.m_blend_tree_resource; blend_tree_resource.Reset(); @@ -219,25 +217,25 @@ TEST_CASE("Blend2Graph", "[AnimGraphResource]") { blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("Blend2")); size_t blend_node_index = blend_tree_resource.m_nodes.size() - 1; - AnimNodeResource& walk_node = blend_tree_resource.m_nodes[walk_node_index]; - walk_node.m_name = "WalkAnim"; - walk_node.m_socket_accessor->SetPropertyValue( + AnimNodeResource* walk_node = blend_tree_resource.m_nodes[walk_node_index]; + walk_node->m_name = "WalkAnim"; + walk_node->m_socket_accessor->SetPropertyValue( "Filename", std::string("media/Walking-loop.ozz")); - AnimNodeResource& run_node = blend_tree_resource.m_nodes[run_node_index]; - run_node.m_socket_accessor->SetPropertyValue( + AnimNodeResource* run_node = blend_tree_resource.m_nodes[run_node_index]; + run_node->m_socket_accessor->SetPropertyValue( "Filename", std::string("media/Running0-loop.ozz")); - run_node.m_name = "RunAnim"; - AnimNodeResource& blend_node = blend_tree_resource.m_nodes[blend_node_index]; - blend_node.m_name = "BlendWalkRun"; + run_node->m_name = "RunAnim"; + AnimNodeResource* blend_node = blend_tree_resource.m_nodes[blend_node_index]; + blend_node->m_name = "BlendWalkRun"; - AnimNodeResource& graph_node = blend_tree_resource.m_nodes[0]; - graph_node.m_socket_accessor->RegisterInput("GraphOutput", nullptr); + AnimNodeResource* graph_node = blend_tree_resource.GetGraphOutputNode(); + graph_node->m_socket_accessor->RegisterInput("GraphOutput", nullptr); - REQUIRE(graph_node.m_socket_accessor->m_inputs.size() == 1); - REQUIRE(blend_node.m_socket_accessor->GetInputIndex("Input0") == 0); - REQUIRE(blend_node.m_socket_accessor->GetInputIndex("Input1") == 1); + REQUIRE(graph_node->m_socket_accessor->m_inputs.size() == 1); + REQUIRE(blend_node->m_socket_accessor->GetInputIndex("Input0") == 0); + REQUIRE(blend_node->m_socket_accessor->GetInputIndex("Input1") == 1); blend_tree_resource.ConnectSockets(walk_node, "Output", blend_node, "Input0"); blend_tree_resource.ConnectSockets(run_node, "Output", blend_node, "Input1"); @@ -247,10 +245,10 @@ TEST_CASE("Blend2Graph", "[AnimGraphResource]") { blend_tree_resource.GetGraphOutputNode(), "GraphOutput"); - graph_resource.SaveToFile("Blend2Graph.animgraph.json"); + graph_resource.SaveToFile("TestGraphBlend2Graph.animgraph.json"); AnimGraphResource graph_resource_loaded; - graph_resource_loaded.LoadFromFile("Blend2Graph.animgraph.json"); + graph_resource_loaded.LoadFromFile("TestGraphBlend2Graph.animgraph.json"); AnimGraphBlendTree blend_tree_graph; graph_resource_loaded.CreateBlendTreeInstance(blend_tree_graph); @@ -263,8 +261,8 @@ TEST_CASE("Blend2Graph", "[AnimGraphResource]") { REQUIRE(blend_tree_graph.Init(graph_context)); REQUIRE(blend_tree_graph.m_nodes.size() == 5); - REQUIRE(blend_tree_graph.m_nodes[0]->m_node_type_name == "BlendTree"); - REQUIRE(blend_tree_graph.m_nodes[1]->m_node_type_name == "BlendTree"); + REQUIRE(blend_tree_graph.m_nodes[0]->m_node_type_name == "BlendTreeSockets"); + REQUIRE(blend_tree_graph.m_nodes[1]->m_node_type_name == "BlendTreeSockets"); REQUIRE(blend_tree_graph.m_nodes[2]->m_node_type_name == "AnimSampler"); REQUIRE(blend_tree_graph.m_nodes[3]->m_node_type_name == "AnimSampler"); REQUIRE(blend_tree_graph.m_nodes[4]->m_node_type_name == "Blend2"); @@ -349,7 +347,7 @@ TEST_CASE("Blend2Graph", "[AnimGraphResource]") { TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { AnimGraphResource graph_resource_origin; graph_resource_origin.m_name = "TestInputOutputGraph"; - graph_resource_origin.m_type = "BlendTree"; + graph_resource_origin.m_graph_type_name = "BlendTree"; BlendTreeResource& blend_tree_resource = graph_resource_origin.m_blend_tree_resource; blend_tree_resource.Reset(); @@ -359,23 +357,23 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("MathFloatToVec3Node")); size_t float_to_vec3_node_index = blend_tree_resource.m_nodes.size() - 1; - AnimNodeResource& graph_output_node = + AnimNodeResource* graph_output_node = blend_tree_resource.GetGraphOutputNode(); - graph_output_node.m_socket_accessor->RegisterInput( + graph_output_node->m_socket_accessor->RegisterInput( "GraphFloatOutput", nullptr); - graph_output_node.m_socket_accessor->RegisterInput( + graph_output_node->m_socket_accessor->RegisterInput( "GraphVec3Output", nullptr); - AnimNodeResource& graph_input_node = + AnimNodeResource* graph_input_node = blend_tree_resource.GetGraphInputNode(); - graph_input_node.m_socket_accessor->RegisterOutput( + graph_input_node->m_socket_accessor->RegisterOutput( "GraphFloatInput", nullptr); // Prepare graph inputs and outputs - AnimNodeResource& float_to_vec3_node = + AnimNodeResource* float_to_vec3_node = blend_tree_resource.m_nodes[float_to_vec3_node_index]; REQUIRE(blend_tree_resource.ConnectSockets( @@ -407,7 +405,7 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { "GraphVec3Output")); WHEN("Saving and loading graph resource") { - const char* filename = "ResourceSaveLoadGraphInputs.json"; + const char* filename = "TestGraphResourceSaveLoadGraphInputs.json"; graph_resource_origin.SaveToFile(filename); AnimGraphResource graph_resource_loaded; @@ -415,31 +413,31 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { BlendTreeResource& graph_blend_tree_loaded = graph_resource_loaded.m_blend_tree_resource; - const AnimNodeResource& graph_loaded_output_node = + const AnimNodeResource* graph_loaded_output_node = graph_blend_tree_loaded.m_nodes[0]; - const AnimNodeResource& graph_loaded_input_node = + const AnimNodeResource* graph_loaded_input_node = graph_blend_tree_loaded.m_nodes[1]; THEN("Graph inputs and outputs must be in loaded resource as well.") { REQUIRE( - graph_output_node.m_socket_accessor->m_inputs.size() - == graph_loaded_output_node.m_socket_accessor->m_inputs.size()); + graph_output_node->m_socket_accessor->m_inputs.size() + == graph_loaded_output_node->m_socket_accessor->m_inputs.size()); REQUIRE( - graph_input_node.m_socket_accessor->m_outputs.size() - == graph_loaded_input_node.m_socket_accessor->m_outputs.size()); + graph_input_node->m_socket_accessor->m_outputs.size() + == graph_loaded_input_node->m_socket_accessor->m_outputs.size()); REQUIRE( - graph_loaded_input_node.m_socket_accessor->GetOutputSocket( + graph_loaded_input_node->m_socket_accessor->GetOutputSocket( "GraphFloatInput") != nullptr); REQUIRE( - graph_loaded_output_node.m_socket_accessor->GetInputSocket( + graph_loaded_output_node->m_socket_accessor->GetInputSocket( "GraphFloatOutput") != nullptr); REQUIRE( - graph_loaded_output_node.m_socket_accessor->GetInputSocket( + graph_loaded_output_node->m_socket_accessor->GetInputSocket( "GraphVec3Output") != nullptr); @@ -488,7 +486,7 @@ TEST_CASE("ResourceSaveLoadMathGraphInputs", "[AnimGraphResource]") { TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") { AnimGraphResource graph_resource_origin; graph_resource_origin.m_name = "TestSimpleMathGraph"; - graph_resource_origin.m_type = "BlendTree"; + graph_resource_origin.m_graph_type_name = "BlendTree"; BlendTreeResource& blend_tree_resource = graph_resource_origin.m_blend_tree_resource; blend_tree_resource.Reset(); @@ -501,29 +499,29 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") { blend_tree_resource.m_nodes.push_back(AnimNodeResourceFactory("MathAddNode")); size_t math_add1_node_index = blend_tree_resource.m_nodes.size() - 1; - AnimNodeResource& graph_output_node = + AnimNodeResource* graph_output_node = blend_tree_resource.GetGraphOutputNode(); - graph_output_node.m_socket_accessor->RegisterInput( + graph_output_node->m_socket_accessor->RegisterInput( "GraphFloat0Output", nullptr); - graph_output_node.m_socket_accessor->RegisterInput( + graph_output_node->m_socket_accessor->RegisterInput( "GraphFloat1Output", nullptr); - graph_output_node.m_socket_accessor->RegisterInput( + graph_output_node->m_socket_accessor->RegisterInput( "GraphFloat2Output", nullptr); - AnimNodeResource& graph_input_node = + AnimNodeResource* graph_input_node = blend_tree_resource.GetGraphInputNode(); - graph_input_node.m_socket_accessor->RegisterOutput( + graph_input_node->m_socket_accessor->RegisterOutput( "GraphFloatInput", nullptr); // Prepare graph inputs and outputs - AnimNodeResource& math_add0_node = + AnimNodeResource* math_add0_node = blend_tree_resource.m_nodes[math_add0_node_index]; - AnimNodeResource& math_add1_node = + AnimNodeResource* math_add1_node = blend_tree_resource.m_nodes[math_add1_node_index]; // direct output @@ -572,7 +570,7 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") { "GraphFloat2Output")); WHEN("Saving and loading graph resource") { - const char* filename = "ResourceSaveLoadGraphInputs.json"; + const char* filename = "TestGraphResourceSaveLoadGraphInputs.json"; graph_resource_origin.SaveToFile(filename); AnimGraphResource graph_resource_loaded; @@ -618,4 +616,154 @@ TEST_CASE("SimpleMathEvaluations", "[AnimGraphResource]") { } } } +} + + +// +// Tests whether BlendTree within a BlendTree can be saved and loaded +// +// +-----------Parent BlendTree-------------+ +// | | +// | +-----Embd Btree---+ | +// | AnmSmpl--+-\ | | +// | | \-SpdScale------+--Out | +// | |------------------+ | +// | | +// +----------------------------------------+ +// +TEST_CASE("EmbeddedBlendTreeSaveLoad", "[AnimGraphResource]") { + const char* parent_graph_filename = "TestGraphEmbeddedBlendTree.json"; + AnimGraphResource parent_graph_resource; + parent_graph_resource.m_name = "ParentBlendTree"; + parent_graph_resource.m_graph_type_name = "BlendTree"; + parent_graph_resource.m_node_type_name = "BlendTree"; + + BlendTreeResource& parent_blend_tree_resource = + parent_graph_resource.m_blend_tree_resource; + parent_blend_tree_resource.Reset(); + parent_blend_tree_resource.InitGraphConnectors(); + + // Setup parent outputs + AnimNodeResource* parent_blend_tree_outputs = parent_blend_tree_resource.GetGraphOutputNode(); + parent_blend_tree_outputs->m_socket_accessor->RegisterInput("Output", nullptr); + + // Parent AnimSampler + parent_blend_tree_resource.m_nodes.push_back( + AnimNodeResourceFactory("AnimSampler")); + + AnimNodeResource* walk_node = + parent_blend_tree_resource.m_nodes.back(); + walk_node->m_name = "WalkAnim"; + walk_node->m_socket_accessor->SetPropertyValue( + "Filename", + std::string("media/Walking-loop.ozz")); + + // + // Embedded Tree + // + parent_blend_tree_resource.m_nodes.push_back( + AnimNodeResourceFactory("BlendTree")); + size_t embedded_blend_tree_node_index = + parent_blend_tree_resource.m_nodes.size() - 1; + AnimGraphResource* embedded_graph = + dynamic_cast(parent_blend_tree_resource.m_nodes.back()); + embedded_graph->m_name = "EmbeddedBlendTree"; + embedded_graph->m_node_type_name = "BlendTree"; + BlendTreeResource* embedded_blend_tree_resource = &embedded_graph->m_blend_tree_resource; + + // Embedded: outputs + AnimNodeResource* embedded_outputs = + embedded_blend_tree_resource->GetGraphOutputNode(); + embedded_outputs->m_socket_accessor->RegisterInput( + "AnimOutput", + nullptr); + + // Embedded: inputs + AnimNodeResource* embedded_inputs = + embedded_blend_tree_resource->GetGraphInputNode(); + embedded_inputs->m_socket_accessor->RegisterOutput( + "AnimInput", + nullptr); + + // Embedded: SpeedScale node + embedded_blend_tree_resource->m_nodes.push_back( + AnimNodeResourceFactory("SpeedScale")); + AnimNodeResource* embedded_speed_scale_resource = embedded_blend_tree_resource->m_nodes.back(); + + embedded_blend_tree_resource->ConnectSockets(embedded_inputs, "AnimInput", embedded_speed_scale_resource, "Input"); + embedded_blend_tree_resource->ConnectSockets(embedded_speed_scale_resource, "Output", embedded_outputs, "AnimOutput"); + + // + // Parent: setup connections + // + REQUIRE(parent_blend_tree_resource.ConnectSockets(walk_node, "Output", embedded_graph, "AnimInput")); + REQUIRE(parent_blend_tree_resource.ConnectSockets(embedded_graph, "AnimOutput", parent_blend_tree_outputs, "Output")); + + parent_graph_resource.SaveToFile(parent_graph_filename); + + // + // Load the graph + // + AnimGraphResource parent_graph_resource_loaded; + parent_graph_resource_loaded.LoadFromFile(parent_graph_filename); + + // + // Check the loaded parent graph + // + CHECK(parent_graph_resource.m_name == parent_graph_resource_loaded.m_name); + CHECK(parent_graph_resource.m_graph_type_name == parent_graph_resource_loaded.m_graph_type_name); + CHECK(parent_graph_resource.m_node_type_name + == parent_graph_resource_loaded.m_node_type_name); + + const BlendTreeResource& parent_blend_tree_resource_loaded = parent_graph_resource_loaded.m_blend_tree_resource; + + CHECK(parent_blend_tree_resource.m_nodes.size() == parent_blend_tree_resource_loaded.m_nodes.size()); + for (size_t i = 0; i < parent_blend_tree_resource.m_nodes.size(); i++) { + const AnimNodeResource* parent_node = parent_blend_tree_resource.m_nodes[i]; + const AnimNodeResource* parent_node_loaded = parent_blend_tree_resource_loaded.m_nodes[i]; + + CHECK(parent_node->m_name == parent_node_loaded->m_name); + CHECK(parent_node->m_node_type_name == parent_node_loaded->m_node_type_name); + } + + CHECK(parent_blend_tree_resource.m_connections.size() == parent_blend_tree_resource_loaded.m_connections.size()); + for (size_t i = 0; i < parent_blend_tree_resource.m_connections.size(); i++) { + const BlendTreeConnectionResource& parent_connection = parent_blend_tree_resource.m_connections[i]; + const BlendTreeConnectionResource& parent_connection_loaded = parent_blend_tree_resource_loaded.m_connections[i]; + + CHECK(parent_connection.source_node_index == parent_connection_loaded.source_node_index); + CHECK(parent_connection.source_socket_name == parent_connection_loaded.source_socket_name); + CHECK(parent_connection.target_node_index == parent_connection_loaded.target_node_index); + CHECK(parent_connection.target_socket_name == parent_connection_loaded.target_socket_name); + } + + // + // Check the loaded embedded graph + // + REQUIRE(parent_blend_tree_resource_loaded.m_nodes[3]->m_node_type_name == "BlendTree"); + + const AnimGraphResource* embedded_graph_loaded = dynamic_cast(parent_blend_tree_resource_loaded.m_nodes[3]); + const BlendTreeResource& embedded_blend_tree_resource_loaded = embedded_graph_loaded->m_blend_tree_resource; + + CHECK(embedded_blend_tree_resource->m_nodes.size() == embedded_blend_tree_resource_loaded.m_nodes.size()); + CHECK(embedded_blend_tree_resource->m_connections.size() == embedded_blend_tree_resource_loaded.m_connections.size()); + + for (size_t i = 0; i < embedded_blend_tree_resource->m_nodes.size(); i++) { + const AnimNodeResource* parent_node = embedded_blend_tree_resource->m_nodes[i]; + const AnimNodeResource* parent_node_loaded = embedded_blend_tree_resource_loaded.m_nodes[i]; + + CHECK(parent_node->m_name == parent_node_loaded->m_name); + CHECK(parent_node->m_node_type_name == parent_node_loaded->m_node_type_name); + } + + CHECK(embedded_blend_tree_resource->m_connections.size() == embedded_blend_tree_resource_loaded.m_connections.size()); + for (size_t i = 0; i < embedded_blend_tree_resource->m_connections.size(); i++) { + const BlendTreeConnectionResource& embedded_connection = embedded_blend_tree_resource->m_connections[i]; + const BlendTreeConnectionResource& embedded_connection_loaded = embedded_blend_tree_resource_loaded.m_connections[i]; + + CHECK(embedded_connection.source_node_index == embedded_connection_loaded.source_node_index); + CHECK(embedded_connection.source_socket_name == embedded_connection_loaded.source_socket_name); + CHECK(embedded_connection.target_node_index == embedded_connection_loaded.target_node_index); + CHECK(embedded_connection.target_socket_name == embedded_connection_loaded.target_socket_name); + } } \ No newline at end of file