Initial graph evaluations, added Float to Vec3 Node, minor editor tweaks.

AnimGraphEditor
Martin Felis 2022-04-03 21:05:11 +02:00
parent 08ae84fcb4
commit 5d8c1e289b
6 changed files with 269 additions and 59 deletions

View File

@ -4,6 +4,8 @@
#include "AnimGraph.h"
#include <cstring>
void AnimGraph::updateOrderedNodes() {
m_eval_ordered_nodes.clear();
updateOrderedNodesRecursive(0);
@ -14,19 +16,28 @@ void AnimGraph::updateOrderedNodesRecursive(int node_index) {
const std::vector<AnimGraphConnection> 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);
}
if (node_index != 0) {
m_eval_ordered_nodes.push_back(node);
}
}
void AnimGraph::markActiveNodes() {
for (size_t i = 0, n = m_nodes.size(); i < n; i++) {
m_nodes[i]->m_state = AnimNodeEvalState::Deactivated;
}
const std::vector<AnimGraphConnection>& graph_output_inputs = m_node_input_connections[0];
const std::vector<AnimGraphConnection>& 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<AnimGraphConnection> graph_output_inputs = m_node_input_connections[0];
const std::vector<AnimGraphConnection> 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<AnimGraphConnection> node_input_connections = m_node_input_connections[node_index];
const std::vector<AnimGraphConnection> 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) {
@ -194,3 +246,25 @@ 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;
}

View File

@ -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) {

View File

@ -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.

View File

@ -240,6 +240,37 @@ struct NodeSocketAccessor<MathAddNode> : 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<MathFloatToVec3Node> : public NodeSocketAccessorBase {
NodeSocketAccessor(AnimNode* node_) {
MathFloatToVec3Node* node = dynamic_cast<MathFloatToVec3Node*>(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<AnimSamplerNode>(node);
} else if (node_type_name == "BlendTree") {
return new NodeSocketAccessor<BlendTreeNode>(node);
} else if (node_type_name == "MathAddNode") {
return new NodeSocketAccessor<MathAddNode>(node);
} else if (node_type_name == "MathFloatToVec3Node") {
return new NodeSocketAccessor<MathFloatToVec3Node>(node);
} else {
std::cerr << "Invalid node type name " << node_type_name << "."
<< std::endl;

View File

@ -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;

View File

@ -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<AnimData*>(*graph_output_socket->m_value.ptr_ptr);
AnimData* graph_output =
static_cast<AnimData*>(*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<AnimData>(
"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<float>(
"SomeFloatOutput",
"GraphFloatOutput",
nullptr);
graph_output_node.m_socket_accessor->RegisterInput<Vec3>(
"GraphVec3Output",
nullptr);
AnimNodeResource& graph_input_node = graph_resource_origin.m_nodes[1];
graph_input_node.m_socket_accessor->RegisterOutput<AnimData>(
"GraphAnimInput",
nullptr);
AnimNodeResource& graph_input_node =
graph_resource_origin.getGraphInputNode();
graph_input_node.m_socket_accessor->RegisterOutput<float>(
"GraphFloatInput",
nullptr);
graph_input_node.m_socket_accessor->RegisterOutput<bool>(
"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();
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<float*>(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*>(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];