瀏覽代碼

Add option to auto tangent new bezier points in animation editor

Co-authored-by: Mikael Hermansson <[email protected]>
Kasper Arnklit Frandsen 10 月之前
父節點
當前提交
5934804335

+ 88 - 9
editor/animation_bezier_editor.cpp

@@ -37,6 +37,7 @@
 #include "editor/gui/editor_spin_slider.h"
 #include "editor/gui/editor_spin_slider.h"
 #include "editor/plugins/animation_player_editor_plugin.h"
 #include "editor/plugins/animation_player_editor_plugin.h"
 #include "editor/themes/editor_scale.h"
 #include "editor/themes/editor_scale.h"
+#include "scene/gui/option_button.h"
 #include "scene/gui/view_panner.h"
 #include "scene/gui/view_panner.h"
 #include "scene/resources/text_line.h"
 #include "scene/resources/text_line.h"
 
 
@@ -97,8 +98,6 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {
 			}
 			}
 		}
 		}
 
 
-		out_handle += Vector2(offset, height);
-
 		float offset_n = animation->track_get_key_time(p_track, i_n);
 		float offset_n = animation->track_get_key_time(p_track, i_n);
 		float height_n = animation->bezier_track_get_key_value(p_track, i_n);
 		float height_n = animation->bezier_track_get_key_value(p_track, i_n);
 		Vector2 in_handle = animation->bezier_track_get_key_in_handle(p_track, i_n);
 		Vector2 in_handle = animation->bezier_track_get_key_in_handle(p_track, i_n);
@@ -116,6 +115,37 @@ void AnimationBezierTrackEdit::_draw_track(int p_track, const Color &p_color) {
 			}
 			}
 		}
 		}
 
 
+		if (moving_inserted_key && moving_selection_from_track == p_track) {
+			if (moving_selection_from_key == i) {
+				Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(p_track, i);
+				if (handle_mode != Animation::HANDLE_MODE_FREE) {
+					float offset_p = offset;
+					float height_p = height;
+					if (E->prev()) {
+						int i_p = E->prev()->get();
+						offset_p = animation->track_get_key_time(p_track, i_p);
+						height_p = animation->bezier_track_get_key_value(p_track, i_p);
+					}
+
+					animation->bezier_track_calculate_handles(offset, offset_p, height_p, offset_n, height_n, handle_mode, Animation::HANDLE_SET_MODE_AUTO, nullptr, &out_handle);
+				}
+			} else if (moving_selection_from_key == i_n) {
+				Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(p_track, i_n);
+				if (handle_mode != Animation::HANDLE_MODE_FREE) {
+					float offset_nn = offset_n;
+					float height_nn = height_n;
+					if (E->next()->next()) {
+						int i_nn = E->next()->next()->get();
+						offset_nn = animation->track_get_key_time(p_track, i_nn);
+						height_nn = animation->bezier_track_get_key_value(p_track, i_nn);
+					}
+
+					animation->bezier_track_calculate_handles(offset_n, offset, height, offset_nn, height_nn, handle_mode, Animation::HANDLE_SET_MODE_AUTO, &in_handle, nullptr);
+				}
+			}
+		}
+
+		out_handle += Vector2(offset, height);
 		in_handle += Vector2(offset_n, height_n);
 		in_handle += Vector2(offset_n, height_n);
 
 
 		Vector2 start(offset, height);
 		Vector2 start(offset, height);
@@ -570,18 +600,53 @@ void AnimationBezierTrackEdit::_notification(int p_what) {
 						}
 						}
 
 
 						Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j);
 						Vector2 in_vec = animation->bezier_track_get_key_in_handle(i, j);
+						Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j);
 
 
 						if ((moving_handle == 1 || moving_handle == -1) && moving_handle_track == i && moving_handle_key == j) {
 						if ((moving_handle == 1 || moving_handle == -1) && moving_handle_track == i && moving_handle_key == j) {
 							in_vec = moving_handle_left;
 							in_vec = moving_handle_left;
 						}
 						}
