WIP: blend tree setup and node sorting.
This commit is contained in:
parent
0d916c98dd
commit
9a79abf4d6
@ -76,10 +76,9 @@ protected:
|
||||
};
|
||||
|
||||
struct SyncTrack {
|
||||
|
||||
};
|
||||
|
||||
class SyncedAnimationNode: public Resource {
|
||||
class SyncedAnimationNode : public Resource {
|
||||
GDCLASS(SyncedAnimationNode, Resource);
|
||||
|
||||
friend class SyncedAnimationGraph;
|
||||
@ -133,11 +132,22 @@ public:
|
||||
}
|
||||
}
|
||||
}
|
||||
virtual void evaluate(GraphEvaluationContext &context, const Vector<AnimationData*>& inputs, AnimationData &output) {}
|
||||
virtual void evaluate(GraphEvaluationContext &context, const Vector<AnimationData *> &inputs, AnimationData &output) {}
|
||||
|
||||
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;
|
||||
@ -153,19 +163,19 @@ private:
|
||||
Ref<Animation> animation;
|
||||
|
||||
void initialize(GraphEvaluationContext &context) override;
|
||||
void evaluate(GraphEvaluationContext &context, const Vector<AnimationData*>& inputs, AnimationData &output) override;
|
||||
void evaluate(GraphEvaluationContext &context, const Vector<AnimationData *> &inputs, AnimationData &output) override;
|
||||
};
|
||||
|
||||
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;
|
||||
@ -203,35 +242,56 @@ struct SortedTreeConstructor {
|
||||
return -1;
|
||||
}
|
||||
|
||||
void add_node(const Ref<SyncedAnimationNode>& node) {
|
||||
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()));
|
||||
}
|
||||
|
||||
bool add_connection(const Ref<SyncedAnimationNode>& source_node, const Ref<SyncedAnimationNode>& target_node, const StringName& target_port_name) {
|
||||
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) {
|
||||
if (!is_connection_valid(source_node, target_node, target_port_name)) {
|
||||
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) {
|
||||
bool is_connection_valid(const Ref<SyncedAnimationNode> &source_node, const Ref<SyncedAnimationNode> &target_node, StringName target_port_name) {
|
||||
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
|
||||
@ -297,47 +366,12 @@ public:
|
||||
return -1;
|
||||
}
|
||||
|
||||
int add_node(const Ref<SyncedAnimationNode>& node) {
|
||||
int add_node(const Ref<SyncedAnimationNode> &node) {
|
||||
nodes.push_back(node);
|
||||
int node_index = nodes.size() - 1;
|
||||
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 {
|
||||
|
||||
void evaluate(GraphEvaluationContext &context, const Vector<AnimationData *> &inputs, AnimationData &output) override {
|
||||
}
|
||||
};
|
||||
|
||||
@ -7,16 +7,16 @@
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
struct SyncedAnimationGraphFixture {
|
||||
Node* character_node;
|
||||
Skeleton3D* skeleton_node;
|
||||
AnimationPlayer* player_node;
|
||||
Node *character_node;
|
||||
Skeleton3D *skeleton_node;
|
||||
AnimationPlayer *player_node;
|
||||
|
||||
int hip_bone_index = -1;
|
||||
|
||||
Ref<Animation> test_animation;
|
||||
Ref<AnimationLibrary> animation_library;
|
||||
|
||||
SyncedAnimationGraph* synced_animation_graph;
|
||||
SyncedAnimationGraph *synced_animation_graph;
|
||||
SyncedAnimationGraphFixture() {
|
||||
character_node = memnew(Node);
|
||||
character_node->set_name("CharacterNode");
|
||||
@ -37,7 +37,7 @@ struct SyncedAnimationGraphFixture {
|
||||
CHECK(track_index == 0);
|
||||
test_animation->track_insert_key(track_index, 0.0, Vector3(0., 0., 0.));
|
||||
test_animation->track_insert_key(track_index, 1.0, Vector3(1., 2., 3.));
|
||||
test_animation->track_set_path(track_index, NodePath(vformat("%s:%s", skeleton_node->get_path().get_concatenated_names(),"Hips")));
|
||||
test_animation->track_set_path(track_index, NodePath(vformat("%s:%s", skeleton_node->get_path().get_concatenated_names(), "Hips")));
|
||||
|
||||
animation_library.instantiate();
|
||||
animation_library->add_animation("TestAnimation", test_animation);
|
||||
@ -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