feature/blend_tree_editor #1

Open
martin wants to merge 29 commits from feature/blend_tree_editor into main
4 changed files with 110 additions and 27 deletions
Showing only changes of commit 4c428a865a - Show all commits

View File

@ -23,7 +23,7 @@ void BLTAnimationGraph::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_tree_root", "animation_node"), &BLTAnimationGraph::set_root_animation_node);
ClassDB::bind_method(D_METHOD("get_tree_root"), &BLTAnimationGraph::get_root_animation_node);
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "SyncedAnimationNode"), "set_tree_root", "get_tree_root");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "tree_root", PROPERTY_HINT_RESOURCE_TYPE, "BLTAnimationNode"), "set_tree_root", "get_tree_root");
ClassDB::bind_method(D_METHOD("set_skeleton", "skeleton"), &BLTAnimationGraph::set_skeleton);
ClassDB::bind_method(D_METHOD("get_skeleton"), &BLTAnimationGraph::get_skeleton);

View File

@ -50,12 +50,14 @@ void BLTAnimationNode::_animation_node_removed(const ObjectID &p_oid, const Stri
void BLTAnimationNodeBlendTree::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_node", "animation_node"), &BLTAnimationNodeBlendTree::add_node);
ClassDB::bind_method(D_METHOD("remove_node", "animation_node"), &BLTAnimationNodeBlendTree::remove_node);
ClassDB::bind_method(D_METHOD("get_node", "node_name"), &BLTAnimationNodeBlendTree::get_node);
ClassDB::bind_method(D_METHOD("get_output_node"), &BLTAnimationNodeBlendTree::get_output_node);
ClassDB::bind_method(D_METHOD("get_node_names"), &BLTAnimationNodeBlendTree::get_node_names_as_typed_array);
ClassDB::bind_method(D_METHOD("is_connection_valid", "source_node", "target_node", "target_port_name"), &BLTAnimationNodeBlendTree::is_connection_valid);
ClassDB::bind_method(D_METHOD("add_connection", "source_node", "target_node", "target_port_name"), &BLTAnimationNodeBlendTree::add_connection);
ClassDB::bind_method(D_METHOD("remove_connection", "source_node", "target_node", "target_port_name"), &BLTAnimationNodeBlendTree::remove_connection);
ClassDB::bind_method(D_METHOD("get_connections"), &BLTAnimationNodeBlendTree::get_connections_as_array);
BIND_CONSTANT(CONNECTION_OK);
@ -435,7 +437,7 @@ Ref<BLTAnimationNode> BLTAnimationNodeBlendTree::BLTBlendTreeGraph::get_output_n
}
int BLTAnimationNodeBlendTree::BLTBlendTreeGraph::find_node_index(const Ref<BLTAnimationNode> &node) const {
for (int i = 0; i < nodes.size(); i++) {
for (uint32_t i = 0; i < nodes.size(); i++) {
if (nodes[i] == node) {
return i;
}
@ -445,7 +447,7 @@ int BLTAnimationNodeBlendTree::BLTBlendTreeGraph::find_node_index(const Ref<BLTA
}
int BLTAnimationNodeBlendTree::BLTBlendTreeGraph::find_node_index_by_name(const StringName &name) const {
for (int i = 0; i < nodes.size(); i++) {
for (uint32_t i = 0; i < nodes.size(); i++) {
if (nodes[i]->get_name() == name) {
return i;
}
@ -474,6 +476,42 @@ void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_node(const Ref<BLTAnimati
node_connection_info.push_back(connection_info);
}
void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::remove_node(const Ref<BLTAnimationNode> &node) {
int removed_node_index = find_node_index(node);
assert(removed_node_index >= 0);
// Remove all connections to and from this node
for (uint32_t i = connections.size() - 1; i > 0; i--) {
if (connections[i].source_node == node || connections[i].target_node == node) {
remove_connection(connections[i].source_node, connections[i].target_node, connections[i].target_port_name);
}
}
// Remove the data directly related to this node
node_connection_info.remove_at(removed_node_index);
nodes.remove_at(removed_node_index);
// Ensure all indices are cleaned up.
for (NodeConnectionInfo &connection_info : node_connection_info) {
for (unsigned int j = 0; j < connection_info.connected_child_node_index_at_port.size(); j++) {
if (connection_info.connected_child_node_index_at_port[j] > removed_node_index) {
connection_info.connected_child_node_index_at_port[j] = connection_info.connected_child_node_index_at_port[j] - 1;
}
}
// Map connected subtrees
HashSet<int> old_indices = connection_info.input_subtree_node_indices;
connection_info.input_subtree_node_indices.clear();
for (int old_index : old_indices) {
if (old_index > removed_node_index) {
connection_info.input_subtree_node_indices.insert(old_index - 1);
} else {
connection_info.input_subtree_node_indices.insert(old_index);
}
}
}
}
void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::sort_nodes_and_references() {
LocalVector<int> sorted_node_indices = get_sorted_node_indices();

View File

@ -510,7 +510,7 @@ public:
}
};
Vector<Ref<BLTAnimationNode>> nodes; // All added nodes
LocalVector<Ref<BLTAnimationNode>> nodes; // All added nodes
LocalVector<NodeConnectionInfo> node_connection_info;
LocalVector<BLTBlendTreeConnection> connections;
@ -526,6 +526,7 @@ public:
void remove_subtree_and_update_subtrees_recursive(int node, const HashSet<int> &removed_subtree_indices);
void add_node(const Ref<BLTAnimationNode> &node);
void remove_node(const Ref<BLTAnimationNode> &node);
ConnectionError is_connection_valid(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, StringName target_port_name) const;
ConnectionError add_connection(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, const StringName &target_port_name);
@ -544,7 +545,7 @@ private:
void setup_runtime_data() {
// Add nodes and allocate runtime data
for (int i = 0; i < tree_graph.nodes.size(); i++) {
for (uint32_t i = 0; i < tree_graph.nodes.size(); i++) {
const Ref<BLTAnimationNode> node = tree_graph.nodes[i];
NodeRuntimeData node_runtime_data;
@ -557,7 +558,7 @@ private:
}
// Populate runtime data (only now is this.nodes populated to retrieve the nodes)
for (int i = 0; i < tree_graph.nodes.size(); i++) {
for (uint32_t i = 0; i < tree_graph.nodes.size(); i++) {
Ref<BLTAnimationNode> node = tree_graph.nodes[i];
NodeRuntimeData &node_runtime_data = _node_runtime_data[i];
@ -599,6 +600,15 @@ public:
tree_graph.add_node(node);
}
void remove_node(const Ref<BLTAnimationNode> &node) {
if (tree_initialized) {
print_error("Cannot remove node from BlendTree: BlendTree already initialized.");
return;
}
tree_graph.remove_node(node);
}
TypedArray<StringName> get_node_names_as_typed_array() const {
Vector<StringName> vec;
for (const Ref<BLTAnimationNode> &node : tree_graph.nodes) {
@ -624,7 +634,7 @@ public:
}
Ref<BLTAnimationNode> get_node_by_index(int node_index) const {
if (node_index < 0 || node_index > tree_graph.nodes.size()) {
if (node_index < 0 || node_index > static_cast<int>(tree_graph.nodes.size())) {
return nullptr;
}
@ -673,7 +683,7 @@ public:
return result;
}
// overrides from SyncedAnimationNode
// overrides from BLTAnimationNode
bool initialize(GraphEvaluationContext &context) override {
if (!BLTAnimationNode::initialize(context)) {
return false;
@ -698,7 +708,7 @@ public:
GodotProfileZone("SyncedBlendTree::activate_inputs");
tree_graph.nodes[0]->active = true;
for (int i = 0; i < tree_graph.nodes.size(); i++) {
for (uint32_t i = 0; i < tree_graph.nodes.size(); i++) {
const Ref<BLTAnimationNode> &node = tree_graph.nodes[i];
if (!node->active) {
@ -712,7 +722,7 @@ public:
void calculate_sync_track(const Vector<Ref<BLTAnimationNode>> &input_nodes) override {
GodotProfileZone("SyncedBlendTree::calculate_sync_track");
for (int i = tree_graph.nodes.size() - 1; i > 0; i--) {
for (uint32_t i = tree_graph.nodes.size() - 1; i > 0; i--) {
const Ref<BLTAnimationNode> &node = tree_graph.nodes[i];
if (!node->active) {
@ -731,7 +741,7 @@ public:
tree_graph.nodes[0]->node_time_info.delta = p_delta;
tree_graph.nodes[0]->node_time_info.position += p_delta;
for (int i = 1; i < tree_graph.nodes.size(); i++) {
for (uint32_t i = 1; i < tree_graph.nodes.size(); i++) {
const Ref<BLTAnimationNode> &node = tree_graph.nodes[i];
if (!node->active) {
@ -751,7 +761,7 @@ public:
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &input_datas, AnimationData &output_data) override {
ZoneScopedN("SyncedBlendTree::evaluate");
for (int i = tree_graph.nodes.size() - 1; i > 0; i--) {
for (uint32_t i = tree_graph.nodes.size() - 1; i > 0; i--) {
const Ref<BLTAnimationNode> &node = tree_graph.nodes[i];
if (!node->active) {

View File

@ -505,6 +505,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(animation_sampler_node_b, blend2_node_b, "Input0"));
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(animation_sampler_node_c, blend2_node_b, "Input1"));
SUBCASE("Add and remove a connection") {
HashSet<int> subgraph_output_initial = blend_tree_graph.node_connection_info[0].input_subtree_node_indices;
HashSet<int> subgraph_blend2a_initial = blend_tree_graph.node_connection_info[blend_tree_graph.find_node_index(blend2_node_a)].input_subtree_node_indices;
HashSet<int> subgraph_blend2b_initial = blend_tree_graph.node_connection_info[blend_tree_graph.find_node_index(blend2_node_b)].input_subtree_node_indices;
@ -524,6 +525,40 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
bool connection_equals_removed_connection = connection.source_node == blend2_node_b && connection.target_node == blend2_node_a && connection.target_port_name == "Input1";
CHECK(connection_equals_removed_connection == false);
}
}
SUBCASE("Remove a node") {
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(blend2_node_b, blend2_node_a, "Input1"));
int animation_sampler_node_b_index_pre_remove = blend_tree_graph.find_node_index(animation_sampler_node_b);
int blend2_node_a_index_pre_remove = blend_tree_graph.find_node_index(blend2_node_a);
int blend2_node_b_index_pre_remove = blend_tree_graph.find_node_index(blend2_node_b);
CHECK(blend_tree_graph.node_connection_info[0].input_subtree_node_indices.size() == 6);
CHECK(blend_tree_graph.node_connection_info[blend2_node_a_index_pre_remove].input_subtree_node_indices.size() == 5);
blend_tree_graph.remove_node(animation_sampler_node_a);
for (const BLTBlendTreeConnection &connection : blend_tree_graph.connections) {
bool is_connection_with_removed_node = connection.source_node == animation_sampler_node_a || connection.target_node == animation_sampler_node_a;
CHECK(is_connection_with_removed_node == false);
}
int animation_sampler_node_b_index_post_remove = blend_tree_graph.find_node_index(animation_sampler_node_b);
int blend2_node_a_index_post_remove = blend_tree_graph.find_node_index(blend2_node_a);
int blend2_node_b_index_post_remove = blend_tree_graph.find_node_index(blend2_node_b);
CHECK(blend_tree_graph.find_node_index(animation_sampler_node_a) == -1);
CHECK(blend2_node_b_index_post_remove == blend2_node_b_index_pre_remove - 1);
CHECK(animation_sampler_node_b_index_post_remove == animation_sampler_node_b_index_pre_remove - 1);
CHECK(blend_tree_graph.node_connection_info[0].input_subtree_node_indices.size() == 5);
CHECK(blend_tree_graph.node_connection_info[blend2_node_a_index_post_remove].input_subtree_node_indices.size() == 4);
CHECK(blend_tree_graph.node_connection_info[blend2_node_a_index_post_remove].connected_child_node_index_at_port[0] == -1);
CHECK(blend_tree_graph.node_connection_info[blend2_node_a_index_post_remove].connected_child_node_index_at_port[1] == blend2_node_b_index_post_remove);
CHECK(blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove].input_subtree_node_indices.has(blend2_node_b_index_post_remove));
CHECK(blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove].input_subtree_node_indices.has(animation_sampler_node_b_index_post_remove));
}
}
} //namespace TestSyncedAnimationGraph