-						Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y));
-
-						Vector2 out_vec = animation->bezier_track_get_key_out_handle(i, j);
 
 
 						if ((moving_handle == 1 || moving_handle == -1) && moving_handle_track == i && moving_handle_key == j) {
 						if ((moving_handle == 1 || moving_handle == -1) && moving_handle_track == i && moving_handle_key == j) {
 							out_vec = moving_handle_right;
 							out_vec = moving_handle_right;
 						}
 						}
 
 
+						if (moving_inserted_key && moving_selection_from_key == j) {
+							Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(i, j);
+							if (handle_mode != Animation::HANDLE_MODE_FREE) {
+								int key_prev = 0;
+								int key_next = moving_selection_from_key;
+								for (int k = 0; k < key_count; k++) {
+									if (k == moving_selection_from_key) {
+										continue;
+									}
+
+									if (animation->track_get_key_time(i, k) < offset) {
+										key_prev = k;
+									} else {
+										key_next = k;
+										break;
+									}
+								}
+
+								float prev_time = offset;
+								float prev_value = value;
+								if (key_prev != moving_selection_from_key) {
+									prev_time = animation->track_get_key_time(i, key_prev);
+									prev_value = animation->bezier_track_get_key_value(i, key_prev);
+								}
+
+								float next_time = offset;
+								float next_value = value;
+								if (key_next != moving_selection_from_key) {
+									next_time = animation->track_get_key_time(i, key_next);
+									next_value = animation->bezier_track_get_key_value(i, key_next);
+								}
+
+								animation->bezier_track_calculate_handles(offset, prev_time, prev_value, next_time, next_value, handle_mode, Animation::HANDLE_SET_MODE_AUTO, &in_vec, &out_vec);
+							}
+						}
+
+						Vector2 pos_in(((offset + in_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + in_vec.y));
 						Vector2 pos_out(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y));
 						Vector2 pos_out(((offset + out_vec.x) - timeline->get_value()) * scale + limit, _bezier_h_to_pixel(value + out_vec.y));
 
 
 						if (i == selected_track || is_selected) {
 						if (i == selected_track || is_selected) {
@@ -1402,6 +1467,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 			EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 			EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 			undo_redo->create_action(TTR("Add Bezier Point"));
 			undo_redo->create_action(TTR("Add Bezier Point"));
 			undo_redo->add_do_method(animation.ptr(), "bezier_track_insert_key", selected_track, time, new_point[0], Vector2(new_point[1], new_point[2]), Vector2(new_point[3], new_point[4]));
 			undo_redo->add_do_method(animation.ptr(), "bezier_track_insert_key", selected_track, time, new_point[0], Vector2(new_point[1], new_point[2]), Vector2(new_point[3], new_point[4]));
+			undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode_at_time", animation.ptr(), selected_track, time, (Animation::HandleMode)editor->bezier_key_mode->get_selected_id(), Animation::HANDLE_SET_MODE_AUTO);
 			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);
 			undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);
 			undo_redo->commit_action();
 			undo_redo->commit_action();
 
 
@@ -1412,6 +1478,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 			_select_at_anim(animation, selected_track, animation->track_get_key_time(selected_track, index), true);
 			_select_at_anim(animation, selected_track, animation->track_get_key_time(selected_track, index), true);
 
 
 			moving_selection_attempt = true;
 			moving_selection_attempt = true;
+			moving_inserted_key = true;
 			moving_selection = false;
 			moving_selection = false;
 			moving_selection_mouse_begin = mb->get_position();
 			moving_selection_mouse_begin = mb->get_position();
 			moving_selection_from_key = index;
 			moving_selection_from_key = index;
@@ -1539,6 +1606,14 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 					real_t h = key[0];
 					real_t h = key[0];
 					h += moving_selection_offset.y;
 					h += moving_selection_offset.y;
 					key[0] = h;
 					key[0] = h;
