Browse Source

Merge pull request #64678 from TokageItLab/implement-ease-baker

Fix Quaternion Tween and add Easing baker to AnimationTrackEditor
Rémi Verschelde 3 years ago
parent
commit
b9919fd87d

+ 3 - 2
core/math/quaternion.cpp

@@ -112,10 +112,11 @@ Quaternion Quaternion::exp() const {
 	Quaternion src = *this;
 	Vector3 src_v = Vector3(src.x, src.y, src.z);
 	real_t theta = src_v.length();
-	if (theta < CMP_EPSILON) {
+	src_v = src_v.normalized();
+	if (theta < CMP_EPSILON || !src_v.is_normalized()) {
 		return Quaternion(0, 0, 0, 1);
 	}
-	return Quaternion(src_v.normalized(), theta);
+	return Quaternion(src_v, theta);
 }
 
 Quaternion Quaternion::slerp(const Quaternion &p_to, const real_t &p_weight) const {

+ 276 - 0
editor/animation_track_editor.cpp

@@ -38,6 +38,7 @@
 #include "editor/editor_undo_redo_manager.h"
 #include "editor/plugins/animation_player_editor_plugin.h"
 #include "scene/animation/animation_player.h"
+#include "scene/animation/tween.h"
 #include "scene/gui/separator.h"
 #include "scene/gui/view_panner.h"
 #include "scene/main/window.h"
@@ -5970,6 +5971,89 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
 #undef NEW_POS
 			undo_redo->commit_action();
 		} break;
+
+		case EDIT_EASE_SELECTION: {
+			ease_dialog->popup_centered(Size2(200, 100) * EDSCALE);
+		} break;
+		case EDIT_EASE_CONFIRM: {
+			undo_redo->create_action(TTR("Make Easing Keys"));
+
+			Tween::TransitionType transition_type = static_cast<Tween::TransitionType>(transition_selection->get_selected_id());
+			Tween::EaseType ease_type = static_cast<Tween::EaseType>(ease_selection->get_selected_id());
+			float fps = ease_fps->get_value();
+			double dur_step = 1.0 / fps;
+
+			// Organize track and key.
+			HashMap<int, Vector<int>> keymap;
+			Vector<int> tracks;
+			for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
+				if (!tracks.has(E.key.track)) {
+					tracks.append(E.key.track);
+				}
+			}
+			for (int i = 0; i < tracks.size(); i++) {
+				switch (animation->track_get_type(tracks[i])) {
+					case Animation::TYPE_VALUE:
+					case Animation::TYPE_POSITION_3D:
+					case Animation::TYPE_ROTATION_3D:
+					case Animation::TYPE_SCALE_3D:
+					case Animation::TYPE_BLEND_SHAPE: {
+						Vector<int> keys;
+						for (const KeyValue<SelectedKey, KeyInfo> &E : selection) {
+							if (E.key.track == tracks[i]) {
+								keys.append(E.key.key);
+							}
+						}
+						keys.sort();
+						keymap.insert(tracks[i], keys);
+					} break;
+					default: {
+					} break;
+				}
+			}
+
+			// Make easing.
+			HashMap<int, Vector<int>>::Iterator E = keymap.begin();
+			while (E) {
+				int track = E->key;
+				Vector<int> keys = E->value;
+				int len = keys.size() - 1;
+
+				// Make insert queue.
+				Vector<Pair<double, Variant>> insert_queue;
+				for (int i = 0; i < len; i++) {
+					// Check neighboring keys.
+					if (keys[i] + 1 == keys[i + 1]) {
+						double from_t = animation->track_get_key_time(track, keys[i]);
+						double to_t = animation->track_get_key_time(track, keys[i + 1]);
+						Variant from_v = animation->track_get_key_value(track, keys[i]);
+						Variant to_v = animation->track_get_key_value(track, keys[i + 1]);
+						Variant delta_v;
+						Variant::sub(to_v, from_v, delta_v);
+						double duration = to_t - from_t;
+						double fixed_duration = duration - 0.01; // Prevent to overwrap keys...
+						for (double delta_t = dur_step; delta_t < fixed_duration; delta_t += dur_step) {
+							Pair<double, Variant> keydata;
+							keydata.first = from_t + delta_t;
+							keydata.second = Tween::interpolate_variant(from_v, delta_v, delta_t, duration, transition_type, ease_type);
+							insert_queue.append(keydata);
+						}
+					}
+				}
+
+				// Do insertion.
+				for (int i = 0; i < insert_queue.size(); i++) {
+					undo_redo->add_do_method(animation.ptr(), "track_insert_key", track, insert_queue[i].first, insert_queue[i].second);
+					undo_redo->add_undo_method(animation.ptr(), "track_remove_key_at_time", track, insert_queue[i].first);
+				}
+
+				++E;
+			}
+
+			undo_redo->commit_action();
+
+		} break;
+
 		case EDIT_DUPLICATE_SELECTION: {
 			if (bezier_edit->is_visible()) {
 				bezier_edit->duplicate_selection();
@@ -6061,8 +6145,115 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
 		} break;
 		case EDIT_APPLY_RESET: {
 			AnimationPlayerEditor::get_singleton()->get_player()->apply_reset(true);
+		} break;
 
+		case EDIT_BAKE_ANIMATION: {
+			bake_dialog->popup_centered(Size2(200, 100) * EDSCALE);
 		} break;
+		case EDIT_BAKE_ANIMATION_CONFIRM: {
+			undo_redo->create_action(TTR("Bake Animation as Linear keys."));
+
+			int track_len = animation->get_track_count();
+			bool b_trs = bake_trs->is_pressed();
+			bool b_bs = bake_blendshape->is_pressed();
+			bool b_v = bake_value->is_pressed();
+
+			double anim_len = animation->get_length() + CMP_EPSILON; // For end key.
+			float fps = bake_fps->get_value();
+			double dur_step = 1.0 / fps;
+
+			for (int i = 0; i < track_len; i++) {
+				bool do_bake = false;
+				Animation::TrackType type = animation->track_get_type(i);
+				do_bake |= b_trs && (type == Animation::TYPE_POSITION_3D || type == Animation::TYPE_ROTATION_3D || type == Animation::TYPE_SCALE_3D);
+				do_bake |= b_bs && type == Animation::TYPE_BLEND_SHAPE;
+				do_bake |= b_v && type == Animation::TYPE_VALUE;
+				if (do_bake && !animation->track_is_compressed(i)) {
+					if (animation->track_get_interpolation_type(i) == Animation::INTERPOLATION_NEAREST) {
+						continue; // Nearest interpolation cannot be baked.
+					}
+
+					// Make insert queue.
+					Vector<Pair<double, Variant>> insert_queue;
+
+					switch (type) {
+						case Animation::TYPE_POSITION_3D: {
+							for (double delta_t = 0.0; delta_t <= anim_len; delta_t += dur_step) {
+								Pair<double, Variant> keydata;
+								keydata.first = delta_t;
+								Vector3 v;
+								animation->position_track_interpolate(i, delta_t, &v);
+								keydata.second = v;
+								insert_queue.append(keydata);
+							}
+						} break;
+						case Animation::TYPE_ROTATION_3D: {
+							for (double delta_t = 0.0; delta_t <= anim_len; delta_t += dur_step) {
+								Pair<double, Variant> keydata;
+								keydata.first = delta_t;
+								Quaternion v;
+								animation->rotation_track_interpolate(i, delta_t, &v);
+								keydata.second = v;
+								insert_queue.append(keydata);
+							}
+						} break;
+						case Animation::TYPE_SCALE_3D: {
+							for (double delta_t = 0.0; delta_t <= anim_len; delta_t += dur_step) {
+								Pair<double, Variant> keydata;
+								keydata.first = delta_t;
+								Vector3 v;
+								animation->scale_track_interpolate(i, delta_t, &v);
+								keydata.second = v;
+								insert_queue.append(keydata);
+							}
+						} break;
+						case Animation::TYPE_BLEND_SHAPE: {
+							for (double delta_t = 0.0; delta_t <= anim_len; delta_t += dur_step) {
+								Pair<double, Variant> keydata;
+								keydata.first = delta_t;
+								float v;
+								animation->blend_shape_track_interpolate(i, delta_t, &v);
+								keydata.second = v;
+								insert_queue.append(keydata);
+							}
+						} break;
+						case Animation::TYPE_VALUE: {
+							for (double delta_t = 0.0; delta_t < anim_len; delta_t += dur_step) {
+								Pair<double, Variant> keydata;
+								keydata.first = delta_t;
+								keydata.second = animation->value_track_interpolate(i, delta_t);
+								insert_queue.append(keydata);
+							}
+						} break;
+						default: {
+						} break;
+					}
+
+					// Cleanup keys.
+					int key_len = animation->track_get_key_count(i);
+					for (int j = key_len - 1; j >= 0; j--) {
+						undo_redo->add_do_method(animation.ptr(), "track_remove_key", i, j);
+					}
+
+					// Insert keys.
+					undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", i, Animation::INTERPOLATION_LINEAR);
+					for (int j = insert_queue.size() - 1; j >= 0; j--) {
+						undo_redo->add_do_method(animation.ptr(), "track_insert_key", i, insert_queue[j].first, insert_queue[j].second);
+						undo_redo->add_undo_method(animation.ptr(), "track_remove_key", i, j);
+					}
+
+					// Undo methods.
+					undo_redo->add_undo_method(animation.ptr(), "track_set_interpolation_type", i, animation->track_get_interpolation_type(i));
+					for (int j = key_len - 1; j >= 0; j--) {
+						undo_redo->add_undo_method(animation.ptr(), "track_insert_key", i, animation->track_get_key_time(i, j), animation->track_get_key_value(i, j), animation->track_get_key_transition(i, j));
+					}
+				}
+			}
+
+			undo_redo->commit_action();
+
+		} break;
+
 		case EDIT_OPTIMIZE_ANIMATION: {
 			optimize_dialog->popup_centered(Size2(250, 180) * EDSCALE);
 
@@ -6470,6 +6661,8 @@ AnimationTrackEditor::AnimationTrackEditor() {
 	edit->get_popup()->add_item(TTR("Scale Selection"), EDIT_SCALE_SELECTION);
 	edit->get_popup()->add_item(TTR("Scale From Cursor"), EDIT_SCALE_FROM_CURSOR);
 	edit->get_popup()->add_separator();
+	edit->get_popup()->add_item(TTR("Make Easing Selection"), EDIT_EASE_SELECTION);
+	edit->get_popup()->add_separator();
 	edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selection", TTR("Duplicate Selection"), KeyModifierMask::CMD | Key::D), EDIT_DUPLICATE_SELECTION);
 	edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/duplicate_selection_transposed", TTR("Duplicate Transposed"), KeyModifierMask::SHIFT | KeyModifierMask::CMD | Key::D), EDIT_DUPLICATE_TRANSPOSED);
 	edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/add_reset_value", TTR("Add RESET Value(s)")));
@@ -6482,6 +6675,7 @@ AnimationTrackEditor::AnimationTrackEditor() {
 	edit->get_popup()->add_separator();
 	edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/apply_reset", TTR("Apply Reset")), EDIT_APPLY_RESET);
 	edit->get_popup()->add_separator();
+	edit->get_popup()->add_item(TTR("Bake Animation"), EDIT_BAKE_ANIMATION);
 	edit->get_popup()->add_item(TTR("Optimize Animation"), EDIT_OPTIMIZE_ANIMATION);
 	edit->get_popup()->add_item(TTR("Clean-Up Animation"), EDIT_CLEAN_UP_ANIMATION);
 
@@ -6604,6 +6798,88 @@ AnimationTrackEditor::AnimationTrackEditor() {
 	scale_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_SCALE_CONFIRM));
 	add_child(scale_dialog);
 
+	//
+	ease_dialog = memnew(ConfirmationDialog);
+	ease_dialog->set_title(TTR("Select Transition and Easing"));
+	ease_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_EASE_CONFIRM));
+	add_child(ease_dialog);
+	GridContainer *ease_grid = memnew(GridContainer);
+	ease_grid->set_columns(2);
+	ease_dialog->add_child(ease_grid);
+	transition_selection = memnew(OptionButton);
+	transition_selection->add_item("Linear", Tween::TRANS_LINEAR);
+	transition_selection->add_item("Sine", Tween::TRANS_SINE);
+	transition_selection->add_item("Quint", Tween::TRANS_QUINT);
+	transition_selection->add_item("Quart", Tween::TRANS_QUART);
+	transition_selection->add_item("Quad", Tween::TRANS_QUAD);
+	transition_selection->add_item("Expo", Tween::TRANS_EXPO);
+	transition_selection->add_item("Elastic", Tween::TRANS_ELASTIC);
+	transition_selection->add_item("Cubic", Tween::TRANS_CUBIC);
+	transition_selection->add_item("Circ", Tween::TRANS_CIRC);
+	transition_selection->add_item("Bounce", Tween::TRANS_BOUNCE);
+	transition_selection->add_item("Back", Tween::TRANS_BACK);
+	transition_selection->select(Tween::TRANS_LINEAR); // Default
+	ease_selection = memnew(OptionButton);
+	ease_selection->add_item("In", Tween::EASE_IN);
+	ease_selection->add_item("Out", Tween::EASE_OUT);
+	ease_selection->add_item("InOut", Tween::EASE_IN_OUT);
+	ease_selection->add_item("OutIn", Tween::EASE_OUT_IN);
+	ease_selection->select(Tween::EASE_IN_OUT); // Default
+	ease_fps = memnew(SpinBox);
+	ease_fps->set_min(1);
+	ease_fps->set_max(999);
+	ease_fps->set_step(1);
+	ease_fps->set_value(30); // Default
+	Label *ease_label1 = memnew(Label);
+	Label *ease_label2 = memnew(Label);
+	Label *ease_label3 = memnew(Label);
+	ease_label1->set_text("Transition Type:");
+	ease_label2->set_text("Ease Type:");
+	ease_label3->set_text("FPS:");
+	ease_grid->add_child(ease_label1);
+	ease_grid->add_child(transition_selection);
+	ease_grid->add_child(ease_label2);
+	ease_grid->add_child(ease_selection);
+	ease_grid->add_child(ease_label3);
+	ease_grid->add_child(ease_fps);
+
+	//
+	bake_dialog = memnew(ConfirmationDialog);
+	bake_dialog->set_title(TTR("Anim. Baker"));
+	bake_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_BAKE_ANIMATION_CONFIRM));
+	add_child(bake_dialog);
+	GridContainer *bake_grid = memnew(GridContainer);
+	bake_grid->set_columns(2);
+	bake_dialog->add_child(bake_grid);
+	bake_trs = memnew(CheckBox);
+	bake_trs->set_pressed(true);
+	bake_blendshape = memnew(CheckBox);
+	bake_blendshape->set_pressed(true);
+	bake_value = memnew(CheckBox);
+	bake_value->set_pressed(true);
+	bake_fps = memnew(SpinBox);
+	bake_fps->set_min(1);
+	bake_fps->set_max(999);
+	bake_fps->set_step(1);
+	bake_fps->set_value(30); // Default
+	Label *bake_label1 = memnew(Label);
+	Label *bake_label2 = memnew(Label);
+	Label *bake_label3 = memnew(Label);
+	Label *bake_label4 = memnew(Label);
+	bake_label1->set_text("Pos/Rot/Scl3D Track:");
+	bake_label2->set_text("Blendshape Track:");
+	bake_label3->set_text("Value Track:");
+	bake_label4->set_text("FPS:");
+	bake_grid->add_child(bake_label1);
+	bake_grid->add_child(bake_trs);
+	bake_grid->add_child(bake_label2);
+	bake_grid->add_child(bake_blendshape);
+	bake_grid->add_child(bake_label3);
+	bake_grid->add_child(bake_value);
+	bake_grid->add_child(bake_label4);
+	bake_grid->add_child(bake_fps);
+
+	//
 	track_copy_dialog = memnew(ConfirmationDialog);
 	add_child(track_copy_dialog);
 	track_copy_dialog->set_title(TTR("Select Tracks to Copy"));

