From 5d8c1e289b01da354b884d04c7efef219646f477 Mon Sep 17 00:00:00 2001 From: Martin Felis Date: Sun, 3 Apr 2022 21:05:11 +0200 Subject: [PATCH] Initial graph evaluations, added Float to Vec3 Node, minor editor tweaks. --- src/AnimGraph/AnimGraph.cc | 106 ++++++++++++++++++++---- src/AnimGraph/AnimGraph.h | 14 ++++ src/AnimGraph/AnimGraphEditor.cc | 21 +++++ src/AnimGraph/AnimGraphNodes.h | 39 +++++++++ src/AnimGraph/AnimGraphResource.h | 17 ++++ tests/AnimGraphResourceTests.cc | 131 ++++++++++++++++++++---------- 6 files changed, 269 insertions(+), 59 deletions(-) diff --git a/src/AnimGraph/AnimGraph.cc b/src/AnimGraph/AnimGraph.cc index 6d5e110..0fc8ed7 100644 --- a/src/AnimGraph/AnimGraph.cc +++ b/src/AnimGraph/AnimGraph.cc @@ -4,6 +4,8 @@ #include "AnimGraph.h" +#include + void AnimGraph::updateOrderedNodes() { m_eval_ordered_nodes.clear(); updateOrderedNodesRecursive(0); @@ -14,11 +16,19 @@ void AnimGraph::updateOrderedNodesRecursive(int node_index) { const std::vector node_input_connections = m_node_input_connections[node_index]; for (size_t i = 0, n = node_input_connections.size(); i < n; i++) { - int input_node_index = getAnimNodeIndex(node_input_connections[i].m_source_node); + int input_node_index = + getAnimNodeIndex(node_input_connections[i].m_source_node); + + if (input_node_index == 1) { + continue; + } + updateOrderedNodesRecursive(input_node_index); } - m_eval_ordered_nodes.push_back(node); + if (node_index != 0) { + m_eval_ordered_nodes.push_back(node); + } } void AnimGraph::markActiveNodes() { @@ -26,7 +36,8 @@ void AnimGraph::markActiveNodes() { m_nodes[i]->m_state = AnimNodeEvalState::Deactivated; } - const std::vector& graph_output_inputs = m_node_input_connections[0]; + const std::vector& graph_output_inputs = + m_node_input_connections[0]; for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) { const AnimGraphConnection& graph_input = graph_output_inputs[i]; AnimNode* node = graph_input.m_source_node; @@ -42,10 +53,14 @@ void AnimGraph::markActiveNodes() { node->MarkActiveInputs(m_node_input_connections[node_index]); // Non-animation data inputs are always active. - for (size_t j = 0, nj = m_node_input_connections[node_index].size(); j < nj; j++) { - const AnimGraphConnection& input = m_node_input_connections[node_index][j]; + for (size_t j = 0, nj = m_node_input_connections[node_index].size(); + j < nj; + j++) { + const AnimGraphConnection& input = + m_node_input_connections[node_index][j]; if (input.m_source_node != nullptr - && input.m_target_socket.m_type != SocketType::SocketTypeAnimation) { + && input.m_target_socket.m_type + != SocketType::SocketTypeAnimation) { input.m_source_node->m_state = AnimNodeEvalState::Activated; } } @@ -102,6 +117,42 @@ void AnimGraph::finishNodeEval(size_t node_index) { } } + +void AnimGraph::evalInputNode() { + for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) { + AnimGraphConnection& graph_input_connection = + m_node_output_connections[1][i]; + + if (graph_input_connection.m_source_socket.m_type + != SocketType::SocketTypeAnimation) { + memcpy( + *graph_input_connection.m_target_socket.m_value.ptr_ptr, + graph_input_connection.m_source_socket.m_value.ptr, + sizeof(void*)); + printf("bla"); + } else { + // TODO: how to deal with anim data outputs? + } + } +} + +void AnimGraph::evalOutputNode() { + for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) { + AnimGraphConnection& graph_output_connection = + m_node_input_connections[0][i]; + + if (graph_output_connection.m_source_socket.m_type + != SocketType::SocketTypeAnimation) { + memcpy( + graph_output_connection.m_target_socket.m_value.ptr, + graph_output_connection.m_source_socket.m_value.ptr, + graph_output_connection.m_target_socket.m_type_size); + } else { + // TODO: how to deal with anim data outputs? + } + } +} + void AnimGraph::evalSyncTracks() { for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) { AnimNode* node = m_eval_ordered_nodes[i]; @@ -115,22 +166,24 @@ void AnimGraph::evalSyncTracks() { } void AnimGraph::updateTime(float dt) { - const std::vector graph_output_inputs = m_node_input_connections[0]; + const std::vector graph_output_inputs = + m_node_input_connections[0]; for (size_t i = 0, n = graph_output_inputs.size(); i < n; i++) { - AnimNode* node = m_eval_ordered_nodes[i]; + AnimNode* node = graph_output_inputs[i].m_source_node; if (node != nullptr) { node->UpdateTime(node->m_time_now, node->m_time_now + dt); } } - for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) { + for (size_t i = m_eval_ordered_nodes.size() - 1; i > 0; --i) { AnimNode* node = m_eval_ordered_nodes[i]; if (node->m_state != AnimNodeEvalState::TimeUpdated) { continue; } int node_index = node->m_index; - const std::vector node_input_connections = m_node_input_connections[node_index]; + const std::vector node_input_connections = + m_node_input_connections[node_index]; float node_time_now = node->m_time_now; float node_time_last = node->m_time_last; @@ -139,7 +192,8 @@ void AnimGraph::updateTime(float dt) { // Only propagate time updates via animation sockets. if (input_node != nullptr - && node_input_connections[i].m_target_socket.m_type == SocketType::SocketTypeAnimation + && node_input_connections[i].m_target_socket.m_type + == SocketType::SocketTypeAnimation && input_node->m_state == AnimNodeEvalState::Activated) { input_node->UpdateTime(node_time_last, node_time_now); } @@ -156,7 +210,7 @@ void AnimGraph::evaluate() { eval_stack[i] = &eval_buffers[i]; } - for (size_t i = m_eval_ordered_nodes.size() - 1; i >= 0; i--) { + for (int i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) { AnimNode* node = m_eval_ordered_nodes[i]; if (node->m_state == AnimNodeEvalState::Deactivated) { @@ -172,11 +226,10 @@ void AnimGraph::evaluate() { } Socket* AnimGraph::getInputSocket(const std::string& name) { - Socket* socket = nullptr; for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) { AnimGraphConnection& connection = m_node_output_connections[1][i]; - if (connection.m_target_socket.m_name == name) { - return &connection.m_target_socket; + if (connection.m_source_socket.m_name == name) { + return &connection.m_source_socket; } } @@ -184,7 +237,6 @@ Socket* AnimGraph::getInputSocket(const std::string& name) { } Socket* AnimGraph::getOutputSocket(const std::string& name) { - Socket* socket = nullptr; for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) { AnimGraphConnection& connection = m_node_input_connections[0][i]; if (connection.m_target_socket.m_name == name) { @@ -192,5 +244,27 @@ Socket* AnimGraph::getOutputSocket(const std::string& name) { } } + return nullptr; +} + +const Socket* AnimGraph::getInputSocket(const std::string& name) const { + for (size_t i = 0, n = m_node_output_connections[1].size(); i < n; i++) { + const AnimGraphConnection& connection = m_node_output_connections[1][i]; + if (connection.m_source_socket.m_name == name) { + return &connection.m_source_socket; + } + } + + return nullptr; +} + +const Socket* AnimGraph::getOutputSocket(const std::string& name) const { + for (size_t i = 0, n = m_node_input_connections[0].size(); i < n; i++) { + const AnimGraphConnection& connection = m_node_input_connections[0][i]; + if (connection.m_target_socket.m_name == name) { + return &connection.m_target_socket; + } + } + return nullptr; } \ No newline at end of file diff --git a/src/AnimGraph/AnimGraph.h b/src/AnimGraph/AnimGraph.h index 8a149ac..f433914 100644 --- a/src/AnimGraph/AnimGraph.h +++ b/src/AnimGraph/AnimGraph.h @@ -74,8 +74,10 @@ struct AnimGraph { } + void evalInputNode(); void prepareNodeEval(size_t node_index); void finishNodeEval(size_t node_index); + void evalOutputNode(); void evalSyncTracks(); void updateTime(float dt); @@ -91,6 +93,18 @@ struct AnimGraph { Socket* getInputSocket(const std::string& name); Socket* getOutputSocket(const std::string& name); + const Socket* getInputSocket(const std::string& name) const; + const Socket* getOutputSocket(const std::string& name) const; + + void* getInputPtr(const std::string& name) const { + const Socket* input_socket = getInputSocket(name); + if (input_socket != nullptr) { + return input_socket->m_value.ptr; + } + + return nullptr; + } + int getNodeEvalOrderIndex(const AnimNode* node) { for (size_t i = 0, n = m_eval_ordered_nodes.size(); i < n; i++) { if (m_eval_ordered_nodes[i] == node) { diff --git a/src/AnimGraph/AnimGraphEditor.cc b/src/AnimGraph/AnimGraphEditor.cc index c4fb19c..7ce53b0 100644 --- a/src/AnimGraph/AnimGraphEditor.cc +++ b/src/AnimGraph/AnimGraphEditor.cc @@ -217,6 +217,14 @@ void AnimGraphEditorUpdate() { node_type_name = "SpeedScale"; } + if (ImGui::MenuItem("MathAddNode")) { + node_type_name = "MathAddNode"; + } + + if (ImGui::MenuItem("MathFloatToVec3Node")) { + node_type_name = "MathFloatToVec3Node"; + } + if (node_type_name != "") { AnimNodeResource node_resource = AnimNodeResourceFactory(node_type_name); @@ -235,6 +243,7 @@ void AnimGraphEditorUpdate() { AnimNodeResource& node_resource = graph_resource.m_nodes[i]; ImNodes::BeginNode(i); + ImGui::PushItemWidth(110.0f); // Header ImNodes::BeginNodeTitleBar(); @@ -264,6 +273,16 @@ void AnimGraphEditorUpdate() { socket_color); ImGui::TextUnformatted(socket.m_name.c_str()); + bool socket_connected = graph_resource.isSocketConnected(node_resource, socket.m_name); + if (!socket_connected && + (socket.m_type == SocketType::SocketTypeFloat)) { + ImGui::SameLine(); + float socket_value = 0.f; + ImGui::PushItemWidth(100.0f - ImGui::CalcTextSize(socket.m_name.c_str()).x); + ImGui::DragFloat("##hidelabel", &socket_value, 0.01f); + ImGui::PopItemWidth(); + } + ImNodes::PushAttributeFlag( ImNodesAttributeFlags_EnableLinkDetachWithDragClick); ImNodes::EndInputAttribute(); @@ -316,6 +335,8 @@ void AnimGraphEditorUpdate() { ImVec2 node_pos = ImNodes::GetNodeGridSpacePos(i); node_resource.m_position[0] = node_pos[0]; node_resource.m_position[1] = node_pos[1]; + ImGui::PopItemWidth(); + ImNodes::EndNode(); // Ensure flags such as SocketFlagAffectsTime are properly set. diff --git a/src/AnimGraph/AnimGraphNodes.h b/src/AnimGraph/AnimGraphNodes.h index 2d4f333..748d8d0 100644 --- a/src/AnimGraph/AnimGraphNodes.h +++ b/src/AnimGraph/AnimGraphNodes.h @@ -240,6 +240,37 @@ struct NodeSocketAccessor : public NodeSocketAccessorBase { }; +// +// MathFloatToVec3Node +// +struct MathFloatToVec3Node : public AnimNode { + float* i_input0 = nullptr; + float* i_input1 = nullptr; + float* i_input2 = nullptr; + Vec3 o_output = {0.f, 0.f, 0.f}; + + void Evaluate() override { + assert (i_input0 != nullptr); + assert (i_input1 != nullptr); + assert (i_input2 != nullptr); + + o_output[0] = *i_input0; + o_output[1] = *i_input1; + o_output[2] = *i_input2; + } +}; + +template <> +struct NodeSocketAccessor : public NodeSocketAccessorBase { + NodeSocketAccessor(AnimNode* node_) { + MathFloatToVec3Node* node = dynamic_cast(node_); + RegisterInput("Input0", &node->i_input0); + RegisterInput("Input1", &node->i_input1); + RegisterInput("Input2", &node->i_input2); + RegisterOutput("Output", &node->o_output); + } +}; + static inline AnimNode* AnimNodeFactory(const std::string& name) { AnimNode* result; if (name == "Blend2") { @@ -250,6 +281,10 @@ static inline AnimNode* AnimNodeFactory(const std::string& name) { result = new AnimSamplerNode; } else if (name == "BlendTree") { result = new BlendTreeNode; + } else if (name == "MathAddNode") { + result = new MathAddNode; + } else if (name == "MathFloatToVec3Node") { + result = new MathFloatToVec3Node; } if (result != nullptr) { @@ -272,6 +307,10 @@ static inline NodeSocketAccessorBase* AnimNodeAccessorFactory( return new NodeSocketAccessor(node); } else if (node_type_name == "BlendTree") { return new NodeSocketAccessor(node); + } else if (node_type_name == "MathAddNode") { + return new NodeSocketAccessor(node); + } else if (node_type_name == "MathFloatToVec3Node") { + return new NodeSocketAccessor(node); } else { std::cerr << "Invalid node type name " << node_type_name << "." << std::endl; diff --git a/src/AnimGraph/AnimGraphResource.h b/src/AnimGraph/AnimGraphResource.h index 7838eb7..dfa7f8d 100644 --- a/src/AnimGraph/AnimGraphResource.h +++ b/src/AnimGraph/AnimGraphResource.h @@ -123,6 +123,23 @@ struct AnimGraphResource { return true; } + bool isSocketConnected( + const AnimNodeResource& node, + const std::string& socket_name) { + int node_index = getNodeIndex(node); + for (size_t i = 0, n = m_connections.size(); i < n; i++) { + const AnimGraphConnectionResource& connection = m_connections[i]; + if ((connection.source_node_index == node_index + && connection.source_socket_name == socket_name) + || ((connection.target_node_index == node_index) + && connection.target_socket_name == socket_name)) { + return true; + } + } + + return false; + } + AnimGraph createInstance() const; void createRuntimeNodeInstances(AnimGraph& instance) const; void prepareGraphIOData(AnimGraph& instance) const; diff --git a/tests/AnimGraphResourceTests.cc b/tests/AnimGraphResourceTests.cc index 5f051e9..82a82f8 100644 --- a/tests/AnimGraphResourceTests.cc +++ b/tests/AnimGraphResourceTests.cc @@ -2,10 +2,9 @@ // Created by martin on 04.02.22. // -#include "AnimGraph/AnimGraphResource.h" #include "AnimGraph/AnimGraph.h" #include "AnimGraph/AnimGraphEditor.h" - +#include "AnimGraph/AnimGraphResource.h" #include "catch.hpp" TEST_CASE("BasicGraph", "[AnimGraphResource]") { @@ -81,14 +80,22 @@ TEST_CASE("BasicGraph", "[AnimGraphResource]") { size_t blend_index = blend2_instance->m_index; REQUIRE(graph.m_node_input_connections[blend_index].size() == 2); - CHECK(graph.m_node_input_connections[blend_index][0].m_source_node == anim_sampler_walk); - CHECK(graph.m_node_input_connections[blend_index][1].m_source_node == anim_sampler_run); + CHECK( + graph.m_node_input_connections[blend_index][0].m_source_node + == anim_sampler_walk); + CHECK( + graph.m_node_input_connections[blend_index][1].m_source_node + == anim_sampler_run); REQUIRE(graph.m_node_output_connections[anim_sampler_index0].size() == 1); - CHECK(graph.m_node_output_connections[anim_sampler_index0][0].m_target_node == blend2_instance); + CHECK( + graph.m_node_output_connections[anim_sampler_index0][0].m_target_node + == blend2_instance); REQUIRE(graph.m_node_output_connections[anim_sampler_index1].size() == 1); - CHECK(graph.m_node_output_connections[anim_sampler_index1][0].m_target_node == blend2_instance); + CHECK( + graph.m_node_output_connections[anim_sampler_index1][0].m_target_node + == blend2_instance); // Emulate evaluation CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 5); @@ -108,16 +115,16 @@ TEST_CASE("BasicGraph", "[AnimGraphResource]") { CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 4); graph.prepareNodeEval(0); - Socket* graph_output_socket = graph.getOutputSocket("GraphOutput"); + const Socket* graph_output_socket = graph.getOutputSocket("GraphOutput"); CHECK(blend2_instance->o_output == (*graph_output_socket->m_value.ptr_ptr)); - AnimData* graph_output = static_cast(*graph_output_socket->m_value.ptr_ptr); + AnimData* graph_output = + static_cast(*graph_output_socket->m_value.ptr_ptr); graph.finishNodeEval(0); CHECK(blend2_instance->o_output == nullptr); CHECK(graph_output == (*graph_output_socket->m_value.ptr_ptr)); CHECK(graph.m_anim_data_work_buffer.m_available_data.size() == 5); } -/* TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") { int node_id = 3321; int input_index = 221; @@ -137,30 +144,62 @@ TEST_CASE("InputAttributeConversion", "[AnimGraphResource]") { CHECK(output_index == parsed_output_index); } -TEST_CASE("ResourceSaveLoadGraphInputs", "[AnimGraphResource]") { +TEST_CASE("ResourceSaveLoadMathGraphInputs", "[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); + size_t float_to_vec3_node_index = + graph_resource_origin.addNode(AnimNodeResourceFactory("MathFloatToVec3Node")); + + AnimNodeResource& graph_output_node = + graph_resource_origin.getGraphOutputNode(); graph_output_node.m_socket_accessor->RegisterInput( - "SomeFloatOutput", + "GraphFloatOutput", + nullptr); + graph_output_node.m_socket_accessor->RegisterInput( + "GraphVec3Output", nullptr); - AnimNodeResource& graph_input_node = graph_resource_origin.m_nodes[1]; - graph_input_node.m_socket_accessor->RegisterOutput( - "GraphAnimInput", - nullptr); + AnimNodeResource& graph_input_node = + graph_resource_origin.getGraphInputNode(); graph_input_node.m_socket_accessor->RegisterOutput( "GraphFloatInput", nullptr); - graph_input_node.m_socket_accessor->RegisterOutput( - "GraphBoolInput", - nullptr); + + // Prepare graph inputs and outputs + AnimNodeResource& float_to_vec3_node = + graph_resource_origin.m_nodes[float_to_vec3_node_index]; + + REQUIRE(graph_resource_origin.connectSockets( + graph_input_node, + "GraphFloatInput", + graph_output_node, + "GraphFloatOutput")); + + REQUIRE(graph_resource_origin.connectSockets( + graph_input_node, + "GraphFloatInput", + float_to_vec3_node, + "Input0")); + REQUIRE(graph_resource_origin.connectSockets( + graph_input_node, + "GraphFloatInput", + float_to_vec3_node, + "Input1")); + REQUIRE(graph_resource_origin.connectSockets( + graph_input_node, + "GraphFloatInput", + float_to_vec3_node, + "Input2")); + + REQUIRE(graph_resource_origin.connectSockets( + float_to_vec3_node, + "Output", + graph_output_node, + "GraphVec3Output")); + WHEN("Saving and loading graph resource") { const char* filename = "ResourceSaveLoadGraphInputs.json"; @@ -183,43 +222,49 @@ TEST_CASE("ResourceSaveLoadGraphInputs", "[AnimGraphResource]") { 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") + "GraphFloatOutput") != nullptr); REQUIRE( graph_loaded_output_node.m_socket_accessor->FindInputSocket( - "SomeFloatOutput") + "GraphVec3Output") != nullptr); - } - WHEN("Instantiating an AnimGraph") { - AnimGraph anim_graph = graph_resource_loaded.createInstance(); + WHEN("Instantiating an AnimGraph") { + AnimGraph anim_graph = graph_resource_loaded.createInstance(); - 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.getInputSocket("GraphFloatInput") != nullptr); + REQUIRE(anim_graph.getInputPtr("GraphFloatInput") == anim_graph.m_input_buffer); - REQUIRE(anim_graph.getInput("GraphAnimInput") == nullptr); - REQUIRE(anim_graph.getInput("GraphFloatInput") == nullptr); - REQUIRE(anim_graph.getInput("GraphBoolInput") == nullptr); + float* graph_float_input = nullptr; + graph_float_input = static_cast(anim_graph.getInputPtr("GraphFloatInput")); + + *graph_float_input = 123.456f; + + anim_graph.updateTime(0.f); + anim_graph.evaluate(); + + anim_graph.evalOutputNode(); + + Socket* float_output_socket = anim_graph.getOutputSocket("GraphFloatOutput"); + Socket* vec3_output_socket = anim_graph.getOutputSocket("GraphVec3Output"); + Vec3& vec3_output = *static_cast(vec3_output_socket->m_value.ptr); + + CHECK(vec3_output[0] == *graph_float_input); + CHECK(vec3_output[1] == *graph_float_input); + CHECK(vec3_output[2] == *graph_float_input); + } } } +} + +/* WHEN("Connecting input to output and instantiating the graph") { AnimNodeResource& graph_output_node = graph_resource_origin.m_nodes[0];