feature/blend_tree_editor #1
@ -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);
|
||||
@ -51,7 +51,7 @@ void BLTAnimationGraph::_update_properties_for_node(const String &p_base_path, R
|
||||
p_node->get_child_nodes(&children);
|
||||
|
||||
for (const Ref<BLTAnimationNode> &child_node : children) {
|
||||
_update_properties_for_node(p_base_path + child_node->name + "/", child_node);
|
||||
_update_properties_for_node(p_base_path + child_node->get_name() + "/", child_node);
|
||||
}
|
||||
}
|
||||
|
||||
@ -132,20 +132,24 @@ void BLTAnimationGraph::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
}
|
||||
}
|
||||
|
||||
void BLTAnimationGraph::_tree_changed() {
|
||||
void BLTAnimationGraph::_graph_changed(const StringName &node_name) {
|
||||
print_line(vformat("Graph changed %x", (uintptr_t)this));
|
||||
|
||||
if (properties_dirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
callable_mp(this, &BLTAnimationGraph::_update_properties).call_deferred();
|
||||
callable_mp(this, &BLTAnimationGraph::_setup_graph).call_deferred();
|
||||
|
||||
properties_dirty = true;
|
||||
}
|
||||
|
||||
void BLTAnimationGraph::_notification(int p_what) {
|
||||
GodotProfileZone("SyncedAnimationGraph::_notification");
|
||||
GodotProfileZone("BLTAnimationGraph::_notification");
|
||||
|
||||
switch (p_what) {
|
||||
case Node::NOTIFICATION_READY: {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
_setup_evaluation_context();
|
||||
_setup_graph();
|
||||
|
||||
@ -230,14 +234,9 @@ AnimationMixer::AnimationCallbackModeDiscrete BLTAnimationGraph::get_callback_mo
|
||||
}
|
||||
|
||||
void BLTAnimationGraph::set_animation_player(const NodePath &p_path) {
|
||||
print_line(vformat("set_animation_player(%s) ", p_path));
|
||||
|
||||
animation_player_path = p_path;
|
||||
if (p_path.is_empty()) {
|
||||
// set_root_node(SceneStringName(path_pp));
|
||||
// while (animation_libraries.size()) {
|
||||
// remove_animation_library(animation_libraries[0].name);
|
||||
// }
|
||||
}
|
||||
graph_context.animation_player = Object::cast_to<AnimationPlayer>(get_node_or_null(animation_player_path));
|
||||
|
||||
_setup_evaluation_context();
|
||||
_setup_graph();
|
||||
@ -250,15 +249,17 @@ NodePath BLTAnimationGraph::get_animation_player() const {
|
||||
}
|
||||
|
||||
void BLTAnimationGraph::set_root_animation_node(const Ref<BLTAnimationNode> &p_animation_node) {
|
||||
print_line(vformat("setting root node to node %s", p_animation_node->get_name()));
|
||||
|
||||
if (root_animation_node.is_valid()) {
|
||||
root_animation_node->disconnect(SNAME("tree_changed"), callable_mp(this, &BLTAnimationGraph::_tree_changed));
|
||||
root_animation_node->disconnect(SNAME("node_changed"), callable_mp(this, &BLTAnimationGraph::_graph_changed));
|
||||
}
|
||||
|
||||
root_animation_node = p_animation_node;
|
||||
|
||||
if (root_animation_node.is_valid()) {
|
||||
_setup_graph();
|
||||
root_animation_node->connect(SNAME("tree_changed"), callable_mp(this, &BLTAnimationGraph::_tree_changed));
|
||||
root_animation_node->connect(SNAME("node_changed"), callable_mp(this, &BLTAnimationGraph::_graph_changed));
|
||||
}
|
||||
|
||||
properties_dirty = true;
|
||||
@ -271,14 +272,9 @@ Ref<BLTAnimationNode> BLTAnimationGraph::get_root_animation_node() const {
|
||||
}
|
||||
|
||||
void BLTAnimationGraph::set_skeleton(const NodePath &p_path) {
|
||||
print_line(vformat("set_skeleton(%s) ", p_path));
|
||||
|
||||
skeleton_path = p_path;
|
||||
if (p_path.is_empty()) {
|
||||
// set_root_node(SceneStringName(path_pp));
|
||||
// while (animation_libraries.size()) {
|
||||
// remove_animation_library(animation_libraries[0].name);
|
||||
// }
|
||||
}
|
||||
graph_context.skeleton_3d = Object::cast_to<Skeleton3D>(get_node_or_null(skeleton_path));
|
||||
|
||||
_setup_evaluation_context();
|
||||
_setup_graph();
|
||||
@ -291,11 +287,15 @@ NodePath BLTAnimationGraph::get_skeleton() const {
|
||||
}
|
||||
|
||||
void BLTAnimationGraph::_process_graph(double p_delta, bool p_update_only) {
|
||||
if (!root_animation_node.is_valid()) {
|
||||
if (!root_animation_node.is_valid() || is_graph_initialization_valid == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
GodotProfileZone("SyncedAnimationGraph::_process_graph");
|
||||
if (graph_context.skeleton_3d == nullptr || graph_context.animation_player == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
GodotProfileZone("BLTAnimationGraph::_process_graph");
|
||||
|
||||
_update_properties();
|
||||
|
||||
@ -311,7 +311,7 @@ void BLTAnimationGraph::_process_graph(double p_delta, bool p_update_only) {
|
||||
}
|
||||
|
||||
void BLTAnimationGraph::_apply_animation_data(const AnimationData &output_data) const {
|
||||
GodotProfileZone("SyncedAnimationGraph::_apply_animation_data");
|
||||
GodotProfileZone("BLTAnimationGraph::_apply_animation_data");
|
||||
|
||||
for (const KeyValue<Animation::TypeHash, size_t> &K : output_data.value_buffer_offset) {
|
||||
const AnimationData::TrackValue *track_value = output_data.get_value<AnimationData::TrackValue>(K.key);
|
||||
@ -353,10 +353,20 @@ void BLTAnimationGraph::_set_process(bool p_process, bool p_force) {
|
||||
processing = p_process;
|
||||
}
|
||||
|
||||
void BLTAnimationGraph::_setup_evaluation_context() {
|
||||
_cleanup_evaluation_context();
|
||||
void BLTAnimationGraph::_setup_animation_player() {
|
||||
if (!is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
graph_context.animation_player = Object::cast_to<AnimationPlayer>(get_node_or_null(animation_player_path));
|
||||
print_line(vformat("AnimationPlayer of graph %x is now %x", (uintptr_t)(this), (uintptr_t)graph_context.animation_player));
|
||||
}
|
||||
|
||||
void BLTAnimationGraph::_setup_evaluation_context() {
|
||||
print_line("_setup_evaluation_context()");
|
||||
_cleanup_evaluation_context();
|
||||
|
||||
_setup_animation_player();
|
||||
graph_context.skeleton_3d = Object::cast_to<Skeleton3D>(get_node_or_null(skeleton_path));
|
||||
}
|
||||
|
||||
@ -370,7 +380,9 @@ void BLTAnimationGraph::_setup_graph() {
|
||||
return;
|
||||
}
|
||||
|
||||
root_animation_node->initialize(graph_context);
|
||||
print_line(vformat("_setup_graph() on graph %x and root node %x", (uintptr_t)(void *)(this), (uintptr_t)(root_animation_node.ptr())));
|
||||
is_graph_initialization_valid = root_animation_node->initialize(graph_context);
|
||||
print_line(vformat("is_graph_initialization_valid = %s", is_graph_initialization_valid ? "true" : "false"));
|
||||
}
|
||||
|
||||
BLTAnimationGraph::BLTAnimationGraph() {
|
||||
|
||||
@ -19,11 +19,12 @@ private:
|
||||
mutable AHashMap<StringName, Pair<Ref<BLTAnimationNode>, StringName>> parameter_to_node_parameter_map;
|
||||
|
||||
mutable bool properties_dirty = true;
|
||||
bool is_graph_initialization_valid = false;
|
||||
|
||||
void _update_properties() const;
|
||||
void _update_properties_for_node(const String &p_base_path, Ref<BLTAnimationNode> p_node) const;
|
||||
|
||||
void _tree_changed();
|
||||
void _graph_changed(const StringName &node_name);
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
@ -75,6 +76,7 @@ public:
|
||||
private:
|
||||
void _set_process(bool p_process, bool p_force = false);
|
||||
|
||||
void _setup_animation_player();
|
||||
void _setup_evaluation_context();
|
||||
void _cleanup_evaluation_context();
|
||||
|
||||
|
||||
@ -5,9 +5,18 @@
|
||||
#include "blendalot_animation_node.h"
|
||||
|
||||
void BLTAnimationNode::_bind_methods() {
|
||||
ADD_SIGNAL(MethodInfo("tree_changed"));
|
||||
ClassDB::bind_method(D_METHOD("set_position", "position"), &BLTAnimationNode::set_position);
|
||||
ClassDB::bind_method(D_METHOD("get_position"), &BLTAnimationNode::get_position);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_position", "get_position");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("animation_node_renamed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "old_name"), PropertyInfo(Variant::STRING, "new_name")));
|
||||
ADD_SIGNAL(MethodInfo("animation_node_removed", PropertyInfo(Variant::INT, "object_id"), PropertyInfo(Variant::STRING, "name")));
|
||||
|
||||
ADD_SIGNAL(MethodInfo(SNAME("node_changed"), PropertyInfo(Variant::STRING_NAME, "node_name")));
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_input_names"), &BLTAnimationNode::get_input_names_as_typed_array);
|
||||
ClassDB::bind_method(D_METHOD("get_input_count"), &BLTAnimationNode::get_input_count);
|
||||
ClassDB::bind_method(D_METHOD("get_input_index", "node"), &BLTAnimationNode::get_input_index);
|
||||
}
|
||||
|
||||
void BLTAnimationNode::get_parameter_list(List<PropertyInfo> *r_list) const {
|
||||
@ -28,8 +37,8 @@ Variant BLTAnimationNode::get_parameter(const StringName &p_name) const {
|
||||
return Variant();
|
||||
}
|
||||
|
||||
void BLTAnimationNode::_tree_changed() {
|
||||
emit_signal(SNAME("tree_changed"));
|
||||
void BLTAnimationNode::_node_changed() {
|
||||
emit_signal(SNAME("node_changed"), get_name());
|
||||
}
|
||||
|
||||
void BLTAnimationNode::_animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name) {
|
||||
@ -40,119 +49,10 @@ void BLTAnimationNode::_animation_node_removed(const ObjectID &p_oid, const Stri
|
||||
emit_signal(SNAME("animation_node_removed"), p_oid, p_node);
|
||||
}
|
||||
|
||||
void BLTAnimationNodeBlendTree::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("add_node", "animation_node"), &BLTAnimationNodeBlendTree::add_node);
|
||||
ClassDB::bind_method(D_METHOD("get_output_node"), &BLTAnimationNodeBlendTree::get_output_node);
|
||||
ClassDB::bind_method(D_METHOD("add_connection", "source_node", "target_node", "target_port_name"), &BLTAnimationNodeBlendTree::add_connection);
|
||||
|
||||
BIND_CONSTANT(CONNECTION_OK);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_NO_SOURCE_NODE);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_NO_TARGET_NODE);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_PARENT_EXISTS);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_TARGET_PORT_NOT_FOUND);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_TARGET_PORT_ALREADY_CONNECTED);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_CREATES_LOOP);
|
||||
}
|
||||
|
||||
void BLTAnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
for (const Ref<BLTAnimationNode> &node : tree_graph.nodes) {
|
||||
String prop_name = node->name;
|
||||
if (prop_name != "Output") {
|
||||
p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR));
|
||||
}
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + prop_name + "/position", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
}
|
||||
|
||||
p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
}
|
||||
|
||||
bool BLTAnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_value) const {
|
||||
String prop_name = p_name;
|
||||
if (prop_name.begins_with("nodes/")) {
|
||||
String node_name = prop_name.get_slicec('/', 1);
|
||||
String what = prop_name.get_slicec('/', 2);
|
||||
int node_index = find_node_index_by_name(node_name);
|
||||
|
||||
if (what == "node") {
|
||||
if (node_index != -1) {
|
||||
r_value = tree_graph.nodes[node_index];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (what == "position") {
|
||||
if (node_index != -1) {
|
||||
r_value = tree_graph.nodes[node_index]->position;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (prop_name == "node_connections") {
|
||||
Array conns;
|
||||
conns.resize(tree_graph.connections.size() * 3);
|
||||
|
||||
int idx = 0;
|
||||
for (const BLTBlendTreeConnection &connection : tree_graph.connections) {
|
||||
conns[idx * 3 + 0] = connection.target_node->name;
|
||||
conns[idx * 3 + 1] = connection.target_node->get_input_index(connection.target_port_name);
|
||||
conns[idx * 3 + 2] = connection.source_node->name;
|
||||
idx++;
|
||||
}
|
||||
|
||||
r_value = conns;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BLTAnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_value) {
|
||||
String prop_name = p_name;
|
||||
if (prop_name.begins_with("nodes/")) {
|
||||
String node_name = prop_name.get_slicec('/', 1);
|
||||
String what = prop_name.get_slicec('/', 2);
|
||||
|
||||
if (what == "node") {
|
||||
Ref<BLTAnimationNode> anode = p_value;
|
||||
if (anode.is_valid()) {
|
||||
anode->name = node_name;
|
||||
add_node(anode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (what == "position") {
|
||||
int node_index = find_node_index_by_name(node_name);
|
||||
if (node_index > -1) {
|
||||
tree_graph.nodes[node_index]->position = p_value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (prop_name == "node_connections") {
|
||||
Array conns = p_value;
|
||||
ERR_FAIL_COND_V(conns.size() % 3 != 0, false);
|
||||
|
||||
for (int i = 0; i < conns.size(); i += 3) {
|
||||
int target_node_index = find_node_index_by_name(conns[i]);
|
||||
int target_node_port_index = conns[i + 1];
|
||||
int source_node_index = find_node_index_by_name(conns[i + 2]);
|
||||
|
||||
Ref<BLTAnimationNode> target_node = tree_graph.nodes[target_node_index];
|
||||
Vector<StringName> target_input_names;
|
||||
target_node->get_input_names(target_input_names);
|
||||
|
||||
add_connection(tree_graph.nodes[source_node_index], target_node, target_input_names[target_node_port_index]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void AnimationData::sample_from_animation(const Ref<Animation> &animation, const Skeleton3D *skeleton_3d, double p_time) {
|
||||
GodotProfileZone("AnimationData::sample_from_animation");
|
||||
|
||||
const LocalVector<Animation::Track *> tracks = animation->get_tracks();
|
||||
const LocalVector<Animation::Track *> &tracks = animation->get_tracks();
|
||||
Animation::Track *const *tracks_ptr = tracks.ptr();
|
||||
|
||||
int count = tracks.size();
|
||||
@ -236,7 +136,7 @@ void AnimationData::allocate_track_value(const Animation::Track *animation_track
|
||||
void AnimationData::allocate_track_values(const Ref<Animation> &animation, const Skeleton3D *skeleton_3d) {
|
||||
GodotProfileZone("AnimationData::allocate_track_values");
|
||||
|
||||
const LocalVector<Animation::Track *> tracks = animation->get_tracks();
|
||||
const LocalVector<Animation::Track *> &tracks = animation->get_tracks();
|
||||
Animation::Track *const *tracks_ptr = tracks.ptr();
|
||||
|
||||
int count = tracks.size();
|
||||
@ -254,35 +154,30 @@ void AnimationDataAllocator::register_track_values(const Ref<Animation> &animati
|
||||
default_data.allocate_track_values(animation, skeleton_3d);
|
||||
}
|
||||
|
||||
//
|
||||
// BLTAnimationNodeSampler
|
||||
//
|
||||
bool BLTAnimationNodeSampler::initialize(GraphEvaluationContext &context) {
|
||||
BLTAnimationNode::initialize(context);
|
||||
if (!BLTAnimationNode::initialize(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
animation = context.animation_player->get_animation(animation_name);
|
||||
if (!animation.is_valid()) {
|
||||
print_error(vformat("Cannot initialize node %s: animation '%s' not found in animation player.", name, animation_name));
|
||||
animation_player = context.animation_player;
|
||||
|
||||
if (animation_player == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (animation_name.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!set_animation(animation_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
context.animation_data_allocator.register_track_values(animation, context.skeleton_3d);
|
||||
|
||||
node_time_info.loop_mode = animation->get_loop_mode();
|
||||
|
||||
// Initialize Sync Track from marker
|
||||
LocalVector<float> sync_markers;
|
||||
int marker_index = 0;
|
||||
StringName marker_name = itos(marker_index);
|
||||
while (animation->has_marker(marker_name)) {
|
||||
sync_markers.push_back(animation->get_marker_time(marker_name));
|
||||
marker_index++;
|
||||
marker_name = itos(marker_index);
|
||||
}
|
||||
|
||||
if (sync_markers.size() > 0) {
|
||||
node_time_info.sync_track = SyncTrack::create_from_markers(animation->get_length(), sync_markers);
|
||||
} else {
|
||||
node_time_info.sync_track = SyncTrack::create_from_markers(animation->get_length(), { 0 });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -317,21 +212,134 @@ void BLTAnimationNodeSampler::evaluate(GraphEvaluationContext &context, const Lo
|
||||
output.sample_from_animation(animation, context.skeleton_3d, node_time_info.position);
|
||||
}
|
||||
|
||||
void BLTAnimationNodeSampler::set_animation(const StringName &p_name) {
|
||||
void BLTAnimationNodeSampler::set_animation_player(AnimationPlayer *p_player) {
|
||||
animation_player = p_player;
|
||||
_node_changed();
|
||||
}
|
||||
|
||||
bool BLTAnimationNodeSampler::set_animation(const StringName &p_name) {
|
||||
bool has_animation_name_changed = p_name != animation_name;
|
||||
animation_name = p_name;
|
||||
|
||||
if (animation_player == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!animation_player->has_animation(p_name)) {
|
||||
if (has_animation_name_changed) {
|
||||
_node_changed();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
animation = animation_player->get_animation(p_name);
|
||||
if (!animation.is_valid()) {
|
||||
print_error(vformat("Cannot initialize node %s: animation '%s' not found in animation player.", get_name(), animation_name));
|
||||
|
||||
_node_changed();
|
||||
return false;
|
||||
}
|
||||
|
||||
node_time_info.loop_mode = animation->get_loop_mode();
|
||||
|
||||
// Initialize Sync Track from marker
|
||||
LocalVector<float> sync_markers;
|
||||
int marker_index = 0;
|
||||
StringName marker_name = itos(marker_index);
|
||||
while (animation->has_marker(marker_name)) {
|
||||
sync_markers.push_back(animation->get_marker_time(marker_name));
|
||||
marker_index++;
|
||||
marker_name = itos(marker_index);
|
||||
}
|
||||
|
||||
if (sync_markers.size() > 0) {
|
||||
node_time_info.sync_track = SyncTrack::create_from_markers(animation->get_length(), sync_markers);
|
||||
} else {
|
||||
node_time_info.sync_track = SyncTrack::create_from_markers(animation->get_length(), { 0 });
|
||||
}
|
||||
|
||||
if (has_animation_name_changed) {
|
||||
_node_changed();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
StringName BLTAnimationNodeSampler::get_animation() const {
|
||||
return animation_name;
|
||||
}
|
||||
|
||||
AnimationPlayer *BLTAnimationNodeSampler::get_animation_player() const {
|
||||
return animation_player;
|
||||
}
|
||||
|
||||
TypedArray<StringName> BLTAnimationNodeSampler::get_animations_as_typed_array() const {
|
||||
TypedArray<StringName> typed_arr;
|
||||
|
||||
if (animation_player == nullptr) {
|
||||
print_error(vformat("BLTAnimationNodeSampler '%s' not yet initialized", get_name()));
|
||||
return typed_arr;
|
||||
}
|
||||
|
||||
Vector<StringName> vec;
|
||||
|
||||
List<StringName> animation_libraries;
|
||||
animation_player->get_animation_library_list(&animation_libraries);
|
||||
|
||||
for (const StringName &library_name : animation_libraries) {
|
||||
Ref<AnimationLibrary> library = animation_player->get_animation_library(library_name);
|
||||
List<StringName> animation_list;
|
||||
library->get_animation_list(&animation_list);
|
||||
for (const StringName &library_animation : animation_list) {
|
||||
vec.push_back(library_animation);
|
||||
}
|
||||
}
|
||||
|
||||
typed_arr.resize(vec.size());
|
||||
for (uint32_t i = 0; i < vec.size(); i++) {
|
||||
typed_arr[i] = vec[i];
|
||||
}
|
||||
return typed_arr;
|
||||
}
|
||||
|
||||
void BLTAnimationNodeSampler::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("set_animation", "name"), &BLTAnimationNodeSampler::set_animation);
|
||||
ClassDB::bind_method(D_METHOD("get_animation"), &BLTAnimationNodeSampler::get_animation);
|
||||
ClassDB::bind_method(D_METHOD("get_animation_player"), &BLTAnimationNodeSampler::get_animation_player);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "animation"), "set_animation", "get_animation");
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_animations"), &BLTAnimationNodeSampler::get_animations_as_typed_array);
|
||||
}
|
||||
|
||||
//
|
||||
// BLTAnimationNodeTimeScale
|
||||
//
|
||||
void BLTAnimationNodeTimeScale::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
p_list->push_back(PropertyInfo(Variant::FLOAT, scale_name, PROPERTY_HINT_RANGE, "-10,10,0.01,or_less,or_greater"));
|
||||
}
|
||||
|
||||
bool BLTAnimationNodeTimeScale::_get(const StringName &p_name, Variant &r_value) const {
|
||||
if (p_name == scale_name) {
|
||||
r_value = scale;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BLTAnimationNodeTimeScale::_set(const StringName &p_name, const Variant &p_value) {
|
||||
if (p_name == scale_name) {
|
||||
scale = p_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// BLTAnimationNodeBlend2
|
||||
//
|
||||
void BLTAnimationNodeBlend2::evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &inputs, AnimationData &output) {
|
||||
GodotProfileZone("AnimationBlend2Node::evaluate");
|
||||
|
||||
@ -378,7 +386,6 @@ Variant BLTAnimationNodeBlend2::get_parameter_default_value(const StringName &p_
|
||||
|
||||
void BLTAnimationNodeBlend2::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
p_list->push_back(PropertyInfo(Variant::FLOAT, blend_weight_pname, PROPERTY_HINT_RANGE, "0,1,0.01,or_less,or_greater"));
|
||||
p_list->push_back(PropertyInfo(Variant::BOOL, sync_pname));
|
||||
}
|
||||
|
||||
bool BLTAnimationNodeBlend2::_get(const StringName &p_name, Variant &r_value) const {
|
||||
@ -387,11 +394,6 @@ bool BLTAnimationNodeBlend2::_get(const StringName &p_name, Variant &r_value) co
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_name == sync_pname) {
|
||||
r_value = sync;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -401,18 +403,16 @@ bool BLTAnimationNodeBlend2::_set(const StringName &p_name, const Variant &p_val
|
||||
return true;
|
||||
}
|
||||
|
||||
if (p_name == sync_pname) {
|
||||
sync = p_value;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//
|
||||
// BLTAnimationNodeBlendTree
|
||||
//
|
||||
BLTAnimationNodeBlendTree::BLTBlendTreeGraph::BLTBlendTreeGraph() {
|
||||
Ref<BLTAnimationNodeOutput> output_node;
|
||||
output_node.instantiate();
|
||||
output_node->name = "Output";
|
||||
output_node->set_name("Output");
|
||||
add_node(output_node);
|
||||
}
|
||||
|
||||
@ -421,7 +421,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;
|
||||
}
|
||||
@ -431,8 +431,8 @@ 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++) {
|
||||
if (nodes[i]->name == name) {
|
||||
for (uint32_t i = 0; i < nodes.size(); i++) {
|
||||
if (nodes[i]->get_name() == name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
@ -441,32 +441,83 @@ int BLTAnimationNodeBlendTree::BLTBlendTreeGraph::find_node_index_by_name(const
|
||||
}
|
||||
|
||||
void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_node(const Ref<BLTAnimationNode> &node) {
|
||||
StringName node_base_name = node->name;
|
||||
StringName node_base_name = node->get_name();
|
||||
if (node_base_name.is_empty()) {
|
||||
node_base_name = node->get_class_name();
|
||||
}
|
||||
node->name = node_base_name;
|
||||
node->set_name(node_base_name);
|
||||
|
||||
int number_suffix = 1;
|
||||
while (find_node_index_by_name(node->name) != -1) {
|
||||
node->name = vformat("%s %d", node_base_name, number_suffix);
|
||||
while (find_node_index_by_name(node->get_name()) != -1) {
|
||||
node->set_name(vformat("%s %d", node_base_name, number_suffix));
|
||||
number_suffix++;
|
||||
}
|
||||
|
||||
nodes.push_back(node);
|
||||
node_connection_info.push_back(NodeConnectionInfo(node.ptr()));
|
||||
|
||||
NodeConnectionInfo connection_info(node.ptr());
|
||||
connection_info.input_subtree_node_indices.insert(nodes.size() - 1);
|
||||
node_connection_info.push_back(connection_info);
|
||||
}
|
||||
|
||||
bool BLTAnimationNodeBlendTree::BLTBlendTreeGraph::remove_node(const Ref<BLTAnimationNode> &node) {
|
||||
if (node == get_output_node()) {
|
||||
// Output node not allowed to be removed
|
||||
return false;
|
||||
}
|
||||
|
||||
int removed_node_index = find_node_index(node);
|
||||
assert(removed_node_index >= 0);
|
||||
|
||||
// Remove all connections to and from this node
|
||||
for (int i = static_cast<int>(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;
|
||||
}
|
||||
}
|
||||
|
||||
if (connection_info.parent_node_index > removed_node_index) {
|
||||
connection_info.parent_node_index--;
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::sort_nodes_and_references() {
|
||||
LocalVector<int> sorted_node_indices = get_sorted_node_indices();
|
||||
|
||||
Vector<Ref<BLTAnimationNode>> sorted_nodes;
|
||||
LocalVector<NodeConnectionInfo> old_node_connection_info = node_connection_info;
|
||||
LocalVector<Ref<BLTAnimationNode>> sorted_nodes;
|
||||
LocalVector<NodeConnectionInfo> old_node_connection_info(node_connection_info);
|
||||
for (unsigned int i = 0; i < sorted_node_indices.size(); i++) {
|
||||
int node_index = sorted_node_indices[i];
|
||||
sorted_nodes.push_back(nodes[node_index]);
|
||||
node_connection_info[i] = old_node_connection_info[node_index];
|
||||
}
|
||||
|
||||
nodes = sorted_nodes;
|
||||
|
||||
for (NodeConnectionInfo &connection_info : node_connection_info) {
|
||||
@ -483,6 +534,20 @@ LocalVector<int> BLTAnimationNodeBlendTree::BLTBlendTreeGraph::get_sorted_node_i
|
||||
sort_nodes_recursive(0, result);
|
||||
result.reverse();
|
||||
|
||||
HashSet<int> connected_node_indices;
|
||||
for (int node_index : result) {
|
||||
connected_node_indices.insert(node_index);
|
||||
}
|
||||
|
||||
// Ensure that nodes that are not reachable from the root node are still added to
|
||||
// the sorted nodes indices.
|
||||
for (Ref<BLTAnimationNode> &node : nodes) {
|
||||
int node_index = find_node_index(node);
|
||||
if (!connected_node_indices.has(node_index)) {
|
||||
result.push_back(node_index);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@ -495,18 +560,69 @@ void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::sort_nodes_recursive(int node
|
||||
result.push_back(node_index);
|
||||
}
|
||||
|
||||
void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_index_and_update_subtrees_recursive(int node, int node_parent) {
|
||||
if (node_parent == -1) {
|
||||
void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_index_and_update_subtrees_recursive(int node_index, int node_parent_index) {
|
||||
if (node_parent_index == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
node_connection_info[node_parent].input_subtree_node_indices.insert(node);
|
||||
node_connection_info[node_parent_index].input_subtree_node_indices.insert(node_index);
|
||||
|
||||
for (int index : node_connection_info[node].input_subtree_node_indices) {
|
||||
node_connection_info[node_parent].input_subtree_node_indices.insert(index);
|
||||
for (int index : node_connection_info[node_index].input_subtree_node_indices) {
|
||||
node_connection_info[node_parent_index].input_subtree_node_indices.insert(index);
|
||||
}
|
||||
|
||||
add_index_and_update_subtrees_recursive(node_parent, node_connection_info[node_parent].parent_node_index);
|
||||
add_index_and_update_subtrees_recursive(node_parent_index, node_connection_info[node_parent_index].parent_node_index);
|
||||
}
|
||||
|
||||
void BLTAnimationNodeBlendTree::BLTBlendTreeGraph::remove_subtree_and_update_subtrees_recursive(int node_index, const HashSet<int> &removed_subtree_indices) {
|
||||
NodeConnectionInfo &connection_info = node_connection_info[node_index];
|
||||
|
||||
for (int subtree_node_index : removed_subtree_indices) {
|
||||
connection_info.input_subtree_node_indices.erase(subtree_node_index);
|
||||
}
|
||||
|
||||
if (connection_info.parent_node_index != -1) {
|
||||
remove_subtree_and_update_subtrees_recursive(connection_info.parent_node_index, removed_subtree_indices);
|
||||
}
|
||||
}
|
||||
|
||||
BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTreeGraph::is_connection_valid(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, StringName target_port_name) const {
|
||||
int source_node_index = find_node_index(source_node);
|
||||
if (source_node_index == -1) {
|
||||
print_error("Cannot connect nodes: source node not found.");
|
||||
return CONNECTION_ERROR_NO_SOURCE_NODE;
|
||||
}
|
||||
|
||||
if (node_connection_info[source_node_index].parent_node_index != -1) {
|
||||
print_error("Cannot connect node: source node already has a parent.");
|
||||
return CONNECTION_ERROR_PARENT_EXISTS;
|
||||
}
|
||||
|
||||
int target_node_index = find_node_index(target_node);
|
||||
if (target_node_index == -1) {
|
||||
print_error("Cannot connect nodes: target node not found.");
|
||||
return CONNECTION_ERROR_NO_TARGET_NODE;
|
||||
}
|
||||
|
||||
Vector<StringName> target_inputs = target_node->get_input_names();
|
||||
|
||||
if (!target_inputs.has(target_port_name)) {
|
||||
print_error("Cannot connect nodes: target port not found.");
|
||||
return CONNECTION_ERROR_TARGET_PORT_NOT_FOUND;
|
||||
}
|
||||
|
||||
int target_input_port_index = target_node->get_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 CONNECTION_ERROR_TARGET_PORT_ALREADY_CONNECTED;
|
||||
}
|
||||
|
||||
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 CONNECTION_ERROR_CONNECTION_CREATES_LOOP;
|
||||
}
|
||||
|
||||
return CONNECTION_OK;
|
||||
}
|
||||
|
||||
BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTreeGraph::add_connection(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, const StringName &target_port_name) {
|
||||
@ -528,42 +644,154 @@ BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTr
|
||||
return CONNECTION_OK;
|
||||
}
|
||||
|
||||
BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTreeGraph::is_connection_valid(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, StringName target_port_name) const {
|
||||
int BLTAnimationNodeBlendTree::BLTBlendTreeGraph::find_connection_index(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, const StringName &target_port_name) const {
|
||||
for (uint32_t i = 0; i < connections.size(); i++) {
|
||||
if (connections[i].source_node == source_node && connections[i].target_node == target_node && connections[i].target_port_name == target_port_name) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
BLTAnimationNodeBlendTree::ConnectionError BLTAnimationNodeBlendTree::BLTBlendTreeGraph::remove_connection(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, const StringName &target_port_name) {
|
||||
int source_node_index = find_node_index(source_node);
|
||||
if (source_node_index == -1) {
|
||||
print_error("Cannot connect nodes: source node not found.");
|
||||
return CONNECTION_ERROR_NO_SOURCE_NODE;
|
||||
}
|
||||
NodeConnectionInfo &connection_info = node_connection_info[source_node_index];
|
||||
|
||||
if (node_connection_info[source_node_index].parent_node_index != -1) {
|
||||
print_error("Cannot connect node: source node already has a parent.");
|
||||
return CONNECTION_ERROR_PARENT_EXISTS;
|
||||
}
|
||||
if (connection_info.parent_node_index != -1) {
|
||||
NodeConnectionInfo &parent_connection_info = node_connection_info[connection_info.parent_node_index];
|
||||
parent_connection_info.input_subtree_node_indices.erase(source_node_index);
|
||||
parent_connection_info.connected_child_node_index_at_port[target_node->get_input_index(target_port_name)] = -1;
|
||||
|
||||
int target_node_index = find_node_index(target_node);
|
||||
if (target_node_index == -1) {
|
||||
print_error("Cannot connect nodes: target node not found.");
|
||||
return CONNECTION_ERROR_NO_TARGET_NODE;
|
||||
}
|
||||
remove_subtree_and_update_subtrees_recursive(connection_info.parent_node_index, connection_info.input_subtree_node_indices);
|
||||
|
||||
Vector<StringName> target_inputs;
|
||||
target_node->get_input_names(target_inputs);
|
||||
connection_info.parent_node_index = -1;
|
||||
|
||||
if (!target_inputs.has(target_port_name)) {
|
||||
print_error("Cannot connect nodes: target port not found.");
|
||||
return CONNECTION_ERROR_TARGET_PORT_NOT_FOUND;
|
||||
}
|
||||
|
||||
int target_input_port_index = target_node->get_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 CONNECTION_ERROR_TARGET_PORT_ALREADY_CONNECTED;
|
||||
}
|
||||
|
||||
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 CONNECTION_ERROR_CONNECTION_CREATES_LOOP;
|
||||
uint32_t connection_index = find_connection_index(source_node, target_node, target_port_name);
|
||||
assert(connection_index >= 0);
|
||||
connections.remove_at(connection_index);
|
||||
} else {
|
||||
return CONNECTION_ERROR_CONNECTION_NOT_FOUND;
|
||||
}
|
||||
|
||||
return CONNECTION_OK;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_graph_offset", "graph_offset"), &BLTAnimationNodeBlendTree::set_graph_offset);
|
||||
ClassDB::bind_method(D_METHOD("get_graph_offset"), &BLTAnimationNodeBlendTree::get_graph_offset);
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_graph_offset", "get_graph_offset");
|
||||
|
||||
BIND_CONSTANT(CONNECTION_OK);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_NO_SOURCE_NODE);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_NO_TARGET_NODE);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_PARENT_EXISTS);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_TARGET_PORT_NOT_FOUND);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_TARGET_PORT_ALREADY_CONNECTED);
|
||||
BIND_CONSTANT(CONNECTION_ERROR_CONNECTION_CREATES_LOOP);
|
||||
}
|
||||
|
||||
void BLTAnimationNodeBlendTree::_get_property_list(List<PropertyInfo> *p_list) const {
|
||||
for (const Ref<BLTAnimationNode> &node : tree_graph.nodes) {
|
||||
String prop_name = node->get_name();
|
||||
if (prop_name != "Output") {
|
||||
p_list->push_back(PropertyInfo(Variant::OBJECT, "nodes/" + prop_name + "/node", PROPERTY_HINT_RESOURCE_TYPE, "AnimationNode", PROPERTY_USAGE_NO_EDITOR));
|
||||
}
|
||||
p_list->push_back(PropertyInfo(Variant::VECTOR2, "nodes/" + prop_name + "/graph_offset", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
}
|
||||
|
||||
p_list->push_back(PropertyInfo(Variant::ARRAY, "node_connections", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR));
|
||||
}
|
||||
|
||||
bool BLTAnimationNodeBlendTree::_get(const StringName &p_name, Variant &r_value) const {
|
||||
String prop_name = p_name;
|
||||
if (prop_name.begins_with("nodes/")) {
|
||||
String node_name = prop_name.get_slicec('/', 1);
|
||||
String what = prop_name.get_slicec('/', 2);
|
||||
int node_index = find_node_index_by_name(node_name);
|
||||
|
||||
if (what == "node") {
|
||||
if (node_index != -1) {
|
||||
r_value = tree_graph.nodes[node_index];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (what == "graph_offset") {
|
||||
if (node_index != -1) {
|
||||
r_value = tree_graph.nodes[node_index]->position;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else if (prop_name == "node_connections") {
|
||||
Array conns;
|
||||
conns.resize(tree_graph.connections.size() * 3);
|
||||
|
||||
int idx = 0;
|
||||
for (const BLTBlendTreeConnection &connection : tree_graph.connections) {
|
||||
conns[idx * 3 + 0] = connection.target_node->get_name();
|
||||
conns[idx * 3 + 1] = connection.target_node->get_input_index(connection.target_port_name);
|
||||
conns[idx * 3 + 2] = connection.source_node->get_name();
|
||||
idx++;
|
||||
}
|
||||
|
||||
r_value = conns;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool BLTAnimationNodeBlendTree::_set(const StringName &p_name, const Variant &p_value) {
|
||||
String prop_name = p_name;
|
||||
if (prop_name.begins_with("nodes/")) {
|
||||
String node_name = prop_name.get_slicec('/', 1);
|
||||
String what = prop_name.get_slicec('/', 2);
|
||||
|
||||
if (what == "node") {
|
||||
Ref<BLTAnimationNode> anode = p_value;
|
||||
if (anode.is_valid()) {
|
||||
anode->set_name(node_name);
|
||||
add_node(anode);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (what == "graph_offset") {
|
||||
int node_index = find_node_index_by_name(node_name);
|
||||
if (node_index > -1) {
|
||||
tree_graph.nodes[node_index]->position = p_value;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else if (prop_name == "node_connections") {
|
||||
Array conns = p_value;
|
||||
ERR_FAIL_COND_V(conns.size() % 3 != 0, false);
|
||||
|
||||
for (int i = 0; i < conns.size(); i += 3) {
|
||||
int target_node_index = find_node_index_by_name(conns[i]);
|
||||
int target_node_port_index = conns[i + 1];
|
||||
int source_node_index = find_node_index_by_name(conns[i + 2]);
|
||||
|
||||
Ref<BLTAnimationNode> target_node = tree_graph.nodes[target_node_index];
|
||||
Vector<StringName> target_input_names = target_node->get_input_names();
|
||||
|
||||
add_connection(tree_graph.nodes[source_node_index], target_node, target_input_names[target_node_port_index]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -5,6 +5,7 @@
|
||||
|
||||
#include "scene/3d/skeleton_3d.h"
|
||||
#include "scene/animation/animation_player.h"
|
||||
#include "scene/resources/animation_library.h"
|
||||
#include "sync_track.h"
|
||||
|
||||
#include <cassert>
|
||||
@ -104,7 +105,8 @@ struct AnimationData {
|
||||
buffer = other.buffer;
|
||||
}
|
||||
AnimationData(AnimationData &&other) noexcept :
|
||||
value_buffer_offset(std::exchange(other.value_buffer_offset, AHashMap<Animation::TypeHash, size_t, HashHasher>())),
|
||||
// We skip copying the offset as that should be identical for all nodes within a BLTAnimationGraph.
|
||||
// value_buffer_offset(std::exchange(other.value_buffer_offset, AHashMap<Animation::TypeHash, size_t, HashHasher>())),
|
||||
buffer(std::exchange(other.buffer, LocalVector<uint8_t>())) {
|
||||
}
|
||||
AnimationData &operator=(const AnimationData &other) {
|
||||
@ -245,7 +247,7 @@ protected:
|
||||
virtual void set_parameter(const StringName &p_name, const Variant &p_value);
|
||||
virtual Variant get_parameter(const StringName &p_name) const;
|
||||
|
||||
virtual void _tree_changed();
|
||||
virtual void _node_changed();
|
||||
virtual void _animation_node_renamed(const ObjectID &p_oid, const String &p_old_name, const String &p_new_name);
|
||||
virtual void _animation_node_removed(const ObjectID &p_oid, const StringName &p_node);
|
||||
|
||||
@ -262,7 +264,6 @@ public:
|
||||
NodeTimeInfo node_time_info;
|
||||
bool active = false;
|
||||
|
||||
StringName name;
|
||||
Vector2 position;
|
||||
|
||||
virtual ~BLTAnimationNode() override = default;
|
||||
@ -274,6 +275,11 @@ public:
|
||||
virtual void activate_inputs(const Vector<Ref<BLTAnimationNode>> &input_nodes) {
|
||||
// By default, all inputs nodes are activated.
|
||||
for (const Ref<BLTAnimationNode> &node : input_nodes) {
|
||||
if (node.ptr() == nullptr) {
|
||||
// TODO: add checking whether tree can be evaluated, i.e. whether all inputs are properly connected.
|
||||
continue;
|
||||
}
|
||||
|
||||
node->active = true;
|
||||
node->node_time_info.is_synced = node_time_info.is_synced;
|
||||
}
|
||||
@ -285,6 +291,7 @@ public:
|
||||
node_time_info.loop_mode = input_nodes[0]->node_time_info.loop_mode;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void update_time(double p_time) {
|
||||
if (node_time_info.is_synced) {
|
||||
node_time_info.sync_position = p_time;
|
||||
@ -293,23 +300,41 @@ public:
|
||||
node_time_info.position += p_time;
|
||||
}
|
||||
}
|
||||
|
||||
virtual void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &input_datas, AnimationData &output_data) {
|
||||
GodotProfileZone("AnimationNode::evaluate");
|
||||
// By default, use the AnimationData of the first input.
|
||||
if (input_datas.size() > 0) {
|
||||
output_data = *input_datas[0];
|
||||
output_data = std::move(*input_datas[0]);
|
||||
}
|
||||
}
|
||||
|
||||
virtual void get_input_names(Vector<StringName> &inputs) const {}
|
||||
void set_position(const Vector2 &p_position) {
|
||||
position = p_position;
|
||||
}
|
||||
|
||||
Vector2 get_position() const {
|
||||
return position;
|
||||
}
|
||||
|
||||
virtual Vector<StringName> get_input_names() const { return {}; }
|
||||
|
||||
TypedArray<StringName> get_input_names_as_typed_array() const {
|
||||
TypedArray<StringName> typed_arr;
|
||||
Vector<StringName> vec = get_input_names();
|
||||
typed_arr.resize(vec.size());
|
||||
for (uint32_t i = 0; i < vec.size(); i++) {
|
||||
typed_arr[i] = vec[i];
|
||||
}
|
||||
return typed_arr;
|
||||
}
|
||||
|
||||
int get_input_index(const StringName &port_name) const {
|
||||
Vector<StringName> inputs;
|
||||
get_input_names(inputs);
|
||||
Vector<StringName> inputs = get_input_names();
|
||||
return inputs.find(port_name);
|
||||
}
|
||||
int get_input_count() const {
|
||||
Vector<StringName> inputs;
|
||||
get_input_names(inputs);
|
||||
Vector<StringName> inputs = get_input_names();
|
||||
return inputs.size();
|
||||
}
|
||||
|
||||
@ -322,9 +347,14 @@ class BLTAnimationNodeSampler : public BLTAnimationNode {
|
||||
|
||||
public:
|
||||
StringName animation_name;
|
||||
AnimationPlayer *animation_player = nullptr;
|
||||
|
||||
void set_animation(const StringName &p_name);
|
||||
void set_animation_player(AnimationPlayer *p_player);
|
||||
bool set_animation(const StringName &p_name);
|
||||
StringName get_animation() const;
|
||||
AnimationPlayer *get_animation_player() const;
|
||||
|
||||
TypedArray<StringName> get_animations_as_typed_array() const;
|
||||
|
||||
private:
|
||||
Ref<Animation> animation;
|
||||
@ -337,12 +367,53 @@ protected:
|
||||
static void _bind_methods();
|
||||
};
|
||||
|
||||
class BLTAnimationNodeTimeScale : public BLTAnimationNode {
|
||||
GDCLASS(BLTAnimationNodeTimeScale, BLTAnimationNode);
|
||||
|
||||
public:
|
||||
float scale = 1.0f;
|
||||
|
||||
private:
|
||||
Ref<Animation> animation;
|
||||
|
||||
Vector<StringName> get_input_names() const override {
|
||||
return { "Input" };
|
||||
}
|
||||
|
||||
bool initialize(GraphEvaluationContext &context) override {
|
||||
node_time_info = {};
|
||||
// TODO: it should not be necessary to force looping here. node_time_info.loop_mode = Animation::LOOP_LINEAR;
|
||||
return true;
|
||||
}
|
||||
void calculate_sync_track(const Vector<Ref<BLTAnimationNode>> &input_nodes) override {
|
||||
if (node_time_info.is_synced) {
|
||||
node_time_info.sync_track = input_nodes[0]->node_time_info.sync_track;
|
||||
node_time_info.sync_track.duration *= scale;
|
||||
}
|
||||
}
|
||||
void update_time(double p_time) override {
|
||||
if (node_time_info.is_synced) {
|
||||
return;
|
||||
}
|
||||
|
||||
BLTAnimationNode::update_time(p_time * scale);
|
||||
}
|
||||
|
||||
protected:
|
||||
void _get_property_list(List<PropertyInfo> *p_list) const;
|
||||
bool _get(const StringName &p_name, Variant &r_value) const;
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
|
||||
private:
|
||||
StringName scale_name = PNAME("scale");
|
||||
};
|
||||
|
||||
class BLTAnimationNodeOutput : public BLTAnimationNode {
|
||||
GDCLASS(BLTAnimationNodeOutput, BLTAnimationNode);
|
||||
|
||||
public:
|
||||
void get_input_names(Vector<StringName> &inputs) const override {
|
||||
inputs.push_back("Input");
|
||||
Vector<StringName> get_input_names() const override {
|
||||
return { "Output" };
|
||||
}
|
||||
};
|
||||
|
||||
@ -353,20 +424,26 @@ public:
|
||||
float blend_weight = 0.0f;
|
||||
bool sync = true;
|
||||
|
||||
void get_input_names(Vector<StringName> &inputs) const override {
|
||||
inputs.push_back("Input0");
|
||||
inputs.push_back("Input1");
|
||||
Vector<StringName> get_input_names() const override {
|
||||
return { "Input0", "Input1" };
|
||||
}
|
||||
|
||||
bool initialize(GraphEvaluationContext &context) override {
|
||||
bool result = BLTAnimationNode::initialize(context);
|
||||
if (!BLTAnimationNode::initialize(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sync) {
|
||||
// TODO: do we always want looping in this case or do we traverse the graph to check what's reasonable?
|
||||
node_time_info.loop_mode = Animation::LOOP_LINEAR;
|
||||
}
|
||||
|
||||
return result;
|
||||
if (node_time_info.loop_mode != Animation::LOOP_LINEAR) {
|
||||
print_line(vformat("Forcing loop mode to linear on nonde %s", get_name()));
|
||||
node_time_info.loop_mode = Animation::LOOP_LINEAR;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
void activate_inputs(const Vector<Ref<BLTAnimationNode>> &input_nodes) override {
|
||||
input_nodes[0]->active = true;
|
||||
@ -379,7 +456,8 @@ public:
|
||||
|
||||
void calculate_sync_track(const Vector<Ref<BLTAnimationNode>> &input_nodes) override {
|
||||
if (node_time_info.is_synced || sync) {
|
||||
assert(input_nodes[0]->node_time_info.loop_mode == input_nodes[1]->node_time_info.loop_mode);
|
||||
// TODO: figure out whether we need to enforce looping mode when syncing is enabled.
|
||||
// assert(input_nodes[0]->node_time_info.loop_mode == input_nodes[1]->node_time_info.loop_mode);
|
||||
node_time_info.sync_track = SyncTrack::blend(blend_weight, input_nodes[0]->node_time_info.sync_track, input_nodes[1]->node_time_info.sync_track);
|
||||
}
|
||||
}
|
||||
@ -393,9 +471,9 @@ public:
|
||||
if (!Math::is_zero_approx(node_time_info.sync_track.duration)) {
|
||||
node_time_info.position = Math::fposmod(static_cast<float>(node_time_info.position), node_time_info.sync_track.duration);
|
||||
node_time_info.sync_position = node_time_info.sync_track.calc_sync_from_abs_time(node_time_info.position);
|
||||
} else {
|
||||
assert(false && !"Loop mode ping-pong not yet supported");
|
||||
}
|
||||
} else {
|
||||
assert(false && !"Loop mode ping-pong not yet supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -423,9 +501,9 @@ private:
|
||||
};
|
||||
|
||||
struct BLTBlendTreeConnection {
|
||||
const Ref<BLTAnimationNode> source_node = nullptr;
|
||||
const Ref<BLTAnimationNode> target_node = nullptr;
|
||||
const StringName target_port_name = "";
|
||||
Ref<BLTAnimationNode> source_node = nullptr;
|
||||
Ref<BLTAnimationNode> target_node = nullptr;
|
||||
StringName target_port_name = "";
|
||||
};
|
||||
|
||||
class BLTAnimationNodeBlendTree : public BLTAnimationNode {
|
||||
@ -441,6 +519,7 @@ public:
|
||||
CONNECTION_ERROR_TARGET_PORT_NOT_FOUND,
|
||||
CONNECTION_ERROR_TARGET_PORT_ALREADY_CONNECTED,
|
||||
CONNECTION_ERROR_CONNECTION_CREATES_LOOP,
|
||||
CONNECTION_ERROR_CONNECTION_NOT_FOUND
|
||||
};
|
||||
|
||||
/**
|
||||
@ -477,32 +556,49 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void _print_subtree() const;
|
||||
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<BLTAnimationNode>> nodes; // All added nodes
|
||||
LocalVector<Ref<BLTAnimationNode>> nodes; // All added nodes
|
||||
LocalVector<NodeConnectionInfo> node_connection_info;
|
||||
LocalVector<BLTBlendTreeConnection> connections;
|
||||
|
||||
BLTBlendTreeGraph();
|
||||
|
||||
Ref<BLTAnimationNode> get_output_node();
|
||||
|
||||
int find_node_index(const Ref<BLTAnimationNode> &node) const;
|
||||
int find_node_index_by_name(const StringName &name) const;
|
||||
void sort_nodes_and_references();
|
||||
LocalVector<int> get_sorted_node_indices();
|
||||
void sort_nodes_recursive(int node_index, LocalVector<int> &result);
|
||||
void add_index_and_update_subtrees_recursive(int node, int node_parent);
|
||||
ConnectionError is_connection_valid(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, StringName target_port_name) const;
|
||||
void add_index_and_update_subtrees_recursive(int node_index, int node_parent_index);
|
||||
void remove_subtree_and_update_subtrees_recursive(int node, const HashSet<int> &removed_subtree_indices);
|
||||
|
||||
void add_node(const Ref<BLTAnimationNode> &node);
|
||||
bool 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);
|
||||
int find_connection_index(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, const StringName &target_port_name) const;
|
||||
ConnectionError remove_connection(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, const StringName &target_port_name);
|
||||
};
|
||||
|
||||
private:
|
||||
BLTBlendTreeGraph tree_graph;
|
||||
bool tree_initialized = false;
|
||||
GraphEvaluationContext *_graph_evaluation_context = nullptr;
|
||||
|
||||
void sort_nodes() {
|
||||
_node_runtime_data.clear();
|
||||
@ -511,7 +607,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;
|
||||
@ -524,13 +620,17 @@ 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];
|
||||
|
||||
for (int port_index = 0; port_index < node->get_input_count(); port_index++) {
|
||||
const int connected_node_index = tree_graph.node_connection_info[i].connected_child_node_index_at_port[port_index];
|
||||
node_runtime_data.input_nodes.push_back(tree_graph.nodes[connected_node_index]);
|
||||
if (connected_node_index == -1) {
|
||||
node_runtime_data.input_nodes.push_back(nullptr);
|
||||
} else {
|
||||
node_runtime_data.input_nodes.push_back(tree_graph.nodes[connected_node_index]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -542,6 +642,8 @@ protected:
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
|
||||
public:
|
||||
Vector2 graph_offset;
|
||||
|
||||
struct NodeRuntimeData {
|
||||
Vector<Ref<BLTAnimationNode>> input_nodes;
|
||||
LocalVector<AnimationData *> input_data;
|
||||
@ -549,53 +651,144 @@ public:
|
||||
};
|
||||
LocalVector<NodeRuntimeData> _node_runtime_data;
|
||||
|
||||
Ref<BLTAnimationNode> get_output_node() const {
|
||||
return tree_graph.nodes[0];
|
||||
void set_graph_offset(const Vector2 &p_graph_offset) {
|
||||
graph_offset = p_graph_offset;
|
||||
}
|
||||
|
||||
Vector2 get_graph_offset() const {
|
||||
return graph_offset;
|
||||
}
|
||||
|
||||
int find_node_index(const Ref<BLTAnimationNode> &node) const {
|
||||
return tree_graph.find_node_index(node);
|
||||
}
|
||||
|
||||
int find_node_index_by_name(const StringName &name) const {
|
||||
return tree_graph.find_node_index_by_name(name);
|
||||
int find_node_index_by_name(const StringName &p_name) const {
|
||||
return tree_graph.find_node_index_by_name(p_name);
|
||||
}
|
||||
|
||||
Ref<BLTAnimationNode> get_node(int node_index) {
|
||||
if (node_index < 0 || node_index > tree_graph.nodes.size()) {
|
||||
void add_node(const Ref<BLTAnimationNode> &node) {
|
||||
tree_graph.add_node(node);
|
||||
|
||||
if (_graph_evaluation_context != nullptr) {
|
||||
node->initialize(*_graph_evaluation_context);
|
||||
}
|
||||
}
|
||||
|
||||
void remove_node(const Ref<BLTAnimationNode> &node) {
|
||||
if (tree_graph.remove_node(node)) {
|
||||
_node_changed();
|
||||
}
|
||||
}
|
||||
|
||||
TypedArray<StringName> get_node_names_as_typed_array() const {
|
||||
Vector<StringName> vec;
|
||||
for (const Ref<BLTAnimationNode> &node : tree_graph.nodes) {
|
||||
vec.push_back(node->get_name());
|
||||
}
|
||||
|
||||
TypedArray<StringName> typed_arr;
|
||||
typed_arr.resize(vec.size());
|
||||
for (uint32_t i = 0; i < vec.size(); i++) {
|
||||
typed_arr[i] = vec[i];
|
||||
}
|
||||
return typed_arr;
|
||||
}
|
||||
|
||||
Ref<BLTAnimationNode> get_node(const StringName &node_name) const {
|
||||
int node_index = tree_graph.find_node_index_by_name(node_name);
|
||||
|
||||
if (node_index >= 0) {
|
||||
return tree_graph.nodes[node_index];
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Ref<BLTAnimationNode> get_node_by_index(int node_index) const {
|
||||
if (node_index < 0 || node_index > static_cast<int>(tree_graph.nodes.size())) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return tree_graph.nodes[node_index];
|
||||
}
|
||||
|
||||
void add_node(const Ref<BLTAnimationNode> &node) {
|
||||
if (tree_initialized) {
|
||||
print_error("Cannot add node to BlendTree: BlendTree already initialized.");
|
||||
return;
|
||||
}
|
||||
Ref<BLTAnimationNode> get_output_node() const {
|
||||
return tree_graph.nodes[0];
|
||||
}
|
||||
|
||||
tree_graph.add_node(node);
|
||||
ConnectionError is_connection_valid(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, const StringName &target_port_name) {
|
||||
return tree_graph.is_connection_valid(source_node, target_node, target_port_name);
|
||||
}
|
||||
|
||||
ConnectionError add_connection(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, const StringName &target_port_name) {
|
||||
if (tree_initialized) {
|
||||
print_error("Cannot add connection to BlendTree: BlendTree already initialized.");
|
||||
return CONNECTION_ERROR_GRAPH_ALREADY_INITIALIZED;
|
||||
ConnectionError result = tree_graph.add_connection(source_node, target_node, target_port_name);
|
||||
if (result == CONNECTION_OK) {
|
||||
_node_changed();
|
||||
}
|
||||
|
||||
return tree_graph.add_connection(source_node, target_node, target_port_name);
|
||||
return result;
|
||||
}
|
||||
|
||||
// overrides from SyncedAnimationNode
|
||||
ConnectionError remove_connection(const Ref<BLTAnimationNode> &source_node, const Ref<BLTAnimationNode> &target_node, const StringName &target_port_name) {
|
||||
ConnectionError result = tree_graph.remove_connection(source_node, target_node, target_port_name);
|
||||
if (result == CONNECTION_OK) {
|
||||
_node_changed();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Array get_connections_as_array() const {
|
||||
Array result;
|
||||
for (const BLTBlendTreeConnection &connection : tree_graph.connections) {
|
||||
result.push_back(connection.source_node);
|
||||
result.push_back(connection.target_node);
|
||||
result.push_back(connection.target_port_name);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void _tree_node_changed(const StringName &node_name) {
|
||||
_node_changed();
|
||||
}
|
||||
|
||||
// overrides from BLTAnimationNode
|
||||
bool initialize(GraphEvaluationContext &context) override {
|
||||
tree_initialized = false;
|
||||
|
||||
if (!BLTAnimationNode::initialize(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_graph_evaluation_context = &context;
|
||||
|
||||
sort_nodes();
|
||||
setup_runtime_data();
|
||||
|
||||
for (const Ref<BLTAnimationNode> &node : tree_graph.nodes) {
|
||||
const HashSet<int> &output_subtree = tree_graph.node_connection_info[0].input_subtree_node_indices;
|
||||
|
||||
for (int i = 0; i < tree_graph.nodes.size(); i++) {
|
||||
const Ref<BLTAnimationNode> &node = tree_graph.nodes[i];
|
||||
|
||||
// Initialize, but skip validation of nodes that are not part of the active tree.
|
||||
if (!output_subtree.has(i)) {
|
||||
node->initialize(context);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!node->initialize(context)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const NodeRuntimeData &node_runtime_data = _node_runtime_data[i];
|
||||
|
||||
for (const Ref<BLTAnimationNode> &input_node : node_runtime_data.input_nodes) {
|
||||
if (!input_node.is_valid()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tree_initialized = true;
|
||||
@ -607,8 +800,15 @@ public:
|
||||
activate_inputs(const Vector<Ref<BLTAnimationNode>> &input_nodes) override {
|
||||
GodotProfileZone("SyncedBlendTree::activate_inputs");
|
||||
|
||||
// TODO: add checking whether tree can be evaluated, i.e. whether all inputs are properly connected.
|
||||
if (tree_graph.nodes.size() == 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
tree_graph.nodes[0]->active = true;
|
||||
for (int i = 0; i < tree_graph.nodes.size(); i++) {
|
||||
tree_graph.nodes[0]->node_time_info.is_synced = node_time_info.is_synced;
|
||||
|
||||
for (uint32_t i = 0; i < tree_graph.nodes.size(); i++) {
|
||||
const Ref<BLTAnimationNode> &node = tree_graph.nodes[i];
|
||||
|
||||
if (!node->active) {
|
||||
@ -622,7 +822,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) {
|
||||
@ -632,16 +832,23 @@ public:
|
||||
const NodeRuntimeData &node_runtime_data = _node_runtime_data[i];
|
||||
|
||||
node->calculate_sync_track(node_runtime_data.input_nodes);
|
||||
|
||||
if (i == 1) {
|
||||
node_time_info = node->node_time_info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void update_time(double p_delta) override {
|
||||
GodotProfileZone("SyncedBlendTree::update_time");
|
||||
|
||||
tree_graph.nodes[0]->node_time_info.delta = p_delta;
|
||||
tree_graph.nodes[0]->node_time_info.position += p_delta;
|
||||
BLTAnimationNode::update_time(p_delta);
|
||||
|
||||
for (int i = 1; i < tree_graph.nodes.size(); i++) {
|
||||
tree_graph.nodes[0]->node_time_info.delta = node_time_info.delta;
|
||||
tree_graph.nodes[0]->node_time_info.position = node_time_info.position;
|
||||
tree_graph.nodes[0]->node_time_info.sync_position = node_time_info.sync_position;
|
||||
|
||||
for (uint32_t i = 1; i < tree_graph.nodes.size(); i++) {
|
||||
const Ref<BLTAnimationNode> &node = tree_graph.nodes[i];
|
||||
|
||||
if (!node->active) {
|
||||
@ -659,9 +866,9 @@ public:
|
||||
}
|
||||
|
||||
void evaluate(GraphEvaluationContext &context, const LocalVector<AnimationData *> &input_datas, AnimationData &output_data) override {
|
||||
ZoneScopedN("SyncedBlendTree::evaluate");
|
||||
GodotProfileZone("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) {
|
||||
@ -689,6 +896,9 @@ public:
|
||||
for (const int child_index : tree_graph.node_connection_info[i].connected_child_node_index_at_port) {
|
||||
context.animation_data_allocator.free(_node_runtime_data[child_index].output_data);
|
||||
}
|
||||
|
||||
// Node must be deactivated. It'll be activated when actually used next time.
|
||||
node->active = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
22
blendalot_math_helper.h
Normal file
22
blendalot_math_helper.h
Normal file
@ -0,0 +1,22 @@
|
||||
//
|
||||
// Created by martin on 20.02.26.
|
||||
//
|
||||
|
||||
#ifndef MASTER_BLENDALOT_MATH_HELPER_H
|
||||
#define MASTER_BLENDALOT_MATH_HELPER_H
|
||||
|
||||
inline int greatest_common_divisor(int a, int b) {
|
||||
while (b != 0) {
|
||||
int temp = b;
|
||||
b = a % b;
|
||||
a = temp;
|
||||
}
|
||||
|
||||
return a;
|
||||
}
|
||||
|
||||
inline int least_common_multiple(int a, int b) {
|
||||
return (a / greatest_common_divisor(a, b)) * b;
|
||||
}
|
||||
|
||||
#endif //MASTER_BLENDALOT_MATH_HELPER_H
|
||||
110
demo/addons/blendalot/animation_graph_editor.gd
Normal file
110
demo/addons/blendalot/animation_graph_editor.gd
Normal file
@ -0,0 +1,110 @@
|
||||
@tool
|
||||
|
||||
extends Control
|
||||
class_name AnimationGraphEditor
|
||||
|
||||
@onready var breadcrumb_button_container: HBoxContainer = %BreadcrumbButtons
|
||||
@onready var active_graph_control: Control = %ActiveGraphControl
|
||||
|
||||
var animation_graph:BLTAnimationGraph = null
|
||||
var animation_graph_root_node:BLTAnimationNode = null
|
||||
var graph_node_stack:Array[BLTAnimationNode] = []
|
||||
var active_graph_edit:Control = null
|
||||
var active_graph_edit_index = -1
|
||||
|
||||
|
||||
func reset_graph_control():
|
||||
for child in active_graph_control.get_children():
|
||||
active_graph_control.remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
|
||||
func edit_animation_root_node(blt_node:BLTAnimationNode):
|
||||
print("Setting root node")
|
||||
graph_node_stack = []
|
||||
active_graph_edit_index = -1
|
||||
truncate_graph_stack(0)
|
||||
|
||||
blt_node.resource_name = "Root"
|
||||
|
||||
if blt_node is BLTAnimationNodeBlendTree:
|
||||
animation_graph_root_node = blt_node
|
||||
push_graph_stack(blt_node)
|
||||
edit_graph(blt_node)
|
||||
return
|
||||
|
||||
assert(is_instance_valid(animation_graph))
|
||||
|
||||
push_warning("Cannot edit node %s. Graph type %s not yet supported." % [blt_node.resource_name, blt_node.get_class()])
|
||||
|
||||
|
||||
func push_graph_stack(blt_node:BLTAnimationNode):
|
||||
graph_node_stack.append(blt_node)
|
||||
active_graph_edit_index = graph_node_stack.size() - 1
|
||||
|
||||
var breadcrumb_button:Button = Button.new()
|
||||
breadcrumb_button_container.add_child(breadcrumb_button)
|
||||
breadcrumb_button.text = blt_node.resource_name
|
||||
breadcrumb_button.toggle_mode = true
|
||||
breadcrumb_button.set_meta("BLTAnimationNode", blt_node)
|
||||
breadcrumb_button.set_meta("graph_edit_index", active_graph_edit_index)
|
||||
breadcrumb_button.pressed.connect(on_breadcrumb_button_pressed.bind(active_graph_edit_index))
|
||||
|
||||
|
||||
func truncate_graph_stack(level:int):
|
||||
graph_node_stack.resize(level)
|
||||
|
||||
var is_above_stack_size = false
|
||||
for child in breadcrumb_button_container.get_children():
|
||||
if is_above_stack_size:
|
||||
breadcrumb_button_container.remove_child(child)
|
||||
child.queue_free()
|
||||
continue
|
||||
|
||||
var button:Button = child as Button
|
||||
if not is_instance_valid(button):
|
||||
continue
|
||||
|
||||
if button.get_meta("graph_edit_index") >= graph_node_stack.size():
|
||||
is_above_stack_size = true
|
||||
breadcrumb_button_container.remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
|
||||
func on_breadcrumb_button_pressed(graph_edit_index:int):
|
||||
print("on_breadcrumb_button_pressed(%d)" % graph_edit_index)
|
||||
active_graph_edit_index = graph_edit_index
|
||||
update_breadcrumb_button_container()
|
||||
|
||||
edit_graph(graph_node_stack[graph_edit_index])
|
||||
|
||||
|
||||
func update_breadcrumb_button_container():
|
||||
for child in breadcrumb_button_container.get_children():
|
||||
var button:Button = child as Button
|
||||
if not is_instance_valid(button):
|
||||
continue
|
||||
|
||||
if button.get_meta("graph_edit_index") == active_graph_edit_index:
|
||||
button.set_pressed_no_signal(true)
|
||||
else:
|
||||
button.set_pressed_no_signal(false)
|
||||
|
||||
|
||||
func edit_graph(blt_node:BLTAnimationNode):
|
||||
if blt_node is BLTAnimationNodeBlendTree:
|
||||
reset_graph_control()
|
||||
var blend_tree_graph_edit:BltBlendTreeEditor = preload ("res://addons/blendalot/blend_tree_editor.tscn").instantiate()
|
||||
active_graph_control.add_child(blend_tree_graph_edit)
|
||||
blend_tree_graph_edit.edit_blend_tree(blt_node)
|
||||
blend_tree_graph_edit.edit_subgraph.connect(handle_subgraph_edit)
|
||||
active_graph_edit = blend_tree_graph_edit
|
||||
else:
|
||||
push_error("Cannot edit graph of node type %s" % blt_node.get_class())
|
||||
|
||||
|
||||
func handle_subgraph_edit(blt_node:BLTAnimationNode):
|
||||
print("handling subgraph edit of node %s" % blt_node.resource_name)
|
||||
truncate_graph_stack(active_graph_edit_index + 1)
|
||||
push_graph_stack(blt_node)
|
||||
edit_graph(blt_node)
|
||||
1
demo/addons/blendalot/animation_graph_editor.gd.uid
Normal file
1
demo/addons/blendalot/animation_graph_editor.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://bxxipuj2s5gxu
|
||||
83
demo/addons/blendalot/animation_graph_editor.tscn
Normal file
83
demo/addons/blendalot/animation_graph_editor.tscn
Normal file
@ -0,0 +1,83 @@
|
||||
[gd_scene format=3 uid="uid://bk5mssvanwjnh"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://bxxipuj2s5gxu" path="res://addons/blendalot/animation_graph_editor.gd" id="1_un1ur"]
|
||||
[ext_resource type="PackedScene" uid="uid://cptd46rpm0gl3" path="res://addons/blendalot/blend_tree_editor.tscn" id="2_utax0"]
|
||||
|
||||
[node name="AnimationGraphEditor" type="VBoxContainer" unique_id=768619585]
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1_un1ur")
|
||||
|
||||
[node name="DebugContainer" type="HBoxContainer" parent="." unique_id=1984631897]
|
||||
visible = false
|
||||
layout_mode = 2
|
||||
|
||||
[node name="ResetGraphButton" type="Button" parent="DebugContainer" unique_id=670074781]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
text = "Reset"
|
||||
|
||||
[node name="FileNameLineEdit" type="LineEdit" parent="DebugContainer" unique_id=724929522]
|
||||
unique_name_in_owner = true
|
||||
custom_minimum_size = Vector2(250, 0)
|
||||
layout_mode = 2
|
||||
text = "editor_test_tree.tres"
|
||||
|
||||
[node name="SaveButton" type="Button" parent="DebugContainer" unique_id=706843675]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
text = "Save
|
||||
"
|
||||
|
||||
[node name="LoadButton" type="Button" parent="DebugContainer" unique_id=1467831200]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
text = "Load
|
||||
"
|
||||
|
||||
[node name="ReinitializeButton" type="Button" parent="DebugContainer" unique_id=281924859]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
text = "Reinitialize"
|
||||
|
||||
[node name="TreeOptionButton" type="OptionButton" parent="DebugContainer" unique_id=2103827540]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
selected = 0
|
||||
item_count = 2
|
||||
popup/item_0/text = "AnimationSampler"
|
||||
popup/item_0/id = 1
|
||||
popup/item_1/text = "Blend2"
|
||||
popup/item_1/id = 2
|
||||
|
||||
[node name="InstantiateTreeButton" type="Button" parent="DebugContainer" unique_id=735069321]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
text = "Instantiate"
|
||||
|
||||
[node name="NavigationBar" type="MarginContainer" parent="." unique_id=815609909]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="Panel" type="Panel" parent="NavigationBar" unique_id=996737763]
|
||||
layout_mode = 2
|
||||
|
||||
[node name="BreadcrumbButtons" type="HBoxContainer" parent="NavigationBar" unique_id=1375619232]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
|
||||
[node name="PathLabel" type="Label" parent="NavigationBar/BreadcrumbButtons" unique_id=1544570774]
|
||||
layout_mode = 2
|
||||
text = "Path:"
|
||||
|
||||
[node name="ActiveGraphControl" type="Control" parent="." unique_id=1769528277]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="BlendTreeEditor" parent="ActiveGraphControl" unique_id=1313738200 instance=ExtResource("2_utax0")]
|
||||
layout_mode = 1
|
||||
275
demo/addons/blendalot/blend_tree_editor.gd
Normal file
275
demo/addons/blendalot/blend_tree_editor.gd
Normal file
@ -0,0 +1,275 @@
|
||||
@tool
|
||||
|
||||
extends Control
|
||||
class_name BltBlendTreeEditor
|
||||
|
||||
@onready var blend_tree_graph_edit: GraphEdit = %BlendTreeGraphEdit
|
||||
@onready var add_node_popup_menu: PopupMenu = %AddNodePopupMenu
|
||||
|
||||
signal edit_subgraph(blt_node:BLTAnimationNode)
|
||||
signal graph_changed()
|
||||
|
||||
var blend_tree:BLTAnimationNodeBlendTree
|
||||
|
||||
var blend_tree_node_to_graph_node = {}
|
||||
var graph_node_to_blend_tree_node = {}
|
||||
|
||||
var selected_nodes = {}
|
||||
var last_selected_graph_node:GraphNode = null
|
||||
var new_node_position:Vector2 = Vector2.ZERO
|
||||
|
||||
var registered_nodes = [
|
||||
"BLTAnimationNodeSampler",
|
||||
"BLTAnimationNodeBlend2",
|
||||
"BLTAnimationNodeBlendTree",
|
||||
]
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
add_node_popup_menu.clear(true)
|
||||
|
||||
for node_name in registered_nodes:
|
||||
add_node_popup_menu.add_item(node_name)
|
||||
|
||||
|
||||
func _reset_editor():
|
||||
for child in blend_tree_graph_edit.get_children():
|
||||
if child.name == "_connection_layer":
|
||||
continue
|
||||
|
||||
child.get_parent().remove_child(child)
|
||||
child.queue_free()
|
||||
|
||||
blend_tree_graph_edit.clear_connections()
|
||||
|
||||
blend_tree = null
|
||||
blend_tree_node_to_graph_node = {}
|
||||
graph_node_to_blend_tree_node = {}
|
||||
selected_nodes = {}
|
||||
|
||||
|
||||
func edit_blend_tree(blt_blend_tree:BLTAnimationNodeBlendTree):
|
||||
_reset_editor()
|
||||
blend_tree = blt_blend_tree
|
||||
blend_tree_graph_edit.scroll_offset = blend_tree.graph_offset
|
||||
|
||||
_update_editor_nodes_from_blend_tree()
|
||||
_update_editor_connections_from_blend_tree()
|
||||
|
||||
|
||||
func _update_editor_nodes_from_blend_tree():
|
||||
for node_name in blend_tree.get_node_names():
|
||||
var blend_tree_node:BLTAnimationNode = blend_tree.get_node(node_name)
|
||||
var graph_node:GraphNode = create_graph_node_for_blt_node(blend_tree_node)
|
||||
blend_tree_graph_edit.add_child(graph_node)
|
||||
|
||||
blend_tree_node_to_graph_node[blend_tree_node] = graph_node
|
||||
graph_node_to_blend_tree_node[graph_node] = blend_tree_node
|
||||
|
||||
|
||||
func _update_editor_connections_from_blend_tree():
|
||||
var connection_array = blend_tree.get_connections()
|
||||
|
||||
for i in range(len(connection_array) / 3):
|
||||
var source_node:BLTAnimationNode = connection_array[i * 3]
|
||||
var target_node:BLTAnimationNode = connection_array[i * 3 + 1]
|
||||
var target_port = connection_array[i * 3 + 2]
|
||||
|
||||
var source_graph_node = blend_tree_node_to_graph_node[source_node]
|
||||
|
||||
var connect_result = blend_tree_graph_edit.connect_node(source_node.resource_name, 0, target_node.resource_name, target_node.get_input_index(target_port), true)
|
||||
|
||||
|
||||
func create_graph_node_for_blt_node(blt_node: BLTAnimationNode) -> GraphNode:
|
||||
var result_graph_node:GraphNode = GraphNode.new()
|
||||
result_graph_node.name = blt_node.resource_name
|
||||
result_graph_node.title = blt_node.resource_name
|
||||
result_graph_node.position_offset = blt_node.position
|
||||
|
||||
var result_slot_offset = 0
|
||||
|
||||
if (blt_node.get_class() != "BLTAnimationNodeOutput"):
|
||||
result_slot_offset = 1
|
||||
var output_slot_label:Label = Label.new()
|
||||
output_slot_label.text = "Result"
|
||||
result_graph_node.add_child(output_slot_label)
|
||||
result_graph_node.set_slot(0, false, 1, Color.WHITE, true, 1, Color.WHITE)
|
||||
|
||||
if blt_node.get_class() == "BLTAnimationNodeBlendTree":
|
||||
result_graph_node.gui_input.connect(_on_node_gui_input.bind(result_graph_node))
|
||||
|
||||
var inputs = blt_node.get_input_names()
|
||||
for i in range(len(inputs)):
|
||||
var slot_label:Label = Label.new()
|
||||
slot_label.text = inputs[i]
|
||||
result_graph_node.add_child(slot_label)
|
||||
result_graph_node.set_slot(i + result_slot_offset, true, 1, Color.WHITE, false, 1, Color.BLACK)
|
||||
|
||||
if blt_node.get_class() == "BLTAnimationNodeSampler":
|
||||
var animation_sampler_node:BLTAnimationNodeSampler = blt_node as BLTAnimationNodeSampler
|
||||
var animation_selector_button = OptionButton.new()
|
||||
var animation_player:AnimationPlayer = animation_sampler_node.get_animation_player()
|
||||
for animation_name in animation_player.get_animation_list():
|
||||
animation_selector_button.add_item(animation_name)
|
||||
if animation_name == animation_sampler_node.animation:
|
||||
animation_selector_button.select(animation_selector_button.item_count - 1)
|
||||
|
||||
animation_selector_button.item_selected.connect(_on_animation_select.bind(animation_sampler_node, animation_selector_button))
|
||||
|
||||
result_graph_node.add_child(animation_selector_button)
|
||||
|
||||
blt_node.node_changed.connect(_trigger_graph_changed)
|
||||
|
||||
return result_graph_node
|
||||
|
||||
|
||||
func _trigger_graph_changed(_node_name):
|
||||
graph_changed.emit()
|
||||
|
||||
|
||||
func _remove_node_connections(graph_node:GraphNode):
|
||||
var node_connections:Array = []
|
||||
|
||||
for connection:Dictionary in blend_tree_graph_edit.connections:
|
||||
if connection["from_node"] == graph_node.name or connection["to_node"] == graph_node.name:
|
||||
node_connections.append(connection)
|
||||
|
||||
for node_connection:Dictionary in node_connections:
|
||||
print("Removing connection %s" % str(node_connection))
|
||||
blend_tree_graph_edit.disconnect_node(node_connection["from_node"], node_connection["from_port"], node_connection["to_node"], node_connection["to_port"])
|
||||
|
||||
|
||||
#
|
||||
# GraphEdit signal handling
|
||||
#
|
||||
func _on_blend_tree_graph_edit_connection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
|
||||
print("Trying to connect '%s' port %d to node '%s' port %d" % [from_node, from_port, to_node, to_port])
|
||||
|
||||
var source_node:BLTAnimationNode = blend_tree.get_node(from_node)
|
||||
var target_node:BLTAnimationNode = blend_tree.get_node(to_node)
|
||||
|
||||
if target_node == null:
|
||||
push_error("Invalid connection, target node %s not found." % to_node)
|
||||
return
|
||||
|
||||
var target_node_port_name = target_node.get_input_names()[to_port]
|
||||
|
||||
var connection_result = blend_tree.is_connection_valid(source_node, target_node, target_node_port_name)
|
||||
if connection_result != blend_tree.CONNECTION_OK:
|
||||
push_error("Could not add connection (error %d)" % connection_result)
|
||||
return
|
||||
|
||||
blend_tree.add_connection(source_node, target_node, target_node_port_name)
|
||||
|
||||
var connect_result = blend_tree_graph_edit.connect_node(from_node, from_port, to_node, to_port, true)
|
||||
print("graph connect result: " + str(connect_result))
|
||||
|
||||
print("Success!")
|
||||
|
||||
|
||||
func _on_blend_tree_graph_edit_disconnection_request(from_node: StringName, from_port: int, to_node: StringName, to_port: int) -> void:
|
||||
var blend_tree_source_node = blend_tree.get_node(from_node)
|
||||
var blend_tree_target_node = blend_tree.get_node(to_node)
|
||||
var target_port_name = blend_tree_target_node.get_input_names()[to_port]
|
||||
blend_tree.remove_connection(blend_tree_source_node, blend_tree_target_node, target_port_name)
|
||||
|
||||
blend_tree_graph_edit.disconnect_node(from_node, from_port, to_node, to_port)
|
||||
|
||||
|
||||
func _on_blend_tree_graph_edit_delete_nodes_request(nodes: Array[StringName]) -> void:
|
||||
for node_name:StringName in nodes:
|
||||
print("remove node '%s'" % node_name)
|
||||
var blend_tree_node:BLTAnimationNode = blend_tree.get_node(node_name)
|
||||
|
||||
if blend_tree_node == null:
|
||||
push_error("Cannot delete node '%s': node not found." % node_name)
|
||||
continue
|
||||
|
||||
if blend_tree_node == blend_tree.get_output_node():
|
||||
push_warning("Output node not allowed to be removed.")
|
||||
continue
|
||||
|
||||
blend_tree_node.node_changed.disconnect(_trigger_graph_changed)
|
||||
|
||||
var graph_node:GraphNode = blend_tree_node_to_graph_node[blend_tree_node]
|
||||
blend_tree.remove_node(blend_tree_node)
|
||||
blend_tree_node_to_graph_node.erase(blend_tree_node)
|
||||
|
||||
_remove_node_connections(graph_node)
|
||||
graph_node_to_blend_tree_node.erase(graph_node)
|
||||
blend_tree_graph_edit.remove_child(graph_node)
|
||||
_on_blend_tree_graph_edit_node_deselected(graph_node)
|
||||
|
||||
EditorInterface.get_inspector().edit(null)
|
||||
|
||||
|
||||
func _on_blend_tree_graph_edit_end_node_move() -> void:
|
||||
for graph_node:GraphNode in selected_nodes.keys():
|
||||
graph_node_to_blend_tree_node[graph_node].position = graph_node.position_offset
|
||||
|
||||
|
||||
func _on_blend_tree_graph_edit_node_deselected(graph_node: Node) -> void:
|
||||
if selected_nodes.has(graph_node):
|
||||
selected_nodes.erase(graph_node)
|
||||
|
||||
|
||||
func _on_blend_tree_graph_edit_node_selected(graph_node: Node) -> void:
|
||||
selected_nodes[graph_node] = graph_node
|
||||
last_selected_graph_node = graph_node
|
||||
EditorInterface.get_inspector().edit(graph_node_to_blend_tree_node[graph_node])
|
||||
|
||||
|
||||
func _on_blend_tree_graph_edit_scroll_offset_changed(offset: Vector2) -> void:
|
||||
if is_instance_valid(blend_tree):
|
||||
blend_tree.graph_offset = offset
|
||||
|
||||
|
||||
#
|
||||
# AddNodePopupMenu
|
||||
#
|
||||
func _on_blend_tree_graph_edit_popup_request(at_position: Vector2) -> void:
|
||||
add_node_popup_menu.position = get_screen_position() + get_local_mouse_position()
|
||||
add_node_popup_menu.reset_size()
|
||||
add_node_popup_menu.popup()
|
||||
new_node_position = blend_tree_graph_edit.scroll_offset + at_position
|
||||
|
||||
|
||||
func _on_add_node_popup_menu_index_pressed(index: int) -> void:
|
||||
var new_blend_tree_node: BLTAnimationNode = ClassDB.instantiate(registered_nodes[index])
|
||||
blend_tree.add_node(new_blend_tree_node)
|
||||
|
||||
var graph_node:GraphNode = create_graph_node_for_blt_node(new_blend_tree_node)
|
||||
blend_tree_graph_edit.add_child(graph_node)
|
||||
|
||||
graph_node_to_blend_tree_node[graph_node] = new_blend_tree_node
|
||||
blend_tree_node_to_graph_node[new_blend_tree_node] = graph_node
|
||||
|
||||
if new_node_position != Vector2.INF:
|
||||
graph_node.position_offset = new_node_position
|
||||
new_blend_tree_node.position = new_node_position
|
||||
|
||||
new_node_position = Vector2.INF
|
||||
|
||||
|
||||
#
|
||||
# Handle Node double click
|
||||
#
|
||||
func _on_node_gui_input(input_event:InputEvent, graph_node:GraphNode):
|
||||
# print("Got input event on graph node %s!" % graph_node.name)
|
||||
|
||||
var mouse_button_event:InputEventMouseButton = input_event as InputEventMouseButton
|
||||
if mouse_button_event and mouse_button_event.double_click:
|
||||
_on_node_double_click(graph_node)
|
||||
|
||||
func _on_node_double_click(graph_node:GraphNode):
|
||||
var blend_tree_node:BLTAnimationNode = graph_node_to_blend_tree_node[graph_node]
|
||||
|
||||
if blend_tree_node is BLTAnimationNodeBlendTree:
|
||||
edit_subgraph.emit(blend_tree_node)
|
||||
|
||||
#
|
||||
# Animation selection for BltAnimationNodeSampler
|
||||
#
|
||||
func _on_animation_select(index:int, blt_node_sampler:BLTAnimationNodeSampler, option_button:OptionButton):
|
||||
blt_node_sampler.animation = option_button.get_item_text(index)
|
||||
blt_node_sampler.node_changed.emit(blt_node_sampler.resource_name)
|
||||
1
demo/addons/blendalot/blend_tree_editor.gd.uid
Normal file
1
demo/addons/blendalot/blend_tree_editor.gd.uid
Normal file
@ -0,0 +1 @@
|
||||
uid://dr0ndqekm21gy
|
||||
52
demo/addons/blendalot/blend_tree_editor.tscn
Normal file
52
demo/addons/blendalot/blend_tree_editor.tscn
Normal file
@ -0,0 +1,52 @@
|
||||
[gd_scene format=3 uid="uid://cptd46rpm0gl3"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dr0ndqekm21gy" path="res://addons/blendalot/blend_tree_editor.gd" id="1_0srhh"]
|
||||
|
||||
[node name="BlendTreeEditor" type="Control" unique_id=1313738200]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_0srhh")
|
||||
|
||||
[node name="Panel" type="Panel" parent="." unique_id=758924321]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[node name="AddNodePopupMenu" type="PopupMenu" parent="Panel" unique_id=108570539]
|
||||
unique_name_in_owner = true
|
||||
oversampling_override = 1.0
|
||||
item_count = 3
|
||||
item_0/text = "BLTAnimationNodeSampler"
|
||||
item_0/id = 0
|
||||
item_1/text = "BLTAnimationNodeBlend2"
|
||||
item_1/id = 1
|
||||
item_2/text = "BLTAnimationNodeBlendTree"
|
||||
item_2/id = 2
|
||||
|
||||
[node name="BlendTreeGraphEdit" type="GraphEdit" parent="Panel" unique_id=391120290]
|
||||
unique_name_in_owner = true
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
right_disconnects = true
|
||||
|
||||
[connection signal="index_pressed" from="Panel/AddNodePopupMenu" to="." method="_on_add_node_popup_menu_index_pressed"]
|
||||
[connection signal="connection_request" from="Panel/BlendTreeGraphEdit" to="." method="_on_blend_tree_graph_edit_connection_request"]
|
||||
[connection signal="delete_nodes_request" from="Panel/BlendTreeGraphEdit" to="." method="_on_blend_tree_graph_edit_delete_nodes_request"]
|
||||
[connection signal="disconnection_request" from="Panel/BlendTreeGraphEdit" to="." method="_on_blend_tree_graph_edit_disconnection_request"]
|
||||
[connection signal="end_node_move" from="Panel/BlendTreeGraphEdit" to="." method="_on_blend_tree_graph_edit_end_node_move"]
|
||||
[connection signal="node_deselected" from="Panel/BlendTreeGraphEdit" to="." method="_on_blend_tree_graph_edit_node_deselected"]
|
||||
[connection signal="node_selected" from="Panel/BlendTreeGraphEdit" to="." method="_on_blend_tree_graph_edit_node_selected"]
|
||||
[connection signal="popup_request" from="Panel/BlendTreeGraphEdit" to="." method="_on_blend_tree_graph_edit_popup_request"]
|
||||
[connection signal="scroll_offset_changed" from="Panel/BlendTreeGraphEdit" to="." method="_on_blend_tree_graph_edit_scroll_offset_changed"]
|
||||
@ -1,16 +0,0 @@
|
||||
@tool
|
||||
extends Control
|
||||
|
||||
|
||||
# Called when the node enters the scene tree for the first time.
|
||||
func _ready() -> void:
|
||||
pass # Replace with function body.
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta: float) -> void:
|
||||
pass
|
||||
|
||||
|
||||
func _on_hit_me_button_pressed() -> void:
|
||||
print("Hello from the main screen plugin!")
|
||||
@ -1 +0,0 @@
|
||||
uid://dvulvuytt81lw
|
||||
@ -1,33 +0,0 @@
|
||||
[gd_scene load_steps=2 format=3 uid="uid://31c6depvs0y1"]
|
||||
|
||||
[ext_resource type="Script" uid="uid://dvulvuytt81lw" path="res://addons/blendalot/blendalot_main_panel.gd" id="1_427jg"]
|
||||
|
||||
[node name="BlendalotMainPanel" type="Control" unique_id=1259518158]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
size_flags_horizontal = 3
|
||||
size_flags_vertical = 3
|
||||
script = ExtResource("1_427jg")
|
||||
|
||||
[node name="VBoxContainer" type="VBoxContainer" parent="." unique_id=2044593527]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
|
||||
[node name="HitMeButton" type="Button" parent="VBoxContainer" unique_id=1060776498]
|
||||
layout_mode = 2
|
||||
size_flags_horizontal = 0
|
||||
text = "Hit me!"
|
||||
|
||||
[node name="GraphFrame" type="GraphFrame" parent="VBoxContainer" unique_id=184673233]
|
||||
layout_mode = 2
|
||||
size_flags_vertical = 3
|
||||
|
||||
[connection signal="pressed" from="VBoxContainer/HitMeButton" to="." method="_on_hit_me_button_pressed"]
|
||||
@ -1,9 +1,8 @@
|
||||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
const MainPanel = preload("res://addons/blendalot/blendalot_main_panel.tscn")
|
||||
|
||||
var main_panel_instance
|
||||
var editor_dock:EditorDock = null
|
||||
var animation_graph_editor:AnimationGraphEditor = null
|
||||
|
||||
func _enable_plugin() -> void:
|
||||
# Add autoloads here.
|
||||
@ -16,25 +15,29 @@ func _disable_plugin() -> void:
|
||||
|
||||
|
||||
func _enter_tree() -> void:
|
||||
main_panel_instance = MainPanel.instantiate()
|
||||
# Add the main panel to the editor's main viewport.
|
||||
EditorInterface.get_editor_main_screen().add_child(main_panel_instance)
|
||||
# Hide the main panel. Very much required.
|
||||
_make_visible(false)
|
||||
editor_dock = EditorDock.new()
|
||||
editor_dock.title = "Animation Graph"
|
||||
editor_dock.default_slot = EditorDock.DOCK_SLOT_BOTTOM
|
||||
animation_graph_editor = preload ("res://addons/blendalot/animation_graph_editor.tscn").instantiate()
|
||||
editor_dock.add_child(animation_graph_editor)
|
||||
add_dock(editor_dock)
|
||||
|
||||
|
||||
func _exit_tree() -> void:
|
||||
if main_panel_instance:
|
||||
main_panel_instance.queue_free()
|
||||
remove_dock(editor_dock)
|
||||
editor_dock.queue_free()
|
||||
editor_dock = null
|
||||
|
||||
animation_graph_editor.queue_free()
|
||||
animation_graph_editor = null
|
||||
|
||||
|
||||
func _has_main_screen():
|
||||
return true
|
||||
func _has_main_screen() -> bool:
|
||||
return false
|
||||
|
||||
|
||||
func _make_visible(visible):
|
||||
if main_panel_instance:
|
||||
main_panel_instance.visible = visible
|
||||
pass
|
||||
|
||||
|
||||
func _get_plugin_name():
|
||||
@ -43,3 +46,19 @@ func _get_plugin_name():
|
||||
|
||||
func _get_plugin_icon():
|
||||
return EditorInterface.get_editor_theme().get_icon("Node", "EditorIcons")
|
||||
|
||||
|
||||
func _handles(obj: Object) -> bool:
|
||||
return obj is BLTAnimationNodeBlendTree
|
||||
|
||||
|
||||
func _edit(object: Object):
|
||||
if not is_instance_valid(animation_graph_editor):
|
||||
push_error("Cannot edit object as AnimationGraphEditor is not initialized")
|
||||
return
|
||||
|
||||
if object is BLTAnimationNodeBlendTree:
|
||||
animation_graph_editor.edit_animation_root_node(object)
|
||||
return
|
||||
|
||||
print("Cannot (yet) edit object " + str(object))
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
[gd_resource type="AnimationNodeBlendTree" load_steps=4 format=3 uid="uid://dqy0dgwsm8t46"]
|
||||
[gd_resource type="AnimationNodeBlendTree" format=3 uid="uid://dqy0dgwsm8t46"]
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_h2yge"]
|
||||
animation = &"Limping-InPlace"
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
[gd_resource type="AnimationNodeBlendTree" load_steps=4 format=3 uid="uid://vsf71o82lkld"]
|
||||
[gd_resource type="AnimationNodeBlendTree" format=3 uid="uid://vsf71o82lkld"]
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_h2yge"]
|
||||
animation = &"Run-InPlace"
|
||||
@ -8,7 +8,28 @@ animation = &"Walk-InPlace"
|
||||
|
||||
[sub_resource type="AnimationNodeBlend2" id="AnimationNodeBlend2_lquwl"]
|
||||
|
||||
[sub_resource type="AnimationNodeAnimation" id="AnimationNodeAnimation_vyt75"]
|
||||
|
||||
[sub_resource type="AnimationNodeBlend2" id="AnimationNodeBlend2_hom0r"]
|
||||
|
||||
[sub_resource type="AnimationNodeBlendTree" id="AnimationNodeBlendTree_vyt75"]
|
||||
|
||||
[sub_resource type="AnimationNodeBlendTree" id="AnimationNodeBlendTree_1rfsi"]
|
||||
nodes/BlendTree/node = SubResource("AnimationNodeBlendTree_vyt75")
|
||||
nodes/BlendTree/position = Vector2(694.9995, 215.55058)
|
||||
|
||||
[sub_resource type="AnimationNodeBlendTree" id="AnimationNodeBlendTree_8tpve"]
|
||||
graph_offset = Vector2(-782, 179.47485)
|
||||
nodes/Animation/node = SubResource("AnimationNodeAnimation_vyt75")
|
||||
nodes/Animation/position = Vector2(-320, 140)
|
||||
nodes/Blend2/node = SubResource("AnimationNodeBlend2_hom0r")
|
||||
nodes/Blend2/position = Vector2(-115.66393, 127.37674)
|
||||
nodes/BlendTree/node = SubResource("AnimationNodeBlendTree_1rfsi")
|
||||
nodes/BlendTree/position = Vector2(-480, 400)
|
||||
node_connections = [&"output", 0, &"Blend2", &"Blend2", 0, &"Animation", &"Blend2", 1, &"BlendTree"]
|
||||
|
||||
[resource]
|
||||
graph_offset = Vector2(-217.4643, 82.84979)
|
||||
nodes/output/position = Vector2(540, 140)
|
||||
nodes/Animation/node = SubResource("AnimationNodeAnimation_1bvp3")
|
||||
nodes/Animation/position = Vector2(120, 80)
|
||||
@ -16,4 +37,6 @@ nodes/Animation/position = Vector2(120, 80)
|
||||
"nodes/Animation 2/position" = Vector2(80, 320)
|
||||
nodes/Blend2/node = SubResource("AnimationNodeBlend2_lquwl")
|
||||
nodes/Blend2/position = Vector2(360, 180)
|
||||
nodes/BlendTree/node = SubResource("AnimationNodeBlendTree_8tpve")
|
||||
nodes/BlendTree/position = Vector2(778.0867, 295.33868)
|
||||
node_connections = [&"output", 0, &"Blend2", &"Blend2", 0, &"Animation", &"Blend2", 1, &"Animation 2"]
|
||||
|
||||
13
demo/main.gd
13
demo/main.gd
@ -12,19 +12,6 @@ extends Node3D
|
||||
func _ready() -> void:
|
||||
blend_weight_slider.value = 0.5
|
||||
|
||||
var blend_tree: BLTAnimationNodeBlendTree = BLTAnimationNodeBlendTree.new()
|
||||
var output_node: BLTAnimationNodeOutput = blend_tree.get_output_node()
|
||||
var sampler_node_1: BLTAnimationNodeSampler = BLTAnimationNodeSampler.new()
|
||||
|
||||
sampler_node_1.animation = "animation_library/Walk-InPlace"
|
||||
|
||||
blend_tree.add_node(sampler_node_1)
|
||||
var result = blend_tree.add_connection(sampler_node_1, output_node, "Input")
|
||||
var anim_graph: BLTAnimationGraph = mixamo_amy_walk_run_synced.get_node("SyncedAnimationGraph")
|
||||
|
||||
anim_graph.tree_root = blend_tree
|
||||
|
||||
|
||||
# Called every frame. 'delta' is the elapsed time since the previous frame.
|
||||
func _process(delta: float) -> void:
|
||||
pass
|
||||
|
||||
171
demo/main.tscn
171
demo/main.tscn
@ -1,12 +1,11 @@
|
||||
[gd_scene load_steps=14 format=3 uid="uid://svj53e2xoio"]
|
||||
[gd_scene format=3 uid="uid://svj53e2xoio"]
|
||||
|
||||
[ext_resource type="PackedScene" uid="uid://d1xcqdqr1qeu6" path="res://assets/MixamoAmy.glb" id="1_0xm2m"]
|
||||
[ext_resource type="Script" uid="uid://bjvgqujpqumj7" path="res://main.gd" id="1_1bvp3"]
|
||||
[ext_resource type="AnimationLibrary" uid="uid://dwubn740aqx51" path="res://animation_library.res" id="3_1bvp3"]
|
||||
[ext_resource type="AnimationNodeBlendTree" uid="uid://dqy0dgwsm8t46" path="res://animation_tree_walk_limp.tres" id="3_272bh"]
|
||||
[ext_resource type="BLTAnimationNodeBlendTree" uid="uid://2qfwr1xkiw0s" path="res://synced_blend_tree_walk_limp.tres" id="4_lquwl"]
|
||||
[ext_resource type="BLTAnimationNodeBlendTree" uid="uid://qsk64ax2o47f" path="res://synced_blend_tree_walk_run.tres" id="5_7mycd"]
|
||||
[ext_resource type="AnimationNodeBlendTree" uid="uid://vsf71o82lkld" path="res://animation_tree_walk_run.tres" id="6_5vw27"]
|
||||
[ext_resource type="BLTAnimationNodeBlendTree" uid="uid://2qfwr1xkiw0s" path="res://synced_blend_tree_walk_limp.tres" id="6_272bh"]
|
||||
|
||||
[sub_resource type="Theme" id="Theme_272bh"]
|
||||
default_font_size = 30
|
||||
@ -32,6 +31,40 @@ sky = SubResource("Sky_1bvp3")
|
||||
tonemap_mode = 2
|
||||
glow_enabled = true
|
||||
|
||||
[sub_resource type="BLTAnimationNodeBlend2" id="BLTAnimationNodeBlend2_7mycd"]
|
||||
resource_name = "BLTAnimationNodeBlend2"
|
||||
position = Vector2(-320, -40)
|
||||
blend_amount = 0.81
|
||||
|
||||
[sub_resource type="BLTAnimationNodeSampler" id="BLTAnimationNodeSampler_272bh"]
|
||||
resource_name = "BLTAnimationNodeSampler"
|
||||
position = Vector2(-490, 7)
|
||||
animation = &"animation_library/Walk-InPlace"
|
||||
|
||||
[sub_resource type="BLTAnimationNodeBlendTree" id="BLTAnimationNodeBlendTree_5vw27"]
|
||||
resource_name = "BLTAnimationNodeBlendTree"
|
||||
position = Vector2(-640, -20)
|
||||
graph_offset = Vector2(-760.67163, -24.823944)
|
||||
nodes/BLTAnimationNodeSampler/node = SubResource("BLTAnimationNodeSampler_272bh")
|
||||
nodes/BLTAnimationNodeSampler/graph_offset = Vector2(-490, 7)
|
||||
node_connections = ["Output", 0, "BLTAnimationNodeSampler"]
|
||||
|
||||
[sub_resource type="BLTAnimationNodeSampler" id="BLTAnimationNodeSampler_kek77"]
|
||||
resource_name = "BLTAnimationNodeSampler"
|
||||
position = Vector2(-620, 140)
|
||||
animation = &"animation_library/Run-InPlace"
|
||||
|
||||
[sub_resource type="BLTAnimationNodeBlendTree" id="BLTAnimationNodeBlendTree_7mycd"]
|
||||
resource_name = "Root"
|
||||
graph_offset = Vector2(-869, -71)
|
||||
nodes/BLTAnimationNodeBlend2/node = SubResource("BLTAnimationNodeBlend2_7mycd")
|
||||
nodes/BLTAnimationNodeBlend2/graph_offset = Vector2(-320, -40)
|
||||
nodes/BLTAnimationNodeSampler/node = SubResource("BLTAnimationNodeSampler_kek77")
|
||||
nodes/BLTAnimationNodeSampler/graph_offset = Vector2(-620, 140)
|
||||
nodes/BLTAnimationNodeBlendTree/node = SubResource("BLTAnimationNodeBlendTree_5vw27")
|
||||
nodes/BLTAnimationNodeBlendTree/graph_offset = Vector2(-640, -20)
|
||||
node_connections = ["Output", 0, "BLTAnimationNodeBlend2", "BLTAnimationNodeBlend2", 0, "BLTAnimationNodeBlendTree", "BLTAnimationNodeBlend2", 1, "BLTAnimationNodeSampler"]
|
||||
|
||||
[node name="Main" type="Node3D" unique_id=933302313]
|
||||
script = ExtResource("1_1bvp3")
|
||||
|
||||
@ -137,6 +170,7 @@ transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, -0.6, 0, 0)
|
||||
tree_root = ExtResource("6_5vw27")
|
||||
anim_player = NodePath("../AnimationPlayer")
|
||||
parameters/Blend2/blend_amount = 0.0
|
||||
parameters/BlendTree/Blend2/blend_amount = 0.0
|
||||
|
||||
[node name="MixamoAmyWalkLimpSynced" parent="Characters" unique_id=1018815116 instance=ExtResource("1_0xm2m")]
|
||||
unique_name_in_owner = true
|
||||
@ -147,22 +181,145 @@ libraries/animation_library = ExtResource("3_1bvp3")
|
||||
|
||||
[node name="SyncedAnimationGraph" type="BLTAnimationGraph" parent="Characters/MixamoAmyWalkLimpSynced" unique_id=1866796918]
|
||||
animation_player = NodePath("../AnimationPlayer2")
|
||||
tree_root = ExtResource("4_lquwl")
|
||||
tree_root = ExtResource("6_272bh")
|
||||
skeleton = NodePath("../Armature/Skeleton3D")
|
||||
parameters/BLTAnimationNodeBlend2/blend_amount = 0.4
|
||||
parameters/BLTAnimationNodeBlend2/blend_amount = 1.0
|
||||
|
||||
[node name="MixamoAmyWalkRunSynced" parent="Characters" unique_id=2088190993 instance=ExtResource("1_0xm2m")]
|
||||
unique_name_in_owner = true
|
||||
transform = Transform3D(1, 0, 0, 0, 1, 0, 0, 0, 1, 1.4, 0, 0)
|
||||
|
||||
[node name="Skeleton3D" parent="Characters/MixamoAmyWalkRunSynced/Armature" parent_id_path=PackedInt32Array(2088190993, 1791722621) index="0" unique_id=1831928682]
|
||||
bones/2/position = Vector3(0, 0, 0)
|
||||
bones/2/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/3/position = Vector3(0, 0, 0)
|
||||
bones/3/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/4/position = Vector3(0, 0, 0)
|
||||
bones/4/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/5/position = Vector3(0, 0, 0)
|
||||
bones/5/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/6/position = Vector3(0, 0, 0)
|
||||
bones/6/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/7/position = Vector3(0, 0, 0)
|
||||
bones/7/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/8/position = Vector3(0, 0, 0)
|
||||
bones/9/position = Vector3(0, 0, 0)
|
||||
bones/9/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/10/position = Vector3(0, 0, 0)
|
||||
bones/10/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/11/position = Vector3(0, 0, 0)
|
||||
bones/11/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/12/position = Vector3(0, 0, 0)
|
||||
bones/12/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/13/position = Vector3(0, 0, 0)
|
||||
bones/13/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/14/position = Vector3(0, 0, 0)
|
||||
bones/14/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/15/position = Vector3(0, 0, 0)
|
||||
bones/15/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/16/position = Vector3(0, 0, 0)
|
||||
bones/17/position = Vector3(0, 0, 0)
|
||||
bones/17/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/18/position = Vector3(0, 0, 0)
|
||||
bones/18/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/19/position = Vector3(0, 0, 0)
|
||||
bones/19/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/20/position = Vector3(0, 0, 0)
|
||||
bones/21/position = Vector3(0, 0, 0)
|
||||
bones/21/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/22/position = Vector3(0, 0, 0)
|
||||
bones/22/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/23/position = Vector3(0, 0, 0)
|
||||
bones/23/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/24/position = Vector3(0, 0, 0)
|
||||
bones/25/position = Vector3(0, 0, 0)
|
||||
bones/25/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/26/position = Vector3(0, 0, 0)
|
||||
bones/26/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/27/position = Vector3(0, 0, 0)
|
||||
bones/27/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/28/position = Vector3(0, 0, 0)
|
||||
bones/29/position = Vector3(0, 0, 0)
|
||||
bones/29/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/30/position = Vector3(0, 0, 0)
|
||||
bones/30/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/31/position = Vector3(0, 0, 0)
|
||||
bones/31/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/32/position = Vector3(0, 0, 0)
|
||||
bones/33/position = Vector3(0, 0, 0)
|
||||
bones/33/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/34/position = Vector3(0, 0, 0)
|
||||
bones/34/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/35/position = Vector3(0, 0, 0)
|
||||
bones/35/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/36/position = Vector3(0, 0, 0)
|
||||
bones/36/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/37/position = Vector3(0, 0, 0)
|
||||
bones/37/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/38/position = Vector3(0, 0, 0)
|
||||
bones/38/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/39/position = Vector3(0, 0, 0)
|
||||
bones/39/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/40/position = Vector3(0, 0, 0)
|
||||
bones/41/position = Vector3(0, 0, 0)
|
||||
bones/41/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/42/position = Vector3(0, 0, 0)
|
||||
bones/42/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/43/position = Vector3(0, 0, 0)
|
||||
bones/43/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/44/position = Vector3(0, 0, 0)
|
||||
bones/45/position = Vector3(0, 0, 0)
|
||||
bones/45/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/46/position = Vector3(0, 0, 0)
|
||||
bones/46/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/47/position = Vector3(0, 0, 0)
|
||||
bones/47/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/48/position = Vector3(0, 0, 0)
|
||||
bones/49/position = Vector3(0, 0, 0)
|
||||
bones/49/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/50/position = Vector3(0, 0, 0)
|
||||
bones/50/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/51/position = Vector3(0, 0, 0)
|
||||
bones/51/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/52/position = Vector3(0, 0, 0)
|
||||
bones/53/position = Vector3(0, 0, 0)
|
||||
bones/53/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/54/position = Vector3(0, 0, 0)
|
||||
bones/54/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/55/position = Vector3(0, 0, 0)
|
||||
bones/55/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/56/position = Vector3(0, 0, 0)
|
||||
bones/57/position = Vector3(0, 0, 0)
|
||||
bones/57/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/58/position = Vector3(0, 0, 0)
|
||||
bones/58/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/59/position = Vector3(0, 0, 0)
|
||||
bones/59/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/60/position = Vector3(0, 0, 0)
|
||||
bones/60/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/61/position = Vector3(0, 0, 0)
|
||||
bones/62/position = Vector3(0, 0, 0)
|
||||
bones/62/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/63/position = Vector3(0, 0, 0)
|
||||
bones/63/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/64/position = Vector3(0, 0, 0)
|
||||
bones/64/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/65/position = Vector3(0, 0, 0)
|
||||
bones/65/rotation = Quaternion(0, 0, 0, 1)
|
||||
bones/66/position = Vector3(0, 0, 0)
|
||||
|
||||
[node name="AnimationPlayer" parent="Characters/MixamoAmyWalkRunSynced" index="1" unique_id=66984852]
|
||||
active = false
|
||||
|
||||
[node name="AnimationPlayer2" type="AnimationPlayer" parent="Characters/MixamoAmyWalkRunSynced" unique_id=1255239074]
|
||||
active = false
|
||||
libraries/animation_library = ExtResource("3_1bvp3")
|
||||
|
||||
[node name="SyncedAnimationGraph" type="BLTAnimationGraph" parent="Characters/MixamoAmyWalkRunSynced" unique_id=1602406394]
|
||||
animation_player = NodePath("../AnimationPlayer2")
|
||||
tree_root = ExtResource("5_7mycd")
|
||||
tree_root = SubResource("BLTAnimationNodeBlendTree_7mycd")
|
||||
skeleton = NodePath("../Armature/Skeleton3D")
|
||||
parameters/BLTAnimationNodeBlend2/blend_amount = 0.4
|
||||
parameters/BLTAnimationNodeBlend2/blend_amount = 0.81
|
||||
|
||||
[connection signal="value_changed" from="UI/MarginContainer/HBoxContainer/BlendWeightSlider" to="." method="_on_blend_weight_slider_value_changed"]
|
||||
|
||||
|
||||
@ -12,7 +12,7 @@ config_version=5
|
||||
|
||||
config/name="Synced Blend Tree Test"
|
||||
run/main_scene="uid://svj53e2xoio"
|
||||
config/features=PackedStringArray("4.5", "Forward Plus")
|
||||
config/features=PackedStringArray("4.6", "Forward Plus")
|
||||
config/icon="res://icon.svg"
|
||||
|
||||
[display]
|
||||
@ -23,3 +23,7 @@ window/size/viewport_height=1024
|
||||
[dotnet]
|
||||
|
||||
project/assembly_name="Synced Blend Tree Test"
|
||||
|
||||
[editor_plugins]
|
||||
|
||||
enabled=PackedStringArray("res://addons/blendalot/plugin.cfg")
|
||||
|
||||
@ -1,12 +1,15 @@
|
||||
[gd_resource type="BLTAnimationNodeBlendTree" load_steps=4 format=3]
|
||||
[gd_resource type="BLTAnimationNodeBlendTree" format=3]
|
||||
|
||||
[sub_resource type="BLTAnimationNodeSampler" id="BLTAnimationNodeSampler_bvt3d"]
|
||||
resource_name = "BLTAnimationNodeSampler 1"
|
||||
animation = &"animation_library/TestAnimationB"
|
||||
|
||||
[sub_resource type="BLTAnimationNodeSampler" id="BLTAnimationNodeSampler_sntl5"]
|
||||
resource_name = "BLTAnimationNodeSampler"
|
||||
animation = &"animation_library/TestAnimationA"
|
||||
|
||||
[sub_resource type="BLTAnimationNodeBlend2" id="BLTAnimationNodeBlend2_n4m28"]
|
||||
resource_name = "Blend2"
|
||||
sync = false
|
||||
blend_amount = 0.5
|
||||
sync = false
|
||||
@ -18,4 +21,4 @@ nodes/Blend2/position = Vector2(0, 0)
|
||||
"nodes/BLTAnimationNodeSampler 1/position" = Vector2(0, 0)
|
||||
nodes/BLTAnimationNodeSampler/node = SubResource("BLTAnimationNodeSampler_sntl5")
|
||||
nodes/BLTAnimationNodeSampler/position = Vector2(0, 0)
|
||||
node_connections = [&"Blend2", 0, &"BLTAnimationNodeSampler", &"Blend2", 1, &"BLTAnimationNodeSampler 1", &"Output", 0, &"Blend2"]
|
||||
node_connections = ["Blend2", 0, "BLTAnimationNodeSampler", "Blend2", 1, "BLTAnimationNodeSampler 1", "Output", 0, "Blend2"]
|
||||
|
||||
@ -1,19 +1,25 @@
|
||||
[gd_resource type="BLTAnimationNodeBlendTree" load_steps=4 format=3 uid="uid://2qfwr1xkiw0s"]
|
||||
[gd_resource type="BLTAnimationNodeBlendTree" format=3 uid="uid://2qfwr1xkiw0s"]
|
||||
|
||||
[sub_resource type="BLTAnimationNodeBlend2" id="BLTAnimationNodeBlend2_bvt3d"]
|
||||
blend_amount = 0.4
|
||||
resource_name = "BLTAnimationNodeBlend2"
|
||||
graph_offset = Vector2(-600, 180)
|
||||
blend_amount = 1.0
|
||||
|
||||
[sub_resource type="BLTAnimationNodeSampler" id="BLTAnimationNodeSampler_sntl5"]
|
||||
resource_name = "BLTAnimationNodeSampler 1"
|
||||
graph_offset = Vector2(-1200, 560)
|
||||
animation = &"animation_library/Limping-InPlace"
|
||||
|
||||
[sub_resource type="BLTAnimationNodeSampler" id="BLTAnimationNodeSampler_n4m28"]
|
||||
resource_name = "BLTAnimationNodeSampler"
|
||||
graph_offset = Vector2(-1300, -40)
|
||||
animation = &"animation_library/Walk-InPlace"
|
||||
|
||||
[resource]
|
||||
nodes/BLTAnimationNodeBlend2/node = SubResource("BLTAnimationNodeBlend2_bvt3d")
|
||||
nodes/BLTAnimationNodeBlend2/position = Vector2(0, 0)
|
||||
nodes/BLTAnimationNodeBlend2/graph_offset = Vector2(-600, 180)
|
||||
"nodes/BLTAnimationNodeSampler 1/node" = SubResource("BLTAnimationNodeSampler_sntl5")
|
||||
"nodes/BLTAnimationNodeSampler 1/position" = Vector2(0, 0)
|
||||
"nodes/BLTAnimationNodeSampler 1/graph_offset" = Vector2(-1200, 560)
|
||||
nodes/BLTAnimationNodeSampler/node = SubResource("BLTAnimationNodeSampler_n4m28")
|
||||
nodes/BLTAnimationNodeSampler/position = Vector2(0, 0)
|
||||
node_connections = [&"BLTAnimationNodeBlend2", 0, &"BLTAnimationNodeSampler", &"BLTAnimationNodeBlend2", 1, &"BLTAnimationNodeSampler 1", &"Output", 0, &"BLTAnimationNodeBlend2"]
|
||||
nodes/BLTAnimationNodeSampler/graph_offset = Vector2(-1300, -40)
|
||||
node_connections = ["BLTAnimationNodeBlend2", 0, "BLTAnimationNodeSampler", "BLTAnimationNodeBlend2", 1, "BLTAnimationNodeSampler 1", "Output", 0, "BLTAnimationNodeBlend2"]
|
||||
|
||||
@ -1,19 +1,25 @@
|
||||
[gd_resource type="BLTAnimationNodeBlendTree" load_steps=4 format=3 uid="uid://qsk64ax2o47f"]
|
||||
[gd_resource type="BLTAnimationNodeBlendTree" format=3 uid="uid://qsk64ax2o47f"]
|
||||
|
||||
[sub_resource type="BLTAnimationNodeBlend2" id="BLTAnimationNodeBlend2_bvt3d"]
|
||||
blend_amount = 0.4
|
||||
resource_name = "BLTAnimationNodeBlend2"
|
||||
graph_offset = Vector2(-360, 140)
|
||||
|
||||
[sub_resource type="BLTAnimationNodeSampler" id="BLTAnimationNodeSampler_sntl5"]
|
||||
resource_name = "BLTAnimationNodeSampler 1"
|
||||
graph_offset = Vector2(-1140, 440)
|
||||
animation = &"animation_library/Run-InPlace"
|
||||
|
||||
[sub_resource type="BLTAnimationNodeSampler" id="BLTAnimationNodeSampler_n4m28"]
|
||||
resource_name = "BLTAnimationNodeSampler"
|
||||
graph_offset = Vector2(-1080, -40)
|
||||
animation = &"animation_library/Walk-InPlace"
|
||||
|
||||
[resource]
|
||||
nodes/Output/position = Vector2(180, 80)
|
||||
nodes/BLTAnimationNodeBlend2/node = SubResource("BLTAnimationNodeBlend2_bvt3d")
|
||||
nodes/BLTAnimationNodeBlend2/position = Vector2(0, 0)
|
||||
nodes/BLTAnimationNodeBlend2/position = Vector2(-360, 140)
|
||||
"nodes/BLTAnimationNodeSampler 1/node" = SubResource("BLTAnimationNodeSampler_sntl5")
|
||||
"nodes/BLTAnimationNodeSampler 1/position" = Vector2(0, 0)
|
||||
"nodes/BLTAnimationNodeSampler 1/position" = Vector2(-1140, 440)
|
||||
nodes/BLTAnimationNodeSampler/node = SubResource("BLTAnimationNodeSampler_n4m28")
|
||||
nodes/BLTAnimationNodeSampler/position = Vector2(0, 0)
|
||||
node_connections = [&"BLTAnimationNodeBlend2", 0, &"BLTAnimationNodeSampler", &"BLTAnimationNodeBlend2", 1, &"BLTAnimationNodeSampler 1", &"Output", 0, &"BLTAnimationNodeBlend2"]
|
||||
nodes/BLTAnimationNodeSampler/position = Vector2(-1080, -40)
|
||||
node_connections = ["BLTAnimationNodeBlend2", 0, "BLTAnimationNodeSampler", "BLTAnimationNodeBlend2", 1, "BLTAnimationNodeSampler 1", "Output", 0, "BLTAnimationNodeBlend2"]
|
||||
|
||||
@ -111,7 +111,7 @@ Some nodes have special names in the Blend Tree:
|
||||
Except for the output node of a Blend Tree the following properties hold:
|
||||
|
||||
* all Blend Tree nodes only operate on properties they own and any other data (e.g. inputs and outputs) are specified
|
||||
via arguments to `SyncedAnimationNode::evaluate(context, inputs, output)` function of the node.
|
||||
via arguments to `BLTAnimationNode::evaluate(context, inputs, output)` function of the node.
|
||||
|
||||
Advantages:
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ void initialize_blendalot_animgraph_module(ModuleInitializationLevel p_level) {
|
||||
ClassDB::register_class<BLTAnimationNodeOutput>();
|
||||
ClassDB::register_class<BLTAnimationNodeBlendTree>();
|
||||
ClassDB::register_class<BLTAnimationNodeSampler>();
|
||||
ClassDB::register_class<BLTAnimationNodeTimeScale>();
|
||||
ClassDB::register_class<BLTAnimationNodeBlend2>();
|
||||
}
|
||||
|
||||
|
||||
36
sync_track.h
36
sync_track.h
@ -2,6 +2,7 @@
|
||||
|
||||
#include "core/templates/local_vector.h"
|
||||
|
||||
#include "blendalot_math_helper.h"
|
||||
#include <cassert>
|
||||
#include <cmath>
|
||||
|
||||
@ -21,7 +22,7 @@
|
||||
* duration. Blended SyncTracks always have their first interval start at t = 0.0s.
|
||||
*/
|
||||
struct SyncTrack {
|
||||
static constexpr int cSyncTrackMaxIntervals = 8;
|
||||
static constexpr int cSyncTrackMaxIntervals = 32;
|
||||
|
||||
SyncTrack() :
|
||||
duration(0.f), num_intervals(1) {
|
||||
@ -59,6 +60,12 @@ struct SyncTrack {
|
||||
}
|
||||
|
||||
double calc_ratio_from_sync_time(double sync_time) const {
|
||||
// When blending SyncTracks with differing numbers of intervals the resulting SyncTrack may have
|
||||
// additional repeats of the animation (=> "virtual sync periods", https://youtu.be/Jkv0pbp0ckQ?t=8178).
|
||||
//
|
||||
// Therefore, we first have to transform it back to the numbers of intervals we actually have.
|
||||
sync_time = fmod(sync_time, num_intervals);
|
||||
|
||||
float interval_ratio = fmod(sync_time, 1.0f);
|
||||
int interval = int(sync_time - interval_ratio);
|
||||
|
||||
@ -126,19 +133,32 @@ struct SyncTrack {
|
||||
*/
|
||||
static SyncTrack
|
||||
blend(float weight, const SyncTrack &track_A, const SyncTrack &track_B) {
|
||||
assert(track_A.num_intervals == track_B.num_intervals);
|
||||
if (Math::is_zero_approx(weight)) {
|
||||
return track_A;
|
||||
}
|
||||
|
||||
if (Math::is_zero_approx(1.0 - weight)) {
|
||||
return track_B;
|
||||
}
|
||||
|
||||
SyncTrack result;
|
||||
result.num_intervals = track_A.num_intervals;
|
||||
|
||||
result.duration =
|
||||
(1.0f - weight) * track_A.duration + weight * track_B.duration;
|
||||
if (track_A.num_intervals != track_B.num_intervals) {
|
||||
result.num_intervals = least_common_multiple(track_A.num_intervals, track_B.num_intervals);
|
||||
} else {
|
||||
result.num_intervals = track_A.num_intervals;
|
||||
}
|
||||
assert(result.num_intervals < cSyncTrackMaxIntervals);
|
||||
|
||||
float track_A_repeats = static_cast<float>(result.num_intervals / track_A.num_intervals);
|
||||
float track_B_repeats = static_cast<float>(result.num_intervals / track_B.num_intervals);
|
||||
|
||||
result.duration = (1.0f - weight) * (track_A.duration * track_A_repeats) + weight * (track_B.duration * track_B_repeats);
|
||||
result.interval_start_ratio[0] = 0.f;
|
||||
|
||||
for (int i = 0; i < result.num_intervals; i++) {
|
||||
float interval_duration_A = track_A.interval_duration_ratio[i];
|
||||
float interval_duration_B = track_B.interval_duration_ratio[i];
|
||||
float interval_duration_A = track_A.interval_duration_ratio[i % track_A.num_intervals] / track_A_repeats;
|
||||
float interval_duration_B = track_B.interval_duration_ratio[i % track_B.num_intervals] / track_B_repeats;
|
||||
result.interval_duration_ratio[i] =
|
||||
(1.0f - weight) * interval_duration_A + weight * interval_duration_B;
|
||||
|
||||
@ -152,8 +172,6 @@ struct SyncTrack {
|
||||
}
|
||||
}
|
||||
|
||||
assert(result.num_intervals < cSyncTrackMaxIntervals);
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
@ -6,7 +6,7 @@
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
struct SyncedAnimationGraphFixture {
|
||||
struct BlendTreeFixture {
|
||||
Node *character_node;
|
||||
Skeleton3D *skeleton_node;
|
||||
AnimationPlayer *player_node;
|
||||
@ -15,13 +15,14 @@ struct SyncedAnimationGraphFixture {
|
||||
|
||||
Ref<Animation> test_animation_a;
|
||||
Ref<Animation> test_animation_b;
|
||||
Ref<Animation> test_animation_c;
|
||||
Ref<Animation> test_animation_sync_a;
|
||||
Ref<Animation> test_animation_sync_b;
|
||||
|
||||
Ref<AnimationLibrary> animation_library;
|
||||
|
||||
BLTAnimationGraph *synced_animation_graph;
|
||||
SyncedAnimationGraphFixture() {
|
||||
BLTAnimationGraph *animation_graph;
|
||||
BlendTreeFixture() {
|
||||
BLTAnimationGraph *scene_animation_graph = dynamic_cast<BLTAnimationGraph *>(SceneTree::get_singleton()->get_root()->find_child("SyncedAnimationGraphFixtureTestNode", true, false));
|
||||
|
||||
if (scene_animation_graph == nullptr) {
|
||||
@ -50,12 +51,12 @@ struct SyncedAnimationGraphFixture {
|
||||
|
||||
SceneTree::get_singleton()->get_root()->add_child(player_node);
|
||||
|
||||
synced_animation_graph = memnew(BLTAnimationGraph);
|
||||
synced_animation_graph->set_name("SyncedAnimationGraphFixtureTestNode");
|
||||
SceneTree::get_singleton()->get_root()->add_child(synced_animation_graph);
|
||||
animation_graph = memnew(BLTAnimationGraph);
|
||||
animation_graph->set_name("SyncedAnimationGraphFixtureTestNode");
|
||||
SceneTree::get_singleton()->get_root()->add_child(animation_graph);
|
||||
|
||||
synced_animation_graph->set_animation_player(player_node->get_path());
|
||||
synced_animation_graph->set_skeleton(skeleton_node->get_path());
|
||||
animation_graph->set_animation_player(player_node->get_path());
|
||||
animation_graph->set_skeleton(skeleton_node->get_path());
|
||||
}
|
||||
|
||||
void setup_animations() {
|
||||
@ -80,6 +81,16 @@ struct SyncedAnimationGraphFixture {
|
||||
|
||||
animation_library->add_animation("TestAnimationB", test_animation_b);
|
||||
|
||||
test_animation_c = memnew(Animation);
|
||||
track_index = test_animation_c->add_track(Animation::TYPE_POSITION_3D);
|
||||
CHECK(track_index == 0);
|
||||
test_animation_c->track_insert_key(track_index, 0.0, Vector3(0., 0., 0.));
|
||||
test_animation_c->track_insert_key(track_index, 3.0, Vector3(2., 4., 6.));
|
||||
test_animation_c->track_set_path(track_index, NodePath(vformat("%s:%s", skeleton_node->get_path().get_concatenated_names(), "Hips")));
|
||||
test_animation_c->set_loop_mode(Animation::LOOP_LINEAR);
|
||||
|
||||
animation_library->add_animation("TestAnimationC", test_animation_c);
|
||||
|
||||
test_animation_sync_a = memnew(Animation);
|
||||
track_index = test_animation_sync_a->add_track(Animation::TYPE_POSITION_3D);
|
||||
CHECK(track_index == 0);
|
||||
@ -112,8 +123,8 @@ struct SyncedAnimationGraphFixture {
|
||||
}
|
||||
|
||||
void assign_scene_variables() {
|
||||
synced_animation_graph = dynamic_cast<BLTAnimationGraph *>(SceneTree::get_singleton()->get_root()->find_child("SyncedAnimationGraphFixtureTestNode", true, false));
|
||||
REQUIRE(synced_animation_graph);
|
||||
animation_graph = dynamic_cast<BLTAnimationGraph *>(SceneTree::get_singleton()->get_root()->find_child("SyncedAnimationGraphFixtureTestNode", true, false));
|
||||
REQUIRE(animation_graph);
|
||||
character_node = (SceneTree::get_singleton()->get_root()->find_child("CharacterNode", true, false));
|
||||
REQUIRE(character_node != nullptr);
|
||||
skeleton_node = dynamic_cast<Skeleton3D *>((SceneTree::get_singleton()->get_root()->find_child("Skeleton", true, false)));
|
||||
@ -139,34 +150,34 @@ struct SyncedAnimationGraphFixture {
|
||||
}
|
||||
};
|
||||
|
||||
namespace TestSyncedAnimationGraph {
|
||||
namespace TestBlendalotAnimationGraph {
|
||||
|
||||
TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") {
|
||||
TEST_CASE("[Blendalot][BlendTree] Test BlendTree construction") {
|
||||
BLTAnimationNodeBlendTree::BLTBlendTreeGraph tree_constructor;
|
||||
|
||||
Ref<BLTAnimationNodeSampler> animation_sampler_node0;
|
||||
animation_sampler_node0.instantiate();
|
||||
animation_sampler_node0->name = "Sampler0";
|
||||
animation_sampler_node0->set_name("Sampler0");
|
||||
tree_constructor.add_node(animation_sampler_node0);
|
||||
|
||||
Ref<BLTAnimationNodeSampler> animation_sampler_node1;
|
||||
animation_sampler_node1.instantiate();
|
||||
animation_sampler_node1->name = "Sampler1";
|
||||
animation_sampler_node1->set_name("Sampler1");
|
||||
tree_constructor.add_node(animation_sampler_node1);
|
||||
|
||||
Ref<BLTAnimationNodeSampler> animation_sampler_node2;
|
||||
animation_sampler_node2.instantiate();
|
||||
animation_sampler_node2->name = "Sampler2";
|
||||
animation_sampler_node2->set_name("Sampler2");
|
||||
tree_constructor.add_node(animation_sampler_node2);
|
||||
|
||||
Ref<BLTAnimationNodeBlend2> node_blend0;
|
||||
node_blend0.instantiate();
|
||||
node_blend0->name = "Blend0";
|
||||
node_blend0->set_name("Blend0");
|
||||
tree_constructor.add_node(node_blend0);
|
||||
|
||||
Ref<BLTAnimationNodeBlend2> node_blend1;
|
||||
node_blend1.instantiate();
|
||||
node_blend1->name = "Blend1";
|
||||
node_blend1->set_name("Blend1");
|
||||
tree_constructor.add_node(node_blend1);
|
||||
|
||||
// Tree
|
||||
@ -201,7 +212,7 @@ TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") {
|
||||
CHECK(tree_constructor.node_connection_info[blend1_index].input_subtree_node_indices.has(blend0_index));
|
||||
|
||||
// Perform remaining connections
|
||||
CHECK(BLTAnimationNodeBlendTree::CONNECTION_OK == tree_constructor.add_connection(node_blend1, tree_constructor.get_output_node(), "Input"));
|
||||
CHECK(BLTAnimationNodeBlendTree::CONNECTION_OK == tree_constructor.add_connection(node_blend1, tree_constructor.get_output_node(), "Output"));
|
||||
CHECK(BLTAnimationNodeBlendTree::CONNECTION_OK == tree_constructor.add_connection(animation_sampler_node2, node_blend1, "Input1"));
|
||||
|
||||
// Output node must have all nodes in its subtree:
|
||||
@ -213,15 +224,15 @@ TEST_CASE("[SyncedAnimationGraph] Test BlendTree construction") {
|
||||
|
||||
tree_constructor.sort_nodes_and_references();
|
||||
|
||||
// Check that for node i all input nodes have a node index j > i.
|
||||
// Check that for node i all input nodes have a node index j >= i (i is part of the subtree)
|
||||
for (unsigned int i = 0; i < tree_constructor.nodes.size(); i++) {
|
||||
for (int input_index : tree_constructor.node_connection_info[i].input_subtree_node_indices) {
|
||||
CHECK(input_index > i);
|
||||
CHECK(input_index >= i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] Test AnimationData blending") {
|
||||
TEST_CASE_FIXTURE(BlendTreeFixture, "[SceneTree][Blendalot] Test AnimationData blending") {
|
||||
AnimationData data_t0;
|
||||
data_t0.allocate_track_values(test_animation_a, skeleton_node);
|
||||
data_t0.sample_from_animation(test_animation_a, skeleton_node, 0.0);
|
||||
@ -256,12 +267,12 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph] SyncedAnimationGraph evaluation with an AnimationSampler as root node") {
|
||||
TEST_CASE_FIXTURE(BlendTreeFixture, "[SceneTree][Blendalot] SyncedAnimationGraph evaluation with an AnimationSampler as root node") {
|
||||
Ref<BLTAnimationNodeSampler> animation_sampler_node;
|
||||
animation_sampler_node.instantiate();
|
||||
animation_sampler_node->animation_name = "animation_library/TestAnimationA";
|
||||
|
||||
synced_animation_graph->set_root_animation_node(animation_sampler_node);
|
||||
animation_graph->set_root_animation_node(animation_sampler_node);
|
||||
|
||||
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
||||
|
||||
@ -278,7 +289,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
||||
CHECK(hip_bone_position.z == doctest::Approx(0.03));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph][BlendTree] BlendTree evaluation with a AnimationSamplerNode connected to the output") {
|
||||
TEST_CASE_FIXTURE(BlendTreeFixture, "[SceneTree][Blendalot][BlendTree] BlendTree evaluation with a AnimationSamplerNode connected to the output") {
|
||||
Ref<BLTAnimationNodeBlendTree> synced_blend_tree_node;
|
||||
synced_blend_tree_node.instantiate();
|
||||
|
||||
@ -287,11 +298,11 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
||||
animation_sampler_node->animation_name = "animation_library/TestAnimationA";
|
||||
|
||||
synced_blend_tree_node->add_node(animation_sampler_node);
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(animation_sampler_node, synced_blend_tree_node->get_output_node(), "Input"));
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(animation_sampler_node, synced_blend_tree_node->get_output_node(), "Output"));
|
||||
|
||||
synced_blend_tree_node->initialize(synced_animation_graph->get_context());
|
||||
synced_blend_tree_node->initialize(animation_graph->get_context());
|
||||
|
||||
synced_animation_graph->set_root_animation_node(synced_blend_tree_node);
|
||||
animation_graph->set_root_animation_node(synced_blend_tree_node);
|
||||
|
||||
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
||||
|
||||
@ -308,7 +319,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
||||
CHECK(hip_bone_position.z == doctest::Approx(0.03));
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph][BlendTree][Blend2Node] BlendTree evaluation with a Blend2Node connected to the output") {
|
||||
TEST_CASE_FIXTURE(BlendTreeFixture, "[SceneTree][Blendalot][BlendTree][Blend2Node] BlendTree evaluation with a Blend2Node connected to the output") {
|
||||
Ref<BLTAnimationNodeBlendTree> synced_blend_tree_node;
|
||||
synced_blend_tree_node.instantiate();
|
||||
|
||||
@ -329,20 +340,19 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
||||
// Blend2
|
||||
Ref<BLTAnimationNodeBlend2> blend2_node;
|
||||
blend2_node.instantiate();
|
||||
blend2_node->name = "Blend2";
|
||||
blend2_node->set_name("Blend2");
|
||||
blend2_node->blend_weight = 0.5;
|
||||
blend2_node->sync = false;
|
||||
|
||||
synced_blend_tree_node->add_node(blend2_node);
|
||||
|
||||
// Connect nodes
|
||||
Vector<StringName> blend2_inputs;
|
||||
blend2_node->get_input_names(blend2_inputs);
|
||||
Vector<StringName> blend2_inputs = blend2_node->get_input_names();
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(animation_sampler_node_a, blend2_node, blend2_inputs[0]));
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(animation_sampler_node_b, blend2_node, blend2_inputs[1]));
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(blend2_node, synced_blend_tree_node->get_output_node(), "Input"));
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == synced_blend_tree_node->add_connection(blend2_node, synced_blend_tree_node->get_output_node(), "Output"));
|
||||
|
||||
synced_blend_tree_node->initialize(synced_animation_graph->get_context());
|
||||
synced_blend_tree_node->initialize(animation_graph->get_context());
|
||||
|
||||
int blend2_node_index = synced_blend_tree_node->find_node_index(blend2_node);
|
||||
const BLTAnimationNodeBlendTree::NodeRuntimeData &blend2_runtime_data = synced_blend_tree_node->_node_runtime_data[blend2_node_index];
|
||||
@ -350,7 +360,7 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
||||
CHECK(blend2_runtime_data.input_nodes[0] == animation_sampler_node_a);
|
||||
CHECK(blend2_runtime_data.input_nodes[1] == animation_sampler_node_b);
|
||||
|
||||
synced_animation_graph->set_root_animation_node(synced_blend_tree_node);
|
||||
animation_graph->set_root_animation_node(synced_blend_tree_node);
|
||||
|
||||
SUBCASE("Perform default evaluation") {
|
||||
Vector3 hip_bone_position = skeleton_node->get_bone_global_pose(hip_bone_index).origin;
|
||||
@ -388,9 +398,9 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
||||
animation_sampler_node_a->animation_name = "animation_library/TestAnimationSyncA";
|
||||
animation_sampler_node_b->animation_name = "animation_library/TestAnimationSyncB";
|
||||
blend2_node->sync = true;
|
||||
synced_blend_tree_node->initialize(synced_animation_graph->get_context());
|
||||
synced_blend_tree_node->initialize(animation_graph->get_context());
|
||||
|
||||
REQUIRE(synced_animation_graph->get_root_animation_node().ptr() == synced_blend_tree_node.ptr());
|
||||
REQUIRE(animation_graph->get_root_animation_node().ptr() == synced_blend_tree_node.ptr());
|
||||
|
||||
// By blending both animations we get a SyncTrack of duration 1.5s with the following
|
||||
// intervals:
|
||||
@ -437,13 +447,13 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
||||
Ref<BLTAnimationNodeBlendTree> loaded_synced_blend_tree = ResourceLoader::load("synced_blend_tree_node.tres");
|
||||
REQUIRE(loaded_synced_blend_tree.is_valid());
|
||||
|
||||
Ref<BLTAnimationNodeBlend2> loaded_blend2_node = loaded_synced_blend_tree->get_node(loaded_synced_blend_tree->find_node_index_by_name("Blend2"));
|
||||
Ref<BLTAnimationNodeBlend2> loaded_blend2_node = loaded_synced_blend_tree->get_node_by_index(loaded_synced_blend_tree->find_node_index_by_name("Blend2"));
|
||||
REQUIRE(loaded_blend2_node.is_valid());
|
||||
CHECK(loaded_blend2_node->sync == false);
|
||||
CHECK(loaded_blend2_node->blend_weight == blend2_node->blend_weight);
|
||||
|
||||
loaded_synced_blend_tree->initialize(synced_animation_graph->get_context());
|
||||
synced_animation_graph->set_root_animation_node(loaded_synced_blend_tree);
|
||||
loaded_synced_blend_tree->initialize(animation_graph->get_context());
|
||||
animation_graph->set_root_animation_node(loaded_synced_blend_tree);
|
||||
|
||||
// Re-evaluate using a different time. All animation samplers will start again from 0.
|
||||
SceneTree::get_singleton()->process(0.2);
|
||||
@ -456,4 +466,266 @@ TEST_CASE_FIXTURE(SyncedAnimationGraphFixture, "[SceneTree][SyncedAnimationGraph
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace TestSyncedAnimationGraph
|
||||
TEST_CASE_FIXTURE(BlendTreeFixture, "[SceneTree][Blendalot][BlendTreeGraph][ChangeConnectivity] BlendTreeGraph with various nodes and connections that are removed") {
|
||||
BLTAnimationNodeBlendTree::BLTBlendTreeGraph blend_tree_graph;
|
||||
|
||||
// TestAnimationA
|
||||
Ref<BLTAnimationNodeSampler> animation_sampler_node_a;
|
||||
animation_sampler_node_a.instantiate();
|
||||
animation_sampler_node_a->animation_name = "animation_library/TestAnimationA";
|
||||
|
||||
blend_tree_graph.add_node(animation_sampler_node_a);
|
||||
|
||||
// TestAnimationB
|
||||
Ref<BLTAnimationNodeSampler> animation_sampler_node_b;
|
||||
animation_sampler_node_b.instantiate();
|
||||
animation_sampler_node_b->animation_name = "animation_library/TestAnimationB";
|
||||
|
||||
blend_tree_graph.add_node(animation_sampler_node_b);
|
||||
|
||||
// TestAnimationB
|
||||
Ref<BLTAnimationNodeSampler> animation_sampler_node_c;
|
||||
animation_sampler_node_c.instantiate();
|
||||
animation_sampler_node_c->animation_name = "animation_library/TestAnimationC";
|
||||
|
||||
blend_tree_graph.add_node(animation_sampler_node_c);
|
||||
|
||||
// Blend2A
|
||||
Ref<BLTAnimationNodeBlend2> blend2_node_a;
|
||||
blend2_node_a.instantiate();
|
||||
blend2_node_a->set_name("Blend2A");
|
||||
blend2_node_a->blend_weight = 0.5;
|
||||
blend2_node_a->sync = false;
|
||||
|
||||
blend_tree_graph.add_node(blend2_node_a);
|
||||
|
||||
// Blend2B
|
||||
Ref<BLTAnimationNodeBlend2> blend2_node_b;
|
||||
blend2_node_b.instantiate();
|
||||
blend2_node_b->set_name("Blend2A");
|
||||
blend2_node_b->blend_weight = 0.5;
|
||||
blend2_node_b->sync = false;
|
||||
|
||||
blend_tree_graph.add_node(blend2_node_b);
|
||||
|
||||
// Connect nodes: Subgraph Output, Blend2A, SamplerA
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(blend2_node_a, blend_tree_graph.get_output_node(), "Output"));
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(animation_sampler_node_a, blend2_node_a, "Input0"));
|
||||
|
||||
// Connect nodes: Subgraph Blend2A, SamplerB, SamplerC
|
||||
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;
|
||||
|
||||
// Add and remove connection
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.add_connection(blend2_node_b, blend2_node_a, "Input1"));
|
||||
blend_tree_graph.remove_connection(blend2_node_b, blend2_node_a, "Input1");
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_graph.is_connection_valid(blend2_node_b, blend2_node_a, "Input1"));
|
||||
|
||||
// Check that we have the same subgraphs as before the connection
|
||||
CHECK(subgraph_output_initial == blend_tree_graph.node_connection_info[0].input_subtree_node_indices);
|
||||
CHECK(subgraph_blend2a_initial == blend_tree_graph.node_connection_info[blend_tree_graph.find_node_index(blend2_node_a)].input_subtree_node_indices);
|
||||
CHECK(subgraph_blend2b_initial == blend_tree_graph.node_connection_info[blend_tree_graph.find_node_index(blend2_node_b)].input_subtree_node_indices);
|
||||
|
||||
// Check that the connection is not present anymore.
|
||||
for (const BLTBlendTreeConnection &connection : blend_tree_graph.connections) {
|
||||
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);
|
||||
|
||||
SUBCASE("Removing the output node does nothing") {
|
||||
int num_nodes = blend_tree_graph.nodes.size();
|
||||
int num_connections = blend_tree_graph.connections.size();
|
||||
CHECK(blend_tree_graph.remove_node(blend_tree_graph.get_output_node()) == false);
|
||||
CHECK(blend_tree_graph.connections.size() == num_connections);
|
||||
CHECK(blend_tree_graph.nodes.size() == num_nodes);
|
||||
}
|
||||
|
||||
SUBCASE("Remove a node with no children") {
|
||||
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));
|
||||
}
|
||||
|
||||
SUBCASE("Remove a node with parent and children") {
|
||||
int num_nodes = blend_tree_graph.nodes.size();
|
||||
blend_tree_graph.remove_node(blend2_node_a);
|
||||
blend_tree_graph.sort_nodes_and_references();
|
||||
|
||||
CHECK(blend_tree_graph.nodes.size() == num_nodes - 1);
|
||||
|
||||
for (const BLTBlendTreeConnection &connection : blend_tree_graph.connections) {
|
||||
bool is_connection_with_removed_node = connection.source_node == blend2_node_a || connection.target_node == blend2_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 animation_sampler_node_c_index_post_remove = blend_tree_graph.find_node_index(animation_sampler_node_c);
|
||||
int blend2_node_b_index_post_remove = blend_tree_graph.find_node_index(blend2_node_b);
|
||||
|
||||
CHECK(blend_tree_graph.find_node_index(blend2_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);
|
||||
|
||||
CHECK(blend_tree_graph.node_connection_info[0].input_subtree_node_indices.size() == 1);
|
||||
CHECK(blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove].input_subtree_node_indices.size() == 3);
|
||||
blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove]._print_subtree();
|
||||
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));
|
||||
CHECK(blend_tree_graph.node_connection_info[blend2_node_b_index_post_remove].input_subtree_node_indices.has(animation_sampler_node_c_index_post_remove));
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("Check evaluation of graph with modified connections") {
|
||||
Ref<BLTAnimationNodeBlendTree> blend_tree_node;
|
||||
blend_tree_node.instantiate();
|
||||
blend_tree_node->add_node(animation_sampler_node_a);
|
||||
blend_tree_node->add_node(animation_sampler_node_b);
|
||||
blend_tree_node->add_node(animation_sampler_node_c);
|
||||
blend_tree_node->add_node(blend2_node_a);
|
||||
|
||||
animation_graph->set_root_animation_node(blend_tree_node);
|
||||
GraphEvaluationContext &graph_context = animation_graph->get_context();
|
||||
CHECK(blend_tree_node->initialize(graph_context) == false);
|
||||
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_node->add_connection(animation_sampler_node_a, blend_tree_node->get_output_node(), "Output"));
|
||||
CHECK(blend_tree_node->initialize(graph_context) == true);
|
||||
|
||||
AnimationData *graph_output = graph_context.animation_data_allocator.allocate();
|
||||
blend_tree_node->activate_inputs(Vector<Ref<BLTAnimationNode>>());
|
||||
blend_tree_node->calculate_sync_track(Vector<Ref<BLTAnimationNode>>());
|
||||
blend_tree_node->update_time(0.825);
|
||||
blend_tree_node->evaluate(graph_context, LocalVector<AnimationData *>(), *graph_output);
|
||||
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_node->add_connection(animation_sampler_node_b, blend2_node_a, "Input0"));
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_node->add_connection(animation_sampler_node_c, blend2_node_a, "Input1"));
|
||||
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_node->remove_connection(animation_sampler_node_a, blend_tree_node->get_output_node(), "Output"));
|
||||
|
||||
REQUIRE(BLTAnimationNodeBlendTree::CONNECTION_OK == blend_tree_node->add_connection(blend2_node_a, blend_tree_node->get_output_node(), "Output"));
|
||||
CHECK(blend_tree_node->initialize(graph_context) == true);
|
||||
|
||||
blend_tree_node->activate_inputs(Vector<Ref<BLTAnimationNode>>());
|
||||
blend_tree_node->calculate_sync_track(Vector<Ref<BLTAnimationNode>>());
|
||||
blend_tree_node->update_time(0.825);
|
||||
blend_tree_node->evaluate(graph_context, LocalVector<AnimationData *>(), *graph_output);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE_FIXTURE(BlendTreeFixture, "[SceneTree][Blendalot][BlendTreeGraph][EmbeddedBlendTree] BlendTree with an embedded BlendTree subgraph") {
|
||||
// Embedded BlendTree
|
||||
Ref<BLTAnimationNodeBlendTree> embedded_blend_tree;
|
||||
embedded_blend_tree.instantiate();
|
||||
|
||||
// TestAnimationB
|
||||
Ref<BLTAnimationNodeSampler> animation_sampler_node_b;
|
||||
animation_sampler_node_b.instantiate();
|
||||
|
||||
embedded_blend_tree->add_node(animation_sampler_node_b);
|
||||
embedded_blend_tree->add_connection(animation_sampler_node_b, embedded_blend_tree->get_output_node(), "Output");
|
||||
|
||||
Ref<BLTAnimationNodeBlendTree> blend_tree;
|
||||
blend_tree.instantiate();
|
||||
|
||||
// Blend2
|
||||
Ref<BLTAnimationNodeBlend2> blend2;
|
||||
blend2.instantiate();
|
||||
blend2->set_name("Blend2");
|
||||
blend_tree->add_node(blend2);
|
||||
|
||||
// TestAnimationA
|
||||
Ref<BLTAnimationNodeSampler> animation_sampler_node_a;
|
||||
animation_sampler_node_a.instantiate();
|
||||
|
||||
blend_tree->add_node(animation_sampler_node_a);
|
||||
|
||||
blend_tree->add_node(embedded_blend_tree);
|
||||
|
||||
blend_tree->add_connection(animation_sampler_node_a, blend2, "Input0");
|
||||
blend_tree->add_connection(embedded_blend_tree, blend2, "Input1");
|
||||
blend_tree->add_connection(blend2, blend_tree->get_output_node(), "Output");
|
||||
|
||||
SUBCASE("Perform regular blend") {
|
||||
animation_sampler_node_b->animation_name = "animation_library/TestAnimationB";
|
||||
animation_sampler_node_a->animation_name = "animation_library/TestAnimationA";
|
||||
blend2->blend_weight = 0.5;
|
||||
blend2->sync = false;
|
||||
|
||||
// Trigger initialization
|
||||
animation_graph->set_root_animation_node(blend_tree);
|
||||
GraphEvaluationContext &graph_context = animation_graph->get_context();
|
||||
REQUIRE(blend_tree->initialize(graph_context));
|
||||
|
||||
// Perform evaluation
|
||||
AnimationData *graph_output = graph_context.animation_data_allocator.allocate();
|
||||
blend_tree->activate_inputs(Vector<Ref<BLTAnimationNode>>());
|
||||
blend_tree->calculate_sync_track(Vector<Ref<BLTAnimationNode>>());
|
||||
blend_tree->update_time(0.1);
|
||||
blend_tree->evaluate(graph_context, LocalVector<AnimationData *>(), *graph_output);
|
||||
|
||||
// Check values
|
||||
AnimationData::TransformTrackValue *hip_transform_value = graph_output->get_value<AnimationData::TransformTrackValue>(test_animation_a->get_tracks()[0]->thash);
|
||||
CHECK(hip_transform_value->loc[0] == doctest::Approx(0.15));
|
||||
CHECK(hip_transform_value->loc[1] == doctest::Approx(0.3));
|
||||
CHECK(hip_transform_value->loc[2] == doctest::Approx(0.45));
|
||||
}
|
||||
SUBCASE("Perform synced blend") {
|
||||
animation_sampler_node_b->animation_name = "animation_library/TestAnimationSyncA";
|
||||
animation_sampler_node_a->animation_name = "animation_library/TestAnimationSyncB";
|
||||
blend2->blend_weight = 0.5;
|
||||
blend2->sync = true;
|
||||
|
||||
// Trigger initialization
|
||||
animation_graph->set_root_animation_node(blend_tree);
|
||||
GraphEvaluationContext &graph_context = animation_graph->get_context();
|
||||
REQUIRE(blend_tree->initialize(graph_context));
|
||||
|
||||
// Perform evaluation
|
||||
AnimationData *graph_output = graph_context.animation_data_allocator.allocate();
|
||||
blend_tree->activate_inputs(Vector<Ref<BLTAnimationNode>>());
|
||||
blend_tree->calculate_sync_track(Vector<Ref<BLTAnimationNode>>());
|
||||
blend_tree->update_time(0.825);
|
||||
blend_tree->evaluate(graph_context, LocalVector<AnimationData *>(), *graph_output);
|
||||
|
||||
// Check values
|
||||
AnimationData::TransformTrackValue *hip_transform_value = graph_output->get_value<AnimationData::TransformTrackValue>(test_animation_a->get_tracks()[0]->thash);
|
||||
CHECK(hip_transform_value->loc[0] == doctest::Approx(1.5));
|
||||
CHECK(hip_transform_value->loc[1] == doctest::Approx(3.0));
|
||||
CHECK(hip_transform_value->loc[2] == doctest::Approx(4.5));
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace TestBlendalotAnimationGraph
|
||||
@ -4,9 +4,9 @@
|
||||
|
||||
#include "tests/test_macros.h"
|
||||
|
||||
namespace TestSyncedAnimationGraph {
|
||||
namespace TestBlendalotAnimationGraph {
|
||||
|
||||
TEST_CASE("[SyncedAnimationGraph][SyncTrack] Basic") {
|
||||
TEST_CASE("[Blendalot][SyncTrack] Basic") {
|
||||
SyncTrack track_a;
|
||||
track_a.num_intervals = 2;
|
||||
track_a.duration = 2.0;
|
||||
@ -84,7 +84,7 @@ TEST_CASE("[SyncedAnimationGraph][SyncTrack] Basic") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[SyncedAnimationGraph][SyncTrack] Create Sync Track from markers") {
|
||||
TEST_CASE("[Blendalot][SyncTrack] Create Sync Track from markers") {
|
||||
SyncTrack track = SyncTrack::create_from_markers(2.0f, { 0.9f, 0.2f });
|
||||
|
||||
WHEN("Querying Ratios") {
|
||||
@ -138,7 +138,7 @@ TEST_CASE("[SyncedAnimationGraph][SyncTrack] Create Sync Track from markers") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("[SyncedAnimationGraph][SyncTrack] Sync Track blending") {
|
||||
TEST_CASE("[Blendalot][SyncTrack] Sync Track blending") {
|
||||
SyncTrack track_a = SyncTrack::create_from_markers(2.0, { 0., 0.6, 1.8 });
|
||||
SyncTrack track_b = SyncTrack::create_from_markers(1.5f, { 1.05, 1.35, 0.3 });
|
||||
|
||||
@ -203,4 +203,47 @@ TEST_CASE("[SyncedAnimationGraph][SyncTrack] Sync Track blending") {
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace TestSyncedAnimationGraph
|
||||
TEST_CASE("[Blendalot][SyncTrack] Sync Track blending non-matching interval count") {
|
||||
SyncTrack track_a = SyncTrack::create_from_markers(2.0, { 0., 0.6, 1.8 });
|
||||
SyncTrack track_b = SyncTrack::create_from_markers(1.5f, { 1.05 });
|
||||
|
||||
WHEN("Blending two synctracks with weight 0.") {
|
||||
SyncTrack blended = SyncTrack::blend(0.f, track_a, track_b);
|
||||
|
||||
blended.duration = track_a.duration;
|
||||
blended.interval_start_ratio[0] = 0.0;
|
||||
for (int i = 0; i < track_a.num_intervals; i++) {
|
||||
CHECK(blended.interval_duration_ratio[i] == track_a.interval_duration_ratio[i]);
|
||||
}
|
||||
}
|
||||
WHEN("Blending two synctracks with weight 1.") {
|
||||
SyncTrack blended = SyncTrack::blend(1.f, track_a, track_b);
|
||||
|
||||
blended.duration = track_b.duration;
|
||||
blended.interval_start_ratio[0] = 0.0;
|
||||
for (int i = 0; i < track_b.num_intervals; i++) {
|
||||
CHECK(blended.interval_duration_ratio[i] == track_b.interval_duration_ratio[i]);
|
||||
}
|
||||
}
|
||||
|
||||
WHEN("Blending with weight 0.2") {
|
||||
float weight = 0.2f;
|
||||
SyncTrack blended = SyncTrack::blend(weight, track_a, track_b);
|
||||
|
||||
float track_a_repeats = static_cast<float>(blended.num_intervals / track_a.num_intervals);
|
||||
float track_b_repeats = static_cast<float>(blended.num_intervals / track_b.num_intervals);
|
||||
|
||||
CHECK(
|
||||
blended.duration == doctest::Approx(2.5));
|
||||
CHECK(
|
||||
blended.interval_start_ratio[0] == 0.0);
|
||||
CHECK(
|
||||
blended.interval_duration_ratio[0] == doctest::Approx((1.0 - weight) * track_a.interval_duration_ratio[0] / track_a_repeats + weight * track_b.interval_duration_ratio[0] / track_b_repeats));
|
||||
CHECK(
|
||||
blended.interval_duration_ratio[1] == doctest::Approx((1.0 - weight) * track_a.interval_duration_ratio[1] / track_a_repeats + weight * track_b.interval_duration_ratio[0] / track_b_repeats));
|
||||
CHECK(
|
||||
blended.interval_duration_ratio[2] == doctest::Approx((1.0 - weight) * track_a.interval_duration_ratio[2] / track_a_repeats + weight * track_b.interval_duration_ratio[0] / track_b_repeats));
|
||||
}
|
||||
}
|
||||
|
||||
} //namespace TestBlendalotAnimationGraph
|
||||
Loading…
x
Reference in New Issue
Block a user