WIP: blend tree setup and node sorting.
This commit is contained in:
parent
0d916c98dd
commit
9a79abf4d6
@ -76,7 +76,6 @@ protected:
|
||||
};
|
||||
|
||||
struct SyncTrack {
|
||||
|
||||
};
|
||||
|
||||
class SyncedAnimationNode : public Resource {
|
||||
@ -137,7 +136,18 @@ public:
|
||||
|
||||
bool is_active() const { return active; }
|
||||
bool set_input_node(const StringName &socket_name, SyncedAnimationNode *node);
|
||||
virtual void get_input_names(Vector<StringName> &inputs) {};
|
||||
virtual void get_input_names(Vector<StringName> &inputs) const {}
|
||||
|
||||
int get_node_input_index(const StringName &port_name) const {
|
||||
Vector<StringName> inputs;
|
||||
get_input_names(inputs);
|
||||
return inputs.find(port_name);
|
||||
}
|
||||
int get_node_input_count() const {
|
||||
Vector<StringName> inputs;
|
||||
get_input_names(inputs);
|
||||
return inputs.size();
|
||||
}
|
||||
|
||||
private:
|
||||
bool active = false;
|
||||
@ -158,14 +168,14 @@ private:
|
||||
|
||||
class OutputNode : public SyncedAnimationNode {
|
||||
public:
|
||||
void get_input_names(Vector<StringName> &inputs) override {
|
||||
void get_input_names(Vector<StringName> &inputs) const override {
|
||||
inputs.push_back("Input");
|
||||
}
|
||||
};
|
||||
|
||||
class AnimationBlend2Node : public SyncedAnimationNode {
|
||||
public:
|
||||
void get_input_names(Vector<StringName> &inputs) override {
|
||||
void get_input_names(Vector<StringName> &inputs) const override {
|
||||
inputs.push_back("Input0");
|
||||
inputs.push_back("Input1");
|
||||
}
|
||||
@ -178,8 +188,37 @@ struct BlendTreeConnection {
|
||||
};
|
||||
|
||||
struct SortedTreeConstructor {
|
||||
Vector<HashSet<SyncedAnimationNode*>> node_subgraph;
|
||||
Vector<Ref<SyncedAnimationNode>> nodes;
|
||||
struct NodeConnectionInfo {
|
||||
int parent_node_index = -1;
|
||||
HashSet<int> input_subtree_node_indices;
|
||||
LocalVector<int> connected_child_node_index_at_port;
|
||||
|
||||
NodeConnectionInfo() = default;
|
||||
|
||||
explicit NodeConnectionInfo(const SyncedAnimationNode *node) {
|
||||
parent_node_index = -1;
|
||||
for (int i = 0; i < node->get_node_input_count(); i++) {
|
||||
connected_child_node_index_at_port.push_back(-1);
|
||||
}
|
||||
}
|
||||
|
||||
void _print_subtree() const {
|
||||
String result = vformat("subtree node indices (%d): ", input_subtree_node_indices.size());
|
||||
bool is_first = true;
|
||||
for (int index : input_subtree_node_indices) {
|
||||
if (is_first) {
|
||||
result += vformat("%d", index);
|
||||
is_first = false;
|
||||
} else {
|
||||
result += vformat(", %d", index);
|
||||
}
|
||||
}
|
||||
print_line(result);
|
||||
}
|
||||
};
|
||||
|
||||
Vector<Ref<SyncedAnimationNode>> nodes; // All added nodes
|
||||
LocalVector<NodeConnectionInfo> node_connection_info;
|
||||
Vector<BlendTreeConnection> connections;
|
||||
|
||||
SortedTreeConstructor() {
|
||||
@ -189,11 +228,11 @@ struct SortedTreeConstructor {
|
||||
add_node(output_node);
|
||||
}
|
||||
|
||||
Ref<SyncedAnimationNode> get_output_node() {
|
||||
Ref<SyncedAnimationNode> get_output_node() const {
|
||||
return nodes[0];
|
||||
}
|
||||
|
||||
int get_node_index(const Ref<SyncedAnimationNode> node) {
|
||||
int get_node_index(const Ref<SyncedAnimationNode> &node) const {
|
||||
for (int i = 0; i < nodes.size(); i++) {
|
||||
if (nodes[i] == node) {
|
||||
return i;
|
||||
@ -205,7 +244,21 @@ struct SortedTreeConstructor {
|
||||
|
||||
void add_node(const Ref<SyncedAnimationNode> &node) {
|
||||
nodes.push_back(node);
|
||||
node_subgraph.push_back(HashSet<SyncedAnimationNode*>());
|
||||
node_connection_info.push_back(NodeConnectionInfo(node.ptr()));
|
||||
}
|
||||
|
||||
void add_index_and_update_subtrees_recursive(int node, int node_parent) {
|
||||
if (node_parent == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
node_connection_info[node_parent].input_subtree_node_indices.insert(node);
|
||||
|
||||
for (int index : node_connection_info[node].input_subtree_node_indices) {
|
||||
node_connection_info[node_parent].input_subtree_node_indices.insert(index);
|
||||
}
|
||||
|
||||
add_index_and_update_subtrees_recursive(node_parent, node_connection_info[node_parent].parent_node_index);
|
||||
}
|
||||
|
||||
bool add_connection(const Ref<SyncedAnimationNode> &source_node, const Ref<SyncedAnimationNode> &target_node, const StringName &target_port_name) {
|
||||
@ -213,25 +266,32 @@ struct SortedTreeConstructor {
|
||||
return false;
|
||||
}
|
||||
|
||||
// check for loops
|
||||
int source_node_index = get_node_index(source_node);
|
||||
if (node_subgraph.get(source_node_index).has(target_node.ptr())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int target_node_index = get_node_index(target_node);
|
||||
node_subgraph.get(target_node_index).insert(source_node.ptr());
|
||||
int target_input_port_index = target_node->get_node_input_index(target_port_name);
|
||||
|
||||
node_connection_info[source_node_index].parent_node_index = target_node_index;
|
||||
node_connection_info[target_node_index].connected_child_node_index_at_port[target_input_port_index] = source_node_index;
|
||||
|
||||
add_index_and_update_subtrees_recursive(source_node_index, target_node_index);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool is_connection_valid(const Ref<SyncedAnimationNode> &source_node, const Ref<SyncedAnimationNode> &target_node, StringName target_port_name) {
|
||||
if (get_node_index(source_node) == -1) {
|
||||
int source_node_index = get_node_index(source_node);
|
||||
if (source_node_index == -1) {
|
||||
print_error("Cannot connect nodes: source node not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (get_node_index(target_node) == -1) {
|
||||
if (node_connection_info[source_node_index].parent_node_index != -1) {
|
||||
print_error("Cannot connect node: source node already has a parent.");
|
||||
return false;
|
||||
}
|
||||
|
||||
int target_node_index = get_node_index(target_node);
|
||||
if (target_node_index == -1) {
|
||||
print_error("Cannot connect nodes: target node not found.");
|
||||
return false;
|
||||
}
|
||||
@ -249,12 +309,22 @@ struct SortedTreeConstructor {
|
||||
return false;
|
||||
}
|
||||
|
||||
int target_input_port_index = target_node->get_node_input_index(target_port_name);
|
||||
if (node_connection_info[target_node_index].connected_child_node_index_at_port[target_input_port_index] != -1) {
|
||||
print_error("Cannot connect node: target port already connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (node_connection_info[source_node_index].input_subtree_node_indices.has(target_node_index)) {
|
||||
print_error("Cannot connect node: connection would create loop.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class SyncedBlendTree : public SyncedAnimationNode {
|
||||
|
||||
Vector<Ref<SyncedAnimationNode>> tree_nodes;
|
||||
Vector<Vector<int>> tree_node_subgraph;
|
||||
|
||||
@ -268,7 +338,6 @@ class SyncedBlendTree : public SyncedAnimationNode {
|
||||
Vector<Ref<AnimationData>> node_output_data;
|
||||
|
||||
void _setup_graph_evaluation() {
|
||||
|
||||
// After this functions we must have:
|
||||
// * nodes sorted by evaluation order
|
||||
// * node_parent filled
|
||||
@ -303,41 +372,6 @@ public:
|
||||
return node_index;
|
||||
}
|
||||
|
||||
bool connect_nodes(const Ref<SyncedAnimationNode>& source_node, const Ref<SyncedAnimationNode>& target_node, StringName target_socket_name) {
|
||||
if (!is_connection_valid(source_node, target_node, target_socket_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool is_connection_valid(const Ref<SyncedAnimationNode>& source_node, const Ref<SyncedAnimationNode>& target_node, StringName target_socket_name) {
|
||||
if (get_node_index(source_node) == -1) {
|
||||
print_error("Cannot connect nodes: source node not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (get_node_index(target_node) == -1) {
|
||||
print_error("Cannot connect nodes: target node not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (target_node == get_output_node() && tree_connections.size() > 0) {
|
||||
print_error("Cannot add connection to output node: output node is already connected");
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector<StringName> target_inputs;
|
||||
target_node->get_input_names(target_inputs);
|
||||
|
||||
if (!target_inputs.has(target_socket_name)) {
|
||||
print_error("Cannot connect nodes: target socket not found.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// overrides from SyncedAnimationNode
|
||||
void initialize(GraphEvaluationContext &context) override {
|
||||
for (Ref<SyncedAnimationNode> node : nodes) {
|
||||
@ -346,18 +380,14 @@ public:
|
||||
}
|
||||
|
||||
void activate_inputs() override {
|
||||
|
||||
}
|
||||
|
||||
void calculate_sync_track() override {
|
||||
|
||||
}
|
||||
|
||||
void update_time(double p_delta) override {
|
||||
|
||||
}
|
||||
|
||||
void evaluate(GraphEvaluationContext &context, const Vector<AnimationData *> &inputs, AnimationData &output) override {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
@ -68,19 +68,61 @@ TEST_CASE("[SyncedAnimationGraph] TestBlendTreeConstruction") {
|
||||
animation_sampler_node1->name = "Sampler1";
|
||||
tree_constructor.add_node(animation_sampler_node1);
|
||||
|
||||
Ref<AnimationSamplerNode> animation_sampler_node2;
|
||||
animation_sampler_node2.instantiate();
|
||||
animation_sampler_node2->name = "Sampler2";
|
||||
tree_constructor.add_node(animation_sampler_node2);
|
||||
|
||||
Ref<AnimationBlend2Node> node_blend0;
|
||||
node_blend0.instantiate();
|
||||
node_blend0->name = "Blend2";
|
||||
node_blend0->name = "Blend0";
|
||||
tree_constructor.add_node(node_blend0);
|
||||
|
||||
Ref<AnimationBlend2Node> node_blend1;
|
||||
node_blend1.instantiate();
|
||||
node_blend1->name = "Blend2";
|
||||
node_blend1->name = "Blend1";
|
||||
tree_constructor.add_node(node_blend1);
|
||||
|
||||
// Tree
|
||||
// Sampler0 -\
|
||||
// Sampler1 -+- Blend0 -\
|
||||
// Sampler2 ------------+ Blend1 - Output
|
||||
|
||||
CHECK(tree_constructor.add_connection(animation_sampler_node0, node_blend0, "Input0"));
|
||||
CHECK(tree_constructor.add_connection(node_blend1, node_blend0, "Input1"));
|
||||
|
||||
// Ensure that subtree is properly updated
|
||||
int sampler0_index = tree_constructor.get_node_index(animation_sampler_node0);
|
||||
int blend0_index = tree_constructor.get_node_index(node_blend0);
|
||||
CHECK(tree_constructor.node_connection_info[blend0_index].input_subtree_node_indices.has(sampler0_index));
|
||||
|
||||
// Connect blend0 to blend1
|
||||
CHECK(tree_constructor.add_connection(node_blend0, node_blend1, "Input0"));
|
||||
|
||||
// Connecting to an already connected port must fail
|
||||
CHECK(!tree_constructor.add_connection(animation_sampler_node1, node_blend0, "Input0"));
|
||||
// Correct connection of Sampler1 to Blend0
|
||||
CHECK(tree_constructor.add_connection(animation_sampler_node1, node_blend0, "Input1"));
|
||||
|
||||
// Ensure that subtree is properly updated
|
||||
int sampler1_index = tree_constructor.get_node_index(animation_sampler_node0);
|
||||
int blend1_index = tree_constructor.get_node_index(node_blend1);
|
||||
CHECK(tree_constructor.node_connection_info[blend1_index].input_subtree_node_indices.has(sampler1_index));
|
||||
CHECK(tree_constructor.node_connection_info[blend1_index].input_subtree_node_indices.has(sampler0_index));
|
||||
CHECK(tree_constructor.node_connection_info[blend1_index].input_subtree_node_indices.has(blend0_index));
|
||||
|
||||
// Creating a loop must fail
|
||||
CHECK(!tree_constructor.add_connection(node_blend1, node_blend0, "Input1"));
|
||||
|
||||
// Perform remaining connections
|
||||
CHECK(tree_constructor.add_connection(node_blend1, tree_constructor.get_output_node(), "Input"));
|
||||
CHECK(tree_constructor.add_connection(animation_sampler_node2, node_blend1, "Input1"));
|
||||
|
||||
// Output node must have all nodes in its subtree:
|
||||
CHECK(tree_constructor.node_connection_info[0].input_subtree_node_indices.has(1));
|
||||
CHECK(tree_constructor.node_connection_info[0].input_subtree_node_indices.has(2));
|
||||
CHECK(tree_constructor.node_connection_info[0].input_subtree_node_indices.has(3));
|
||||
CHECK(tree_constructor.node_connection_info[0].input_subtree_node_indices.has(4));
|
||||
CHECK(tree_constructor.node_connection_info[0].input_subtree_node_indices.has(5));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] SimpleAnimationSamplerTest" * doctest::skip(true)) {
|
||||
@ -105,6 +147,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
||||
CHECK(hip_bone_position.z == doctest::Approx(0.03));
|
||||
}
|
||||
|
||||
// Currently disabled!
|
||||
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] SimpleBlendTreeTest" * doctest::skip(true)) {
|
||||
Ref<SyncedBlendTree> synced_blend_tree_node;
|
||||
synced_blend_tree_node.instantiate();
|
||||
@ -114,9 +157,6 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
||||
animation_sampler_node->animation_name = "animation_library/TestAnimation";
|
||||
synced_blend_tree_node->add_node(animation_sampler_node);
|
||||
|
||||
|
||||
synced_blend_tree_node->connect_nodes(animation_sampler_node, synced_blend_tree_node->get_output_node(), "Input");
|
||||
|
||||
synced_animation_graph->set_graph_root_node(synced_blend_tree_node);
|
||||
|
||||
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
||||
@ -134,5 +174,4 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
||||
CHECK(hip_bone_position.z == doctest::Approx(0.03));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
} //namespace TestSyncedAnimationGraph
|
||||
Loading…
x
Reference in New Issue
Block a user