+
+					Animation::HandleMode handle_mode = animation->bezier_track_get_key_handle_mode(E->get().first, E->get().second);
+					Animation::HandleSetMode handle_set_mode = Animation::HANDLE_SET_MODE_NONE;
+					if (moving_inserted_key) {
+						handle_mode = (Animation::HandleMode)editor->bezier_key_mode->get_selected_id();
+						handle_set_mode = Animation::HANDLE_SET_MODE_AUTO;
+					}
+
 					undo_redo->add_do_method(
 					undo_redo->add_do_method(
 							this,
 							this,
 							"_bezier_track_insert_key_at_anim",
 							"_bezier_track_insert_key_at_anim",
@@ -1548,7 +1623,8 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 							key[0],
 							key[0],
 							Vector2(key[1], key[2]),
 							Vector2(key[1], key[2]),
 							Vector2(key[3], key[4]),
 							Vector2(key[3], key[4]),
-							animation->bezier_track_get_key_handle_mode(E->get().first, E->get().second));
+							handle_mode,
+							handle_set_mode);
 				}
 				}
 
 
 				// 4 - (undo) Remove inserted keys.
 				// 4 - (undo) Remove inserted keys.
@@ -1621,6 +1697,7 @@ void AnimationBezierTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
 
 
 			moving_selection = false;
 			moving_selection = false;
 			moving_selection_attempt = false;
 			moving_selection_attempt = false;
+			moving_inserted_key = false;
 			moving_selection_mouse_begin = Point2();
 			moving_selection_mouse_begin = Point2();
 			queue_redraw();
 			queue_redraw();
 		}
 		}
@@ -2058,9 +2135,11 @@ void AnimationBezierTrackEdit::_menu_selected(int p_index) {
 				}
 				}
 				float h = (get_size().height / 2.0 - menu_insert_key.y) * timeline_v_zoom + timeline_v_scroll;
 				float h = (get_size().height / 2.0 - menu_insert_key.y) * timeline_v_zoom + timeline_v_scroll;
 				Array new_point = animation->make_default_bezier_key(h);
 				Array new_point = animation->make_default_bezier_key(h);
+				Animation::HandleMode handle_mode = (Animation::HandleMode)editor->bezier_key_mode->get_selected_id();
 				EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 				EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
 				undo_redo->create_action(TTR("Add Bezier Point"));
 				undo_redo->create_action(TTR("Add Bezier Point"));
 				undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);
 				undo_redo->add_do_method(animation.ptr(), "track_insert_key", selected_track, time, new_point);
+				undo_redo->add_do_method(editor, "_bezier_track_set_key_handle_mode_at_time", animation.ptr(), selected_track, time, handle_mode, Animation::HANDLE_SET_MODE_AUTO);
 				undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
 				undo_redo->add_undo_method(this, "_clear_selection_for_anim", animation);
 				undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);
 				undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", selected_track, time);
 				AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
 				AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
@@ -2343,9 +2422,9 @@ void AnimationBezierTrackEdit::delete_selection() {
 	}
 	}
 }
 }
 
 