+ 16 - 0
editor/animation_track_editor.h

@@ -32,6 +32,7 @@
 #define ANIMATION_TRACK_EDITOR_H
 
 #include "editor/editor_data.h"
+#include "editor/editor_properties.h"
 #include "editor/editor_spin_slider.h"
 #include "editor/property_selector.h"
 
@@ -451,6 +452,12 @@ class AnimationTrackEditor : public VBoxContainer {
 
 	////////////// edit menu stuff
 
+	ConfirmationDialog *bake_dialog = nullptr;
+	CheckBox *bake_trs = nullptr;
+	CheckBox *bake_blendshape = nullptr;
+	CheckBox *bake_value = nullptr;
+	SpinBox *bake_fps = nullptr;
+
 	ConfirmationDialog *optimize_dialog = nullptr;
 	SpinBox *optimize_velocity_error = nullptr;
 	SpinBox *optimize_angular_error = nullptr;
@@ -464,6 +471,11 @@ class AnimationTrackEditor : public VBoxContainer {
 	ConfirmationDialog *scale_dialog = nullptr;
 	SpinBox *scale = nullptr;
 
+	ConfirmationDialog *ease_dialog = nullptr;
+	OptionButton *transition_selection = nullptr;
+	OptionButton *ease_selection = nullptr;
+	SpinBox *ease_fps = nullptr;
+
 	void _select_all_tracks_for_copy();
 
 	void _edit_menu_about_to_popup();
@@ -521,6 +533,8 @@ public:
 		EDIT_SCALE_SELECTION,
 		EDIT_SCALE_FROM_CURSOR,
 		EDIT_SCALE_CONFIRM,
+		EDIT_EASE_SELECTION,
+		EDIT_EASE_CONFIRM,
 		EDIT_DUPLICATE_SELECTION,
 		EDIT_DUPLICATE_TRANSPOSED,
 		EDIT_ADD_RESET_KEY,
@@ -529,6 +543,8 @@ public:
 		EDIT_GOTO_NEXT_STEP_TIMELINE_ONLY, // Next step without updating animation.
 		EDIT_GOTO_PREV_STEP,
 		EDIT_APPLY_RESET,
+		EDIT_BAKE_ANIMATION,
+		EDIT_BAKE_ANIMATION_CONFIRM,
 		EDIT_OPTIMIZE_ANIMATION,
 		EDIT_OPTIMIZE_ANIMATION_CONFIRM,
 		EDIT_CLEAN_UP_ANIMATION,

+ 2 - 6
scene/animation/tween.cpp

@@ -479,12 +479,8 @@ Variant Tween::interpolate_variant(Variant p_initial_val, Variant p_delta_val, f
 		case Variant::QUATERNION: {
 			Quaternion i = p_initial_val;
 			Quaternion d = p_delta_val;
-			Quaternion r;
-
-			APPLY_EQUATION(x);
-			APPLY_EQUATION(y);
-			APPLY_EQUATION(z);
-			APPLY_EQUATION(w);
+			Quaternion r = i * d;
+			r = i.slerp(r, run_equation(p_trans, p_ease, p_time, 0.0, 1.0, p_duration));
 			return r;
 		}