From 3e02f28b183fcdf9b6d83e60d546874002eb707b Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Mon, 14 Feb 2022 22:37:19 +0100 Subject: [PATCH] Graph Input/Output wiring added. --- src/AnimGraphEditor.cc | 111 +++++++++++++++- src/AnimGraphResource.cc | 168 +++++++++++++++++++---- src/AnimGraphResource.h | 127 ++++++++++++++++-- tests/AnimGraphResourceTests.cc | 228 +++++++++++++++++++++++++++++++- 4 files changed, 588 insertions(+), 46 deletions(-) diff --git a/src/AnimGraphEditor.cc b/src/AnimGraphEditor.cc index 4b460b7..5ccc5e6 100644 --- a/src/AnimGraphEditor.cc +++ b/src/AnimGraphEditor.cc @@ -9,8 +9,24 @@ static AnimGraphResource gGraphResource; -constexpr int NodeInputAttributeFlag = 2 << 16; -constexpr int NodeOutputAttributeFlag = 2 << 17; +ImNodesPinShape sGetSocketShapeFromSocketType(const SocketType& socket_type) { + switch (socket_type) { + case SocketType::SocketTypeAnimation: + return ImNodesPinShape_QuadFilled; + case SocketType::SocketTypeFloat: + return ImNodesPinShape_CircleFilled; + case SocketType::SocketTypeVec3: + return ImNodesPinShape_TriangleFilled; + case SocketType::SocketTypeQuat: + return ImNodesPinShape_Triangle; + case SocketType::SocketTypeBool: + return ImNodesPinShape_Circle; + default: + break; + } + + return ImNodesPinShape_Quad; +} void AnimGraphEditorUpdate() { ImGui::BeginMenuBar(); @@ -20,7 +36,7 @@ void AnimGraphEditorUpdate() { if (ImGui::Button("Load")) { gGraphResource.loadFromFile("editor_graph.json"); - for (size_t i = 1, n = gGraphResource.m_nodes.size(); i < n; i++) { + for (size_t i = 0, n = gGraphResource.m_nodes.size(); i < n; i++) { const AnimNodeResource& node_resource = gGraphResource.m_nodes[i]; ImNodes::SetNodeGridSpacePos( i, @@ -85,7 +101,84 @@ void AnimGraphEditorUpdate() { ImGui::PopStyleVar(ImGuiStyleVar_WindowPadding); } - for (size_t i = 1, n = gGraphResource.m_nodes.size(); i < n; i++) { + // Graph Output and Inputs + if (gGraphResource.m_nodes.size() > 0) { + // Graph Output + AnimNodeResource& graph_output_node = gGraphResource.m_nodes[0]; + ImNodes::BeginNode(0); + + // Header + ImNodes::BeginNodeTitleBar(); + ImGui::TextUnformatted("Graph Output"); + ImNodes::EndNodeTitleBar(); + + // Graph Outputs = Graph Node inputs + const std::vector& graph_outputs = + graph_output_node.m_socket_accessor->m_inputs; + for (size_t j = 0, ni = graph_outputs.size(); j < ni; j++) { + const Socket& socket = graph_outputs[j]; + ImNodes::BeginInputAttribute( + GenerateInputAttributeId(0, j), + sGetSocketShapeFromSocketType(socket.m_type), + ImColor(255, 255, 255, 255)); + ImGui::Text(socket.m_name.c_str()); + ImNodes::EndInputAttribute(); + } + + if (ImGui::Button("+Output")) { + static float bla = 0.f; + std::string socket_name = "Output"; + socket_name += + std::to_string(graph_output_node.m_socket_accessor->m_inputs.size()); + graph_output_node.m_socket_accessor->RegisterInput( + socket_name, + nullptr); + } + + ImVec2 node_pos = ImNodes::GetNodeGridSpacePos(0); + graph_output_node.m_position[0] = node_pos[0]; + graph_output_node.m_position[1] = node_pos[1]; + ImNodes::EndNode(); + + // Graph Input + AnimNodeResource& graph_input_node = gGraphResource.m_nodes[1]; + ImNodes::BeginNode(1); + + // Header + ImNodes::BeginNodeTitleBar(); + ImGui::TextUnformatted("Graph Input"); + ImNodes::EndNodeTitleBar(); + + // Graph Input = Graph Node outputs + const std::vector& graph_inputs = + graph_input_node.m_socket_accessor->m_outputs; + for (size_t j = 0, ni = graph_inputs.size(); j < ni; j++) { + const Socket& socket = graph_inputs[j]; + ImNodes::BeginOutputAttribute( + GenerateOutputAttributeId(1, j), + sGetSocketShapeFromSocketType(socket.m_type), + ImColor(255, 255, 255, 255)); + ImGui::Text(socket.m_name.c_str()); + ImNodes::EndInputAttribute(); + } + + if (ImGui::Button("+Input")) { + static float bla = 0.f; + std::string socket_name = "Input"; + socket_name += + std::to_string(graph_input_node.m_socket_accessor->m_outputs.size()); + graph_input_node.m_socket_accessor->RegisterOutput( + socket_name, + nullptr); + } + + node_pos = ImNodes::GetNodeGridSpacePos(1); + graph_input_node.m_position[0] = node_pos[0]; + graph_input_node.m_position[1] = node_pos[1]; + ImNodes::EndNode(); + } + + for (size_t i = 0, n = gGraphResource.m_nodes.size(); i < n; i++) { AnimNodeResource& node_resource = gGraphResource.m_nodes[i]; ImNodes::BeginNode(i); @@ -100,7 +193,10 @@ void AnimGraphEditorUpdate() { node_resource.m_socket_accessor->m_inputs; for (size_t j = 0, ni = node_inputs.size(); j < ni; j++) { const Socket& socket = node_inputs[j]; - ImNodes::BeginInputAttribute(GenerateInputAttributeId(i, j)); + ImNodes::BeginInputAttribute( + GenerateInputAttributeId(i, j), + sGetSocketShapeFromSocketType(socket.m_type), + ImColor(255, 255, 255, 255)); ImGui::Text(socket.m_name.c_str()); ImNodes::EndInputAttribute(); } @@ -110,7 +206,10 @@ void AnimGraphEditorUpdate() { node_resource.m_socket_accessor->m_outputs; for (size_t j = 0, ni = node_outputs.size(); j < ni; j++) { const Socket& socket = node_outputs[j]; - ImNodes::BeginOutputAttribute(GenerateOutputAttributeId(i, j)); + ImNodes::BeginOutputAttribute( + GenerateOutputAttributeId(i, j), + sGetSocketShapeFromSocketType(socket.m_type), + ImColor(255, 255, 255, 255)); ImGui::Text(socket.m_name.c_str()); ImNodes::EndInputAttribute(); } diff --git a/src/AnimGraphResource.cc b/src/AnimGraphResource.cc index 7e0b484..0a5c1b1 100644 --- a/src/AnimGraphResource.cc +++ b/src/AnimGraphResource.cc @@ -2,31 +2,88 @@ // Created by martin on 04.02.22. // -#include #include "AnimGraphResource.h" +#include + #include "3rdparty/json/json.hpp" using json = nlohmann::json; -std::string sPinTypeToStr (SocketType pin_type) { +// +// Socket <-> json +// +std::string sSocketTypeToStr(SocketType pin_type) { std::string result = "unknown"; switch (pin_type) { - case SocketType::SocketTypeFloat: result = "Float"; break; - case SocketType::SocketTypeAnimation: result = "AnimationData"; break; - default: result = "Unknown"; + case SocketType::SocketTypeBool: + result = "Bool"; + break; + case SocketType::SocketTypeAnimation: + result = "Animation"; + break; + case SocketType::SocketTypeFloat: + result = "Float"; + break; + case SocketType::SocketTypeVec3: + result = "Vec3"; + break; + case SocketType::SocketTypeQuat: + result = "Quat"; + break; + case SocketType::SocketTypeString: + result = "String"; + break; + default: + result = "Unknown"; } return result; } -json sSocketToJson(const Socket& pin) { +json sSocketToJson(const Socket& socket) { json result; - result["name"] = pin.m_name; - result["type"] = sPinTypeToStr(pin.m_type); + result["name"] = socket.m_name; + result["type"] = sSocketTypeToStr(socket.m_type); return result; } +Socket sJsonToSocket(const json& json_data) { + Socket result; + result.m_type = SocketType::SocketTypeUndefined; + result.m_value.ptr = nullptr; + result.m_name = json_data["name"]; + + std::string type_string = json_data["type"]; + + if (type_string == "Bool") { + result.m_type = SocketType::SocketTypeBool; + result.m_type_size = sizeof(bool); + } else if (type_string == "Animation") { + result.m_type = SocketType::SocketTypeAnimation; + result.m_type_size = sizeof(AnimData); + } else if (type_string == "Float") { + result.m_type = SocketType::SocketTypeFloat; + result.m_type_size = sizeof(float); + } else if (type_string == "Vec3") { + result.m_type = SocketType::SocketTypeVec3; + result.m_type_size = sizeof(Vec3); + } else if (type_string == "Quat") { + result.m_type = SocketType::SocketTypeQuat; + result.m_type_size = sizeof(Quat); + } else if (type_string == "String") { + result.m_type = SocketType::SocketTypeString; + result.m_type_size = sizeof(std::string); + } else { + std::cerr << "Invalid socket type '" << type_string << "'." << std::endl; + } + + return result; +} + +// +// AnimGraphNode <-> json +// json sAnimGraphNodeToJson(const AnimNodeResource& node) { json result; @@ -41,7 +98,6 @@ json sAnimGraphNodeToJson(const AnimNodeResource& node) { return result; } - AnimNodeResource sAnimGraphNodeFromJson(const json& json_node) { AnimNodeResource result; @@ -51,12 +107,15 @@ AnimNodeResource sAnimGraphNodeFromJson(const json& json_node) { result.m_position[1] = json_node["position"][1]; result.m_anim_node = AnimNodeFactory(result.m_type_name); - result.m_socket_accessor = AnimNodeAccessorFactory(result.m_type_name, result.m_anim_node); + result.m_socket_accessor = + AnimNodeAccessorFactory(result.m_type_name, result.m_anim_node); return result; } - +// +// AnimGraphConnection <-> Json +// json sAnimGraphConnectionToJson(const AnimGraphConnection& connection) { json result; @@ -71,7 +130,6 @@ json sAnimGraphConnectionToJson(const AnimGraphConnection& connection) { return result; } - AnimGraphConnection sAnimGraphConnectionFromJson(const json& json_node) { AnimGraphConnection connection; @@ -84,18 +142,25 @@ AnimGraphConnection sAnimGraphConnectionFromJson(const json& json_node) { return connection; } - void AnimGraphResource::clear() { m_name = ""; + for (size_t i = 0; i < m_nodes.size(); i++) { + delete m_nodes[i].m_socket_accessor; + m_nodes[i].m_socket_accessor = nullptr; + delete m_nodes[i].m_anim_node; + m_nodes[i].m_anim_node = nullptr; + } m_nodes.clear(); 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_connections.clear(); } - -bool AnimGraphResource::saveToFile (const char* filename) const { +bool AnimGraphResource::saveToFile(const char* filename) const { json result; result["name"] = m_name; @@ -111,15 +176,32 @@ bool AnimGraphResource::saveToFile (const char* filename) const { result["connections"][i] = sAnimGraphConnectionToJson(connection); } + // Graph inputs and outputs + { + const AnimNodeResource& graph_output_node = 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_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]); + } + } + std::ofstream output_file; - output_file.open (filename); + output_file.open(filename); output_file << to_string(result) << std::endl; output_file.close(); return true; } -bool AnimGraphResource::loadFromFile (const char* filename) { +bool AnimGraphResource::loadFromFile(const char* filename) { std::ifstream input_file; input_file.open(filename); std::stringstream buffer; @@ -127,21 +209,25 @@ bool AnimGraphResource::loadFromFile (const char* filename) { json json_data = json::parse(buffer.str(), nullptr, false); if (json_data.is_discarded()) { - std::cerr << "Error parsing json of file '" << filename << "'." << std::endl; + std::cerr << "Error parsing json of file '" << filename << "'." + << std::endl; } if (json_data["type"] != "AnimGraphResource") { - std::cerr << "Invalid json object. Expected type 'AnimGraphResource' but got '" << json_data["type"] << "'." << std::endl; + std::cerr + << "Invalid json object. Expected type 'AnimGraphResource' but got '" + << json_data["type"] << "'." << std::endl; } - m_nodes.clear(); - m_connections.clear(); + clear(); m_name = json_data["name"]; for (size_t i = 0; i < json_data["nodes"].size(); 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; + std::cerr + << "Invalid json object. Expected type 'AnimNodeResource' but got '" + << json_node["type"] << "'." << std::endl; return false; } @@ -152,14 +238,48 @@ bool AnimGraphResource::loadFromFile (const char* filename) { for (size_t i = 0; i < json_data["connections"].size(); i++) { const json& json_connection = json_data["connections"][i]; if (json_connection["type"] != "AnimGraphConnection") { - std::cerr << "Invalid json object. Expected type 'AnimGraphConnection' but got '" << json_connection["type"] << "'." << std::endl; + std::cerr << "Invalid json object. Expected type 'AnimGraphConnection' " + "but got '" + << json_connection["type"] << "'." << std::endl; return false; } - AnimGraphConnection connection = sAnimGraphConnectionFromJson(json_connection); + AnimGraphConnection connection = + sAnimGraphConnectionFromJson(json_connection); m_connections.push_back(connection); } + const json& graph_outputs = json_data["nodes"][0]["inputs"]; + for (size_t i = 0; i < graph_outputs.size(); i++) { + AnimNodeResource& graph_node = m_nodes[0]; + graph_node.m_socket_accessor->m_inputs.push_back( + sJsonToSocket(graph_outputs[i])); + } + + const json& graph_inputs = json_data["nodes"][1]["outputs"]; + for (size_t i = 0; i < graph_inputs.size(); i++) { + AnimNodeResource& graph_node = m_nodes[1]; + graph_node.m_socket_accessor->m_outputs.push_back( + sJsonToSocket(graph_inputs[i])); + } + return false; } +void* AnimGraph::GetOutput(const std::string& name) { + Socket* socket = m_socket_accessor->FindInputSocket(name); + if (socket == nullptr) { + return nullptr; + } + + return socket->m_value.ptr; +} + +void* AnimGraph::GetInput(const std::string& name) { + Socket* socket = m_socket_accessor->FindOutputSocket(name); + if (socket == nullptr) { + return nullptr; + } + + return *(socket->m_value.ptr_ptr); +} \ No newline at end of file diff --git a/src/AnimGraphResource.h b/src/AnimGraphResource.h index 53fad95..08be856 100644 --- a/src/AnimGraphResource.h +++ b/src/AnimGraphResource.h @@ -340,10 +340,66 @@ struct AnimGraphResource { bool saveToFile(const char* filename) const; bool loadFromFile(const char* filename); + AnimNodeResource& getGraphOutputNode() { + return m_nodes[0]; + } + + AnimNodeResource& getGraphInputNode() { + return m_nodes[1]; + } + size_t addNode(AnimNodeResource node_resource) { m_nodes.push_back(node_resource); return m_nodes.size() - 1; } + + bool connectSockets( + const AnimNodeResource& source_node, + const std::string& source_socket, + const AnimNodeResource& target_node, + const std::string& target_socket) { + size_t source_index = -1; + size_t target_index = -1; + for (size_t i = 0, n = m_nodes.size(); i < n; i++) { + if (&source_node == &m_nodes[i]) { + source_index = i; + } + + if (&target_node == &m_nodes[i]) { + target_index = i; + } + + if (source_index < m_nodes.size() && target_index < m_nodes.size()) { + break; + } + } + + if (source_index >= m_nodes.size() || target_index >= m_nodes.size()) { + std::cerr << "Cannot connect nodes: could not find nodes." << std::endl; + return false; + } + + size_t source_socket_index = + source_node.m_socket_accessor->GetOutputIndex(source_socket); + size_t target_socket_index = + target_node.m_socket_accessor->GetInputIndex(target_socket); + + if (source_socket_index >= source_node.m_socket_accessor->m_outputs.size() + || target_socket_index + >= target_node.m_socket_accessor->m_inputs.size()) { + std::cerr << "Cannot connect nodes: could not find sockets." << std::endl; + return false; + } + + AnimGraphConnection connection; + connection.m_source_node_index = source_index; + connection.m_source_socket_index = source_socket_index; + connection.m_target_node_index = target_index; + connection.m_target_socket_index = target_socket_index; + m_connections.push_back(connection); + + return true; + } }; static inline AnimNode* AnimNodeFactory(const std::string& name) { @@ -396,6 +452,17 @@ static inline AnimNodeResource AnimNodeResourceFactory( } struct AnimGraph { + ~AnimGraph() { + delete[] m_input_buffer; + delete[] m_output_buffer; + + for (int i = 0; i < m_nodes.size(); i++) { + delete m_nodes[i]; + } + + delete m_socket_accessor; + } + void UpdateTime(float dt); void Evaluate(); @@ -406,6 +473,12 @@ struct AnimGraph { char* m_input_buffer = nullptr; char* m_output_buffer = nullptr; + std::vector& GetGraphOutputs() { return m_socket_accessor->m_inputs; } + std::vector& GetGraphInputs() { return m_socket_accessor->m_outputs; } + + void* GetOutput(const std::string& name); + void* GetInput(const std::string& name); + AnimNode* getAnimNode(const char* name) { for (size_t i = 0; i < m_nodes.size(); i++) { if (m_nodes[i]->m_name == name) { @@ -441,23 +514,47 @@ struct AnimGraph { } // Prepare graph inputs - const AnimNodeResource& graph_node = resource.m_nodes[0]; - result.m_socket_accessor = AnimNodeAccessorFactory("BlendTree", result.m_nodes[0]); + result.m_socket_accessor = + AnimNodeAccessorFactory("BlendTree", result.m_nodes[0]); + result.m_socket_accessor->m_outputs = + resource.m_nodes[1].m_socket_accessor->m_outputs; + result.m_socket_accessor->m_inputs = + resource.m_nodes[0].m_socket_accessor->m_inputs; + // inputs int input_block_size = 0; - const std::vector& graph_inputs = - graph_node.m_socket_accessor->m_inputs; + std::vector& graph_inputs = result.GetGraphInputs(); for (int i = 0; i < graph_inputs.size(); i++) { - input_block_size += graph_inputs[i].m_type_size; + input_block_size += sizeof(void*); } result.m_input_buffer = new char[input_block_size]; memset(result.m_input_buffer, 0, input_block_size); + int input_block_offset = 0; for (int i = 0; i < graph_inputs.size(); i++) { if (graph_inputs[i].m_type == SocketType::SocketTypeAnimation) { - result.m_socket_accessor->RegisterInput(graph_inputs[i].m_name, static_cast( (void*) &result.m_input_buffer[input_block_offset])); } - input_block_offset += graph_inputs[i].m_type_size; + graph_inputs[i].m_value.ptr = + (void*)&result.m_input_buffer[input_block_offset]; + input_block_offset += sizeof(void*); + } + + // outputs + int output_block_size = 0; + std::vector& graph_outputs = result.GetGraphOutputs(); + for (int i = 0; i < graph_outputs.size(); i++) { + output_block_size += graph_outputs[i].m_type_size; + } + result.m_output_buffer = new char[output_block_size]; + memset(result.m_output_buffer, 0, output_block_size); + + int output_block_offset = 0; + for (int i = 0; i < graph_outputs.size(); i++) { + if (graph_outputs[i].m_type == SocketType::SocketTypeAnimation) { + } + graph_outputs[i].m_value.ptr = + (void*)&result.m_output_buffer[output_block_offset]; + output_block_offset += graph_outputs[i].m_type_size; } // connect the nodes @@ -478,8 +575,12 @@ struct AnimGraph { source_node = result.m_nodes[connection.m_source_node_index]; source_node_name = source_node->m_name; source_node_type = source_node->m_node_type_name; - source_node_accessor = - AnimNodeAccessorFactory(source_node_type, source_node); + if (connection.m_source_node_index == 1) { + source_node_accessor = result.m_socket_accessor; + } else { + source_node_accessor = + AnimNodeAccessorFactory(source_node_type, source_node); + } } if (connection.m_target_node_index >= 0) { @@ -536,6 +637,14 @@ struct AnimGraph { size_t target_node_index = result.getAnimNodeIndex(target_node); result.m_node_inputs[target_node_index].push_back(source_node); + + if (target_node_accessor != result.m_socket_accessor) { + delete target_node_accessor; + } + + if (source_node_accessor != result.m_socket_accessor) { + delete source_node_accessor; + } } return result; diff --git a/tests/AnimGraphResourceTests.cc b/tests/AnimGraphResourceTests.cc index 1e5028a..a2d5614 100644 --- a/tests/AnimGraphResourceTests.cc +++ b/tests/AnimGraphResourceTests.cc @@ -70,25 +70,28 @@ TEST_CASE("BasicGraph", "[AnimGraphResource]") { << std::endl; } - REQUIRE(graph.m_nodes.size() == 4); + REQUIRE(graph.m_nodes.size() == 5); REQUIRE(graph.m_nodes[0]->m_node_type_name == "BlendTree"); - REQUIRE(graph.m_nodes[1]->m_node_type_name == "AnimSampler"); + REQUIRE(graph.m_nodes[1]->m_node_type_name == "BlendTree"); REQUIRE(graph.m_nodes[2]->m_node_type_name == "AnimSampler"); - REQUIRE(graph.m_nodes[3]->m_node_type_name == "Blend2"); + REQUIRE(graph.m_nodes[3]->m_node_type_name == "AnimSampler"); + REQUIRE(graph.m_nodes[4]->m_node_type_name == "Blend2"); // connections within the graph AnimSamplerNode* anim_sampler_instance0 = - dynamic_cast(graph.m_nodes[1]); - AnimSamplerNode* anim_sampler_instance1 = dynamic_cast(graph.m_nodes[2]); - Blend2Node* blend2_instance = dynamic_cast(graph.m_nodes[3]); + AnimSamplerNode* anim_sampler_instance1 = + dynamic_cast(graph.m_nodes[3]); + Blend2Node* blend2_instance = dynamic_cast(graph.m_nodes[4]); CHECK(anim_sampler_instance0->m_output == &blend2_instance->m_input0); CHECK(anim_sampler_instance1->m_output == &blend2_instance->m_input1); // connections from graph to the graph node CHECK(graph.m_socket_accessor->m_inputs.size() == 1); CHECK(graph.m_socket_accessor->FindInputSocket("GraphOutput")); - CHECK(reinterpret_cast(blend2_instance->m_output) == graph.m_input_buffer); + CHECK( + reinterpret_cast(blend2_instance->m_output) + == graph.GetOutput("GraphOutput")); // check node input dependencies size_t anim_sampler_index0 = graph.getAnimNodeIndex(anim_sampler_instance0); @@ -119,4 +122,215 @@ TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") { SplitOutputAttributeId(attribute_id, &parsed_node_id, &parsed_output_index); CHECK(node_id == parsed_node_id); CHECK(output_index == parsed_output_index); +} + +TEST_CASE("ResourceSaveLoadGraphInputs", "[AnimGraphResource]") { + AnimGraphResource graph_resource_origin; + + graph_resource_origin.clear(); + graph_resource_origin.m_name = "TestInputOutputGraph"; + + AnimNodeResource& graph_output_node = graph_resource_origin.m_nodes[0]; + graph_output_node.m_socket_accessor->RegisterInput( + "GraphOutput", + nullptr); + graph_output_node.m_socket_accessor->RegisterInput( + "SomeFloatOutput", + nullptr); + + AnimNodeResource& graph_input_node = graph_resource_origin.m_nodes[1]; + graph_input_node.m_socket_accessor->RegisterOutput( + "GraphAnimInput", + nullptr); + graph_input_node.m_socket_accessor->RegisterOutput( + "GraphFloatInput", + nullptr); + graph_input_node.m_socket_accessor->RegisterOutput( + "GraphBoolInput", + nullptr); + + WHEN("Saving and loading graph resource") { + const char* filename = "ResourceSaveLoadGraphInputs.json"; + graph_resource_origin.saveToFile(filename); + + AnimGraphResource graph_resource_loaded; + graph_resource_loaded.loadFromFile(filename); + + const AnimNodeResource& graph_loaded_output_node = + graph_resource_loaded.m_nodes[0]; + const AnimNodeResource& graph_loaded_input_node = + graph_resource_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()); + + REQUIRE( + 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->FindOutputSocket( + "GraphAnimInput") + != nullptr); + REQUIRE( + graph_loaded_input_node.m_socket_accessor->FindOutputSocket( + "GraphFloatInput") + != nullptr); + REQUIRE( + graph_loaded_input_node.m_socket_accessor->FindOutputSocket( + "GraphBoolInput") + != nullptr); + + REQUIRE( + graph_loaded_output_node.m_socket_accessor->FindInputSocket( + "GraphOutput") + != nullptr); + REQUIRE( + graph_loaded_output_node.m_socket_accessor->FindInputSocket( + "SomeFloatOutput") + != nullptr); + } + + WHEN("Instantiating an AnimGraph") { + AnimGraph anim_graph = + AnimGraph::createFromResource(graph_resource_loaded); + REQUIRE( + anim_graph.GetOutput("GraphOutput") == anim_graph.m_output_buffer); + REQUIRE( + anim_graph.GetOutput("SomeFloatOutput") + == anim_graph.m_output_buffer + sizeof(AnimData)); + + REQUIRE(anim_graph.GetInput("GraphAnimInput") == nullptr); + REQUIRE(anim_graph.GetInput("GraphFloatInput") == nullptr); + REQUIRE(anim_graph.GetInput("GraphBoolInput") == nullptr); + } + } + + WHEN("Connecting input to output and instantiating the graph") { + AnimNodeResource& graph_output_node = graph_resource_origin.m_nodes[0]; + AnimNodeResource& graph_input_node = graph_resource_origin.m_nodes[1]; + + REQUIRE(graph_resource_origin.connectSockets( + graph_input_node, + "GraphAnimInput", + graph_output_node, + "GraphOutput")); + + AnimGraph anim_graph = AnimGraph::createFromResource(graph_resource_origin); + + void* graph_anim_input_ptr = anim_graph.GetInput("GraphAnimInput"); + void* graph_output_ptr = anim_graph.GetOutput("GraphOutput"); + + REQUIRE(graph_anim_input_ptr == graph_output_ptr); + REQUIRE(graph_output_ptr == anim_graph.m_output_buffer); + + REQUIRE( + anim_graph.GetInput("GraphAnimInput") + == anim_graph.GetOutput("GraphOutput")); + } +} + +TEST_CASE("GraphInputOutputConnectivity", "[AnimGraphResource]") { + AnimGraphResource graph_resource; + + graph_resource.clear(); + graph_resource.m_name = "TestGraphInputOutputConnectivity"; + + AnimNodeResource& graph_output_node = graph_resource.m_nodes[0]; + graph_output_node.m_socket_accessor->RegisterInput( + "GraphFloatOutput", + nullptr); + graph_output_node.m_socket_accessor->RegisterInput( + "GraphAnimOutput", + nullptr); + + AnimNodeResource& graph_input_node = graph_resource.m_nodes[1]; + graph_input_node.m_socket_accessor->RegisterOutput( + "GraphFloatInput", + nullptr); + graph_input_node.m_socket_accessor->RegisterOutput( + "GraphAnimInput0", + nullptr); + graph_input_node.m_socket_accessor->RegisterOutput( + "GraphAnimInput1", + nullptr); + + WHEN("Connecting float input with float output") { + REQUIRE(graph_resource.connectSockets( + graph_resource.getGraphInputNode(), + "GraphFloatInput", + graph_resource.getGraphOutputNode(), + "GraphFloatOutput")); + + AnimGraph anim_graph = AnimGraph::createFromResource(graph_resource); + + THEN ("Writing to the input pointer changes the value of the output.") { + float* float_input_ptr = (float*)anim_graph.GetInput("GraphFloatInput"); + REQUIRE(float_input_ptr != nullptr); + *float_input_ptr = 23.123f; + + float* float_output_ptr = + (float*)anim_graph.GetOutput("GraphFloatOutput"); + REQUIRE(float_output_ptr != nullptr); + CHECK(*float_output_ptr == Approx(23.123f)); + } + } + + WHEN("Connecting adding a Blend2 node") { + size_t blend2_node_index = graph_resource.addNode(AnimNodeResourceFactory("Blend2")); + AnimNodeResource& blend2_node_resource = graph_resource.m_nodes[blend2_node_index]; + + REQUIRE(graph_resource.connectSockets( + graph_resource.getGraphInputNode(), + "GraphFloatInput", + blend2_node_resource, + "Weight")); + + THEN ("Connected float input points to the blend weight.") { + AnimGraph anim_graph = AnimGraph::createFromResource(graph_resource); + Blend2Node* blend2_node = dynamic_cast(anim_graph.m_nodes[blend2_node_index]); + + REQUIRE (*anim_graph.m_socket_accessor->m_outputs[0].m_value.ptr_ptr == &blend2_node->m_blend_weight); + float* float_input_ptr = (float*)anim_graph.GetInput("GraphFloatInput"); + REQUIRE (float_input_ptr == &blend2_node->m_blend_weight); + } + + + WHEN ("Connecting AnimData inputs to blend2 node and blend2 output to graph output.") { + REQUIRE(graph_resource.connectSockets( + graph_resource.getGraphInputNode(), + "GraphAnimInput0", + blend2_node_resource, + "Input0")); + + REQUIRE(graph_resource.connectSockets( + graph_resource.getGraphInputNode(), + "GraphAnimInput1", + blend2_node_resource, + "Input1")); + + REQUIRE(graph_resource.connectSockets( + blend2_node_resource, + "Output", + graph_resource.getGraphOutputNode(), + "GraphAnimOutput" + )); + + THEN("AnimData from output gets blended and result is written to Output.") { + AnimGraph anim_graph = AnimGraph::createFromResource(graph_resource); + Blend2Node* blend2_node = dynamic_cast(anim_graph.m_nodes[blend2_node_index]); + + AnimData* graph_input0 = (AnimData*) anim_graph.GetInput("GraphAnimInput0"); + REQUIRE(graph_input0 == &blend2_node->m_input0); + + AnimData* graph_input1 = (AnimData*) anim_graph.GetInput("GraphAnimInput1"); + REQUIRE(graph_input1 == &blend2_node->m_input1); + + AnimData* graph_output = (AnimData*) anim_graph.GetOutput("GraphAnimOutput"); + REQUIRE(graph_output == blend2_node->m_output); + } + } + } } \ No newline at end of file