-void AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim(const Ref<Animation> &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode) {
+void AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim(const Ref<Animation> &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode, Animation::HandleSetMode p_handle_set_mode) {
 	int idx = p_anim->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle);
 	int idx = p_anim->bezier_track_insert_key(p_track, p_time, p_value, p_in_handle, p_out_handle);
-	p_anim->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode);
+	p_anim->bezier_track_set_key_handle_mode(p_track, idx, p_handle_mode, p_handle_set_mode);
 }
 }
 
 
 void AnimationBezierTrackEdit::_bind_methods() {
 void AnimationBezierTrackEdit::_bind_methods() {
@@ -2354,7 +2433,7 @@ void AnimationBezierTrackEdit::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_select_at_anim"), &AnimationBezierTrackEdit::_select_at_anim);
 	ClassDB::bind_method(D_METHOD("_select_at_anim"), &AnimationBezierTrackEdit::_select_at_anim);
 	ClassDB::bind_method(D_METHOD("_update_hidden_tracks_after"), &AnimationBezierTrackEdit::_update_hidden_tracks_after);
 	ClassDB::bind_method(D_METHOD("_update_hidden_tracks_after"), &AnimationBezierTrackEdit::_update_hidden_tracks_after);
 	ClassDB::bind_method(D_METHOD("_update_locked_tracks_after"), &AnimationBezierTrackEdit::_update_locked_tracks_after);
 	ClassDB::bind_method(D_METHOD("_update_locked_tracks_after"), &AnimationBezierTrackEdit::_update_locked_tracks_after);
-	ClassDB::bind_method(D_METHOD("_bezier_track_insert_key_at_anim"), &AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim);
+	ClassDB::bind_method(D_METHOD("_bezier_track_insert_key_at_anim"), &AnimationBezierTrackEdit::_bezier_track_insert_key_at_anim, DEFVAL(Animation::HANDLE_SET_MODE_NONE));
 
 
 	ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"), PropertyInfo(Variant::INT, "track")));
 	ADD_SIGNAL(MethodInfo("select_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::BOOL, "single"), PropertyInfo(Variant::INT, "track")));
 	ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::INT, "track")));
 	ADD_SIGNAL(MethodInfo("deselect_key", PropertyInfo(Variant::INT, "index"), PropertyInfo(Variant::INT, "track")));

+ 2 - 1
editor/animation_bezier_editor.h

@@ -108,6 +108,7 @@ class AnimationBezierTrackEdit : public Control {
 	typedef Pair<int, int> IntPair;
 	typedef Pair<int, int> IntPair;
 
 
 	bool moving_selection_attempt = false;
 	bool moving_selection_attempt = false;
+	bool moving_inserted_key = false;
 	Point2 moving_selection_mouse_begin;
 	Point2 moving_selection_mouse_begin;
 	IntPair select_single_attempt;
 	IntPair select_single_attempt;
 	bool moving_selection = false;
 	bool moving_selection = false;
@@ -230,7 +231,7 @@ public:
 	void paste_keys(real_t p_ofs, bool p_ofs_valid);
 	void paste_keys(real_t p_ofs, bool p_ofs_valid);
 	void delete_selection();
 	void delete_selection();
 
 
-	void _bezier_track_insert_key_at_anim(const Ref<Animation> &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode);
+	void _bezier_track_insert_key_at_anim(const Ref<Animation> &p_anim, int p_track, double p_time, real_t p_value, const Vector2 &p_in_handle, const Vector2 &p_out_handle, const Animation::HandleMode p_handle_mode, Animation::HandleSetMode p_handle_set_mode = Animation::HANDLE_SET_MODE_NONE);
 
 
 	AnimationBezierTrackEdit();
 	AnimationBezierTrackEdit();
 };
 };

+ 27 - 3
editor/animation_track_editor.cpp

@@ -4880,6 +4880,9 @@ AnimationTrackEditor::TrackIndices AnimationTrackEditor::_confirm_insert(InsertD
 	}
 	}
 
 
 	undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, value);
 	undo_redo->add_do_method(animation.ptr(), "track_insert_key", p_id.track_idx, time, value);
+	if (!created && p_id.type == Animation::TYPE_BEZIER) {
+		undo_redo->add_do_method(this, "_bezier_track_set_key_handle_mode_at_time", animation.ptr(), p_id.track_idx, time, (Animation::HandleMode)bezier_key_mode->get_selected_id(), Animation::HANDLE_SET_MODE_AUTO);
+	}
 
 
 	if (created) {
 	if (created) {
 		// Just remove the track.
 		// Just remove the track.
@@ -6316,12 +6319,17 @@ void AnimationTrackEditor::_bezier_edit(int p_for_track) {
 }
 }
 
 
 void AnimationTrackEditor::_bezier_track_set_key_handle_mode(Animation *p_anim, int p_track, int p_index, Animation::HandleMode p_mode, Animation::HandleSetMode p_set_mode) {
 void AnimationTrackEditor::_bezier_track_set_key_handle_mode(Animation *p_anim, int p_track, int p_index, Animation::HandleMode p_mode, Animation::HandleSetMode p_set_mode) {
-	if (!p_anim) {
-		return;
-	}
+	ERR_FAIL_NULL(p_anim);
 	p_anim->bezier_track_set_key_handle_mode(p_track, p_index, p_mode, p_set_mode);
 	p_anim->bezier_track_set_key_handle_mode(p_track, p_index, p_mode, p_set_mode);
 }
 }
 
 
+void AnimationTrackEditor::_bezier_track_set_key_handle_mode_at_time(Animation *p_anim, int p_track, float p_time, Animation::HandleMode p_mode, Animation::HandleSetMode p_set_mode) {
+	ERR_FAIL_NULL(p_anim);
+	int index = p_anim->track_find_key(p_track, p_time, Animation::FIND_MODE_APPROX);
+	ERR_FAIL_COND(index < 0);
+	_bezier_track_set_key_handle_mode(p_anim, p_track, index, p_mode, p_set_mode);
+}
+
 void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, bool p_ofs_valid, int p_track) {
 void AnimationTrackEditor::_anim_duplicate_keys(float p_ofs, bool p_ofs_valid, int p_track) {
 	if (selection.size() && animation.is_valid()) {
 	if (selection.size() && animation.is_valid()) {
 		int top_track = 0x7FFFFFFF;
 		int top_track = 0x7FFFFFFF;
@@ -7699,6 +7707,7 @@ void AnimationTrackEditor::_bind_methods() {
 	ClassDB::bind_method("_clear_selection", &AnimationTrackEditor::_clear_selection);
 	ClassDB::bind_method("_clear_selection", &AnimationTrackEditor::_clear_selection);
 
 
 	ClassDB::bind_method(D_METHOD("_bezier_track_set_key_handle_mode", "animation", "track_idx", "key_idx", "key_handle_mode", "key_handle_set_mode"), &AnimationTrackEditor::_bezier_track_set_key_handle_mode, DEFVAL(Animation::HANDLE_SET_MODE_NONE));
 	ClassDB::bind_method(D_METHOD("_bezier_track_set_key_handle_mode", "animation", "track_idx", "key_idx", "key_handle_mode", "key_handle_set_mode"), &AnimationTrackEditor::_bezier_track_set_key_handle_mode, DEFVAL(Animation::HANDLE_SET_MODE_NONE));
+	ClassDB::bind_method(D_METHOD("_bezier_track_set_key_handle_mode_at_time", "animation", "track_idx", "time", "key_handle_mode", "key_handle_set_mode"), &AnimationTrackEditor::_bezier_track_set_key_handle_mode_at_time, DEFVAL(Animation::HANDLE_SET_MODE_NONE));
 
 
 	ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "timeline_only"), PropertyInfo(Variant::BOOL, "update_position_only")));
 	ADD_SIGNAL(MethodInfo("timeline_changed", PropertyInfo(Variant::FLOAT, "position"), PropertyInfo(Variant::BOOL, "timeline_only"), PropertyInfo(Variant::BOOL, "update_position_only")));
 	ADD_SIGNAL(MethodInfo("keying_changed"));
 	ADD_SIGNAL(MethodInfo("keying_changed"));
@@ -7870,6 +7879,21 @@ AnimationTrackEditor::AnimationTrackEditor() {
 	spacer->set_h_size_flags(SIZE_EXPAND_FILL);
 	spacer->set_h_size_flags(SIZE_EXPAND_FILL);
 	bottom_hf->add_child(spacer);
 	bottom_hf->add_child(spacer);
 
 
+	Label *bezier_key_default_label = memnew(Label);
+	bezier_key_default_label->set_text(TTR("Bezier Default Mode:"));
+	bottom_hf->add_child(bezier_key_default_label);
+
+	bezier_key_mode = memnew(OptionButton);
+	bezier_key_mode->add_item(TTR("Free"));
+	bezier_key_mode->add_item(TTR("Linear"));
+	bezier_key_mode->add_item(TTR("Balanced"));
+	bezier_key_mode->add_item(TTR("Mirrored"));
+	bezier_key_mode->set_tooltip_text(TTR("Set the default behavior of new bezier keys."));
+	bezier_key_mode->select(2);
+
+	bottom_hf->add_child(bezier_key_mode);
+	bottom_hf->add_child(memnew(VSeparator));
+
 	bezier_edit_icon = memnew(Button);
 	bezier_edit_icon = memnew(Button);
 	bezier_edit_icon->set_flat(true);
 	bezier_edit_icon->set_flat(true);
 	bezier_edit_icon->set_disabled(true);
 	bezier_edit_icon->set_disabled(true);

+ 2 - 0
editor/animation_track_editor.h

@@ -618,6 +618,7 @@ class AnimationTrackEditor : public VBoxContainer {
 	OptionButton *snap_mode = nullptr;
 	OptionButton *snap_mode = nullptr;
 	Button *auto_fit = nullptr;
 	Button *auto_fit = nullptr;
 	Button *auto_fit_bezier = nullptr;
 	Button *auto_fit_bezier = nullptr;
+	OptionButton *bezier_key_mode = nullptr;
 
 
 	Button *imported_anim_warning = nullptr;
 	Button *imported_anim_warning = nullptr;
 	void _show_imported_anim_warning();
 	void _show_imported_anim_warning();
@@ -767,6 +768,7 @@ class AnimationTrackEditor : public VBoxContainer {
 	void _cancel_bezier_edit();
 	void _cancel_bezier_edit();
 	void _bezier_edit(int p_for_track);
 	void _bezier_edit(int p_for_track);
 	void _bezier_track_set_key_handle_mode(Animation *p_anim, int p_track, int p_index, Animation::HandleMode p_mode, Animation::HandleSetMode p_set_mode = Animation::HANDLE_SET_MODE_NONE);
 	void _bezier_track_set_key_handle_mode(Animation *p_anim, int p_track, int p_index, Animation::HandleMode p_mode, Animation::HandleSetMode p_set_mode = Animation::HANDLE_SET_MODE_NONE);
+	void _bezier_track_set_key_handle_mode_at_time(Animation *p_anim, int p_track, float p_time, Animation::HandleMode p_mode, Animation::HandleSetMode p_set_mode = Animation::HANDLE_SET_MODE_NONE);
 
 
 	////////////// edit menu stuff
 	////////////// edit menu stuff
 
 

+ 90 - 73
scene/resources/animation.cpp

@@ -3492,79 +3492,10 @@ void Animation::bezier_track_set_key_handle_mode(int p_track, int p_index, Handl
 
 
 	bt->values.write[p_index].value.handle_mode = p_mode;
 	bt->values.write[p_index].value.handle_mode = p_mode;
 
 
-	switch (p_mode) {
-		case HANDLE_MODE_LINEAR: {
-			bt->values.write[p_index].value.in_handle = Vector2(0, 0);
-			bt->values.write[p_index].value.out_handle = Vector2(0, 0);
-		} break;
-		case HANDLE_MODE_BALANCED:
-		case HANDLE_MODE_MIRRORED: {
-			int prev_key = MAX(0, p_index - 1);
-			int next_key = MIN(bt->values.size() - 1, p_index + 1);
-			if (prev_key == next_key) {
-				break; // Exists only one key.
-			}
-			real_t in_handle_x = 0;
-			real_t in_handle_y = 0;
-			real_t out_handle_x = 0;
-			real_t out_handle_y = 0;
-			if (p_mode == HANDLE_MODE_BALANCED) {
-				// Note:
-				// If p_set_mode == HANDLE_SET_MODE_NONE, I don't know if it should change the Tangent implicitly.
-				// At the least, we need to avoid corrupting the handles when loading animation from the resource.
-				// However, changes made by the Inspector do not go through the BezierEditor,
-				// so if you change from Free to Balanced or Mirrored in Inspector, there is no guarantee that
-				// it is Balanced or Mirrored until there is a handle operation.
-				if (p_set_mode == HANDLE_SET_MODE_RESET) {
-					real_t handle_length = 1.0 / 3.0;
-					in_handle_x = (bt->values[prev_key].time - bt->values[p_index].time) * handle_length;
-					in_handle_y = 0;
-					out_handle_x = (bt->values[next_key].time - bt->values[p_index].time) * handle_length;
-					out_handle_y = 0;
-					bt->values.write[p_index].value.in_handle = Vector2(in_handle_x, in_handle_y);
-					bt->values.write[p_index].value.out_handle = Vector2(out_handle_x, out_handle_y);
-				} else if (p_set_mode == HANDLE_SET_MODE_AUTO) {
-					real_t handle_length = 1.0 / 6.0;
-					real_t tangent = (bt->values[next_key].value.value - bt->values[prev_key].value.value) / (bt->values[next_key].time - bt->values[prev_key].time);
-					in_handle_x = (bt->values[prev_key].time - bt->values[p_index].time) * handle_length;
-					in_handle_y = in_handle_x * tangent;
-					out_handle_x = (bt->values[next_key].time - bt->values[p_index].time) * handle_length;
-					out_handle_y = out_handle_x * tangent;
-					bt->values.write[p_index].value.in_handle = Vector2(in_handle_x, in_handle_y);
-					bt->values.write[p_index].value.out_handle = Vector2(out_handle_x, out_handle_y);
-				}
-			} else {
-				real_t handle_length = 1.0 / 4.0;
-				real_t prev_interval = Math::abs(bt->values[p_index].time - bt->values[prev_key].time);
-				real_t next_interval = Math::abs(bt->values[p_index].time - bt->values[next_key].time);
-				real_t min_time = 0;
-				if (Math::is_zero_approx(prev_interval)) {
-					min_time = next_interval;
-				} else if (Math::is_zero_approx(next_interval)) {
-					min_time = prev_interval;
-				} else {
-					min_time = MIN(prev_interval, next_interval);
-				}
-				if (p_set_mode == HANDLE_SET_MODE_RESET) {
-					in_handle_x = -min_time * handle_length;
-					in_handle_y = 0;
-					out_handle_x = min_time * handle_length;
-					out_handle_y = 0;
-					bt->values.write[p_index].value.in_handle = Vector2(in_handle_x, in_handle_y);
-					bt->values.write[p_index].value.out_handle = Vector2(out_handle_x, out_handle_y);
-				} else if (p_set_mode == HANDLE_SET_MODE_AUTO) {
-					real_t tangent = (bt->values[next_key].value.value - bt->values[prev_key].value.value) / min_time;
-					in_handle_x = -min_time * handle_length;
-					in_handle_y = in_handle_x * tangent;
-					out_handle_x = min_time * handle_length;
-					out_handle_y = out_handle_x * tangent;
-					bt->values.write[p_index].value.in_handle = Vector2(in_handle_x, in_handle_y);
-					bt->values.write[p_index].value.out_handle = Vector2(out_handle_x, out_handle_y);
-				}
-			}
-		} break;
-		default: {
-		} break;
+	if (p_mode != HANDLE_MODE_FREE && p_set_mode != HANDLE_SET_MODE_NONE) {
+		Vector2 &in_handle = bt->values.write[p_index].value.in_handle;
+		Vector2 &out_handle = bt->values.write[p_index].value.out_handle;
+		bezier_track_calculate_handles(p_track, p_index, p_mode, p_set_mode, &in_handle, &out_handle);
 	}
 	}
 
 
 	emit_changed();
 	emit_changed();
@@ -3581,6 +3512,92 @@ Animation::HandleMode Animation::bezier_track_get_key_handle_mode(int p_track, i
 
 
 	return bt->values[p_index].value.handle_mode;
 	return bt->values[p_index].value.handle_mode;
 }
 }
+
+bool Animation::bezier_track_calculate_handles(int p_track, int p_index, HandleMode p_mode, HandleSetMode p_set_mode, Vector2 *r_in_handle, Vector2 *r_out_handle) {
+	ERR_FAIL_INDEX_V(p_track, tracks.size(), false);
+	Track *t = tracks[p_track];
+	ERR_FAIL_COND_V(t->type != TYPE_BEZIER, false);
+
+	BezierTrack *bt = static_cast<BezierTrack *>(t);
+	ERR_FAIL_INDEX_V(p_index, bt->values.size(), false);
+
+	int prev_key = MAX(0, p_index - 1);
+	int next_key = MIN(bt->values.size() - 1, p_index + 1);
+	if (prev_key == next_key) {
+		return false;
+	}
+
+	float time = bt->values[p_index].time;
+	float prev_time = bt->values[prev_key].time;
+	float prev_value = bt->values[prev_key].value.value;
+	float next_time = bt->values[next_key].time;
+	float next_value = bt->values[next_key].value.value;
+
+	return bezier_track_calculate_handles(time, prev_time, prev_value, next_time, next_value, p_mode, p_set_mode, r_in_handle, r_out_handle);
+}
+
+bool Animation::bezier_track_calculate_handles(float p_time, float p_prev_time, float p_prev_value, float p_next_time, float p_next_value, HandleMode p_mode, HandleSetMode p_set_mode, Vector2 *r_in_handle, Vector2 *r_out_handle) {
+	ERR_FAIL_COND_V(p_mode == HANDLE_MODE_FREE, false);
+	ERR_FAIL_COND_V(p_set_mode == HANDLE_SET_MODE_NONE, false);
+
+	Vector2 in_handle;
+	Vector2 out_handle;
+
+	if (p_mode == HANDLE_MODE_LINEAR) {
+		in_handle = Vector2(0, 0);
+		out_handle = Vector2(0, 0);
+	} else if (p_mode == HANDLE_MODE_BALANCED) {
+		if (p_set_mode == HANDLE_SET_MODE_RESET) {
+			real_t handle_length = 1.0 / 3.0;
+			in_handle.x = (p_prev_time - p_time) * handle_length;
+			in_handle.y = 0;
+			out_handle.x = (p_next_time - p_time) * handle_length;
+			out_handle.y = 0;
+		} else if (p_set_mode == HANDLE_SET_MODE_AUTO) {
+			real_t handle_length = 1.0 / 6.0;
+			real_t tangent = (p_next_value - p_prev_value) / (p_next_time - p_prev_time);
+			in_handle.x = (p_prev_time - p_time) * handle_length;
+			in_handle.y = in_handle.x * tangent;
+			out_handle.x = (p_next_time - p_time) * handle_length;
+			out_handle.y = out_handle.x * tangent;
+		}
+	} else if (p_mode == HANDLE_MODE_MIRRORED) {
+		real_t handle_length = 1.0 / 4.0;
+		real_t prev_interval = Math::abs(p_time - p_prev_time);
+		real_t next_interval = Math::abs(p_time - p_next_time);
+		real_t min_time = 0;
+		if (Math::is_zero_approx(prev_interval)) {
+			min_time = next_interval;
+		} else if (Math::is_zero_approx(next_interval)) {
+			min_time = prev_interval;
+		} else {
+			min_time = MIN(prev_interval, next_interval);
+		}
+		if (p_set_mode == HANDLE_SET_MODE_RESET) {
+			in_handle.x = -min_time * handle_length;
+			in_handle.y = 0;
+			out_handle.x = min_time * handle_length;
+			out_handle.y = 0;
+		} else if (p_set_mode == HANDLE_SET_MODE_AUTO) {
+			real_t tangent = (p_next_value - p_prev_value) / min_time;
+			in_handle.x = -min_time * handle_length;
+			in_handle.y = in_handle.x * tangent;
+			out_handle.x = min_time * handle_length;
+			out_handle.y = out_handle.x * tangent;
+		}
+	}
+
+	if (r_in_handle != nullptr) {
+		*r_in_handle = in_handle;
+	}
+
+	if (r_out_handle != nullptr) {
+		*r_out_handle = out_handle;
+	}
+
+	return true;
+}
+
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 
 
 real_t Animation::bezier_track_interpolate(int p_track, double p_time) const {
 real_t Animation::bezier_track_interpolate(int p_track, double p_time) const {

+ 2 - 0
scene/resources/animation.h

@@ -481,6 +481,8 @@ public:
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 	void bezier_track_set_key_handle_mode(int p_track, int p_index, HandleMode p_mode, HandleSetMode p_set_mode = HANDLE_SET_MODE_NONE);
 	void bezier_track_set_key_handle_mode(int p_track, int p_index, HandleMode p_mode, HandleSetMode p_set_mode = HANDLE_SET_MODE_NONE);
 	HandleMode bezier_track_get_key_handle_mode(int p_track, int p_index) const;
 	HandleMode bezier_track_get_key_handle_mode(int p_track, int p_index) const;
+	bool bezier_track_calculate_handles(int p_track, int p_index, HandleMode p_mode, HandleSetMode p_set_mode, Vector2 *r_in_handle, Vector2 *r_out_handle);
+	bool bezier_track_calculate_handles(float p_time, float p_prev_time, float p_prev_value, float p_next_time, float p_next_value, HandleMode p_mode, HandleSetMode p_set_mode, Vector2 *r_in_handle, Vector2 *r_out_handle);
 #endif // TOOLS_ENABLED
 #endif // TOOLS_ENABLED
 
 
 	real_t bezier_track_interpolate(int p_track, double p_time) const;
 	real_t bezier_track_interpolate(int p_track, double p_time) const;