diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 86d08de4306..a832c772a6f 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -6196,15 +6196,12 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { case EDIT_GOTO_PREV_STEP: { goto_prev_step(false); } break; - case EDIT_APPLY_RESET: { - AnimationPlayerEditor::get_singleton()->get_player()->apply_reset(true); - } break; - case EDIT_BAKE_ANIMATION: { + case EDIT_BAKE_TRACK: { bake_dialog->popup_centered(Size2(200, 100) * EDSCALE); } break; - case EDIT_BAKE_ANIMATION_CONFIRM: { - undo_redo->create_action(TTR("Bake Animation as Linear keys.")); + case EDIT_BAKE_TRACK_CONFIRM: { + undo_redo->create_action(TTR("Bake Track as Linear keys.")); int track_len = animation->get_track_count(); bool b_trs = bake_trs->is_pressed(); @@ -6311,6 +6308,10 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { } break; + case EDIT_APPLY_RESET: { + AnimationPlayerEditor::get_singleton()->get_player()->apply_reset(true); + } break; + case EDIT_OPTIMIZE_ANIMATION: { optimize_dialog->popup_centered(Size2(250, 180) * EDSCALE); @@ -6319,6 +6320,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) { animation->optimize(optimize_velocity_error->get_value(), optimize_angular_error->get_value(), optimize_precision_error->get_value()); _update_tracks(); undo_redo->clear_history(true, undo_redo->get_history_for_object(animation.ptr()).id); + undo_redo->clear_history(true, undo_redo->get_history_for_object(this).id); } break; case EDIT_CLEAN_UP_ANIMATION: { @@ -6387,6 +6389,7 @@ void AnimationTrackEditor::_cleanup_animation(Ref p_animation) { } undo_redo->clear_history(true, undo_redo->get_history_for_object(animation.ptr()).id); + undo_redo->clear_history(true, undo_redo->get_history_for_object(this).id); _update_tracks(); } @@ -6732,11 +6735,12 @@ AnimationTrackEditor::AnimationTrackEditor() { edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_next_step", TTR("Go to Next Step"), KeyModifierMask::CMD | Key::RIGHT), EDIT_GOTO_NEXT_STEP); edit->get_popup()->add_shortcut(ED_SHORTCUT("animation_editor/goto_prev_step", TTR("Go to Previous Step"), KeyModifierMask::CMD | Key::LEFT), EDIT_GOTO_PREV_STEP); edit->get_popup()->add_separator(); + edit->get_popup()->add_item(TTR("Bake Track"), EDIT_BAKE_TRACK); + 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); + edit->get_popup()->add_item(TTR("Optimize Animation (no undo)"), EDIT_OPTIMIZE_ANIMATION); + edit->get_popup()->add_item(TTR("Clean-Up Animation (no undo)"), EDIT_CLEAN_UP_ANIMATION); edit->get_popup()->connect("id_pressed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed)); edit->get_popup()->connect("about_to_popup", callable_mp(this, &AnimationTrackEditor::_edit_menu_about_to_popup)); @@ -6904,8 +6908,8 @@ AnimationTrackEditor::AnimationTrackEditor() { // 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)); + bake_dialog->set_title(TTR("Track Baker")); + bake_dialog->connect("confirmed", callable_mp(this, &AnimationTrackEditor::_edit_menu_pressed).bind(EDIT_BAKE_TRACK_CONFIRM)); add_child(bake_dialog); GridContainer *bake_grid = memnew(GridContainer); bake_grid->set_columns(2); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index 990ee5f6be9..a17ee65eab9 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -545,9 +545,9 @@ public: EDIT_GOTO_NEXT_STEP, EDIT_GOTO_NEXT_STEP_TIMELINE_ONLY, // Next step without updating animation. EDIT_GOTO_PREV_STEP, + EDIT_BAKE_TRACK, + EDIT_BAKE_TRACK_CONFIRM, EDIT_APPLY_RESET, - EDIT_BAKE_ANIMATION, - EDIT_BAKE_ANIMATION_CONFIRM, EDIT_OPTIMIZE_ANIMATION, EDIT_OPTIMIZE_ANIMATION_CONFIRM, EDIT_CLEAN_UP_ANIMATION, diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 4197251d7ec..823617328ee 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -4048,6 +4048,65 @@ void Animation::clear() { emit_signal(SceneStringNames::get_singleton()->tracks_changed); } +bool Animation::_float_track_optimize_key(const TKey t0, const TKey t1, const TKey t2, real_t p_allowed_velocity_err, real_t p_allowed_precision_error) { + // Remove overlapping keys. + if (Math::is_equal_approx(t0.time, t1.time) || Math::is_equal_approx(t1.time, t2.time)) { + return true; + } + if (abs(t0.value - t1.value) < p_allowed_precision_error && abs(t1.value - t2.value) < p_allowed_precision_error) { + return true; + } + // Calc velocities. + double v0 = (t1.value - t0.value) / (t1.time - t0.time); + double v1 = (t2.value - t1.value) / (t2.time - t1.time); + // Avoid zero div but check equality. + if (abs(v0 - v1) < p_allowed_precision_error) { + return true; + } else if (abs(v0) < p_allowed_precision_error || abs(v1) < p_allowed_precision_error) { + return false; + } + if (!signbit(v0 * v1)) { + v0 = abs(v0); + v1 = abs(v1); + double ratio = v0 < v1 ? v0 / v1 : v1 / v0; + if (ratio >= 1.0 - p_allowed_velocity_err) { + return true; + } + } + return false; +} + +bool Animation::_vector2_track_optimize_key(const TKey t0, const TKey t1, const TKey t2, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error) { + // Remove overlapping keys. + if (Math::is_equal_approx(t0.time, t1.time) || Math::is_equal_approx(t1.time, t2.time)) { + return true; + } + if ((t0.value - t1.value).length() < p_allowed_precision_error && (t1.value - t2.value).length() < p_allowed_precision_error) { + return true; + } + // Calc velocities. + Vector2 vc0 = (t1.value - t0.value) / (t1.time - t0.time); + Vector2 vc1 = (t2.value - t1.value) / (t2.time - t1.time); + double v0 = vc0.length(); + double v1 = vc1.length(); + // Avoid zero div but check equality. + if (abs(v0 - v1) < p_allowed_precision_error) { + return true; + } else if (abs(v0) < p_allowed_precision_error || abs(v1) < p_allowed_precision_error) { + return false; + } + // Check axis. + if (vc0.normalized().dot(vc1.normalized()) >= 1.0 - p_allowed_angular_error * 2.0) { + v0 = abs(v0); + v1 = abs(v1); + double ratio = v0 < v1 ? v0 / v1 : v1 / v0; + if (ratio >= 1.0 - p_allowed_velocity_err) { + return true; + } + } + return false; +} + bool Animation::_vector3_track_optimize_key(const TKey t0, const TKey t1, const TKey t2, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error) { // Remove overlapping keys. if (Math::is_equal_approx(t0.time, t1.time) || Math::is_equal_approx(t1.time, t2.time)) { @@ -4059,8 +4118,8 @@ bool Animation::_vector3_track_optimize_key(const TKey t0, const TKey t0, const TKey= 1.0 - p_allowed_angular_error * 2.0) { - real_t ratio = v0 < v1 ? v0 / v1 : v1 / v0; + v0 = abs(v0); + v1 = abs(v1); + double ratio = v0 < v1 ? v0 / v1 : v1 / v0; if (ratio >= 1.0 - p_allowed_velocity_err) { return true; } @@ -4089,42 +4150,21 @@ bool Animation::_quaternion_track_optimize_key(const TKey t0, const Quaternion q0 = t0.value * t1.value * t0.value.inverse(); Quaternion q1 = t1.value * t2.value * t1.value.inverse(); if (q0.get_axis().dot(q1.get_axis()) >= 1.0 - p_allowed_angular_error * 2.0) { + double a0 = Math::acos(t0.value.dot(t1.value)); + double a1 = Math::acos(t1.value.dot(t2.value)); + if (a0 + a1 >= Math_PI) { + return false; // Rotation is more than 180 deg, keep key. + } // Calc velocities. - real_t v0 = Math::acos(t0.value.dot(t1.value)) / (t1.time - t0.time); - real_t v1 = Math::acos(t1.value.dot(t2.value)) / (t2.time - t1.time); + double v0 = a0 / (t1.time - t0.time); + double v1 = a1 / (t2.time - t1.time); // Avoid zero div but check equality. if (abs(v0 - v1) < p_allowed_precision_error) { return true; } else if (abs(v0) < p_allowed_precision_error || abs(v1) < p_allowed_precision_error) { return false; } - real_t ratio = v0 < v1 ? v0 / v1 : v1 / v0; - if (ratio >= 1.0 - p_allowed_velocity_err) { - return true; - } - } - return false; -} - -bool Animation::_float_track_optimize_key(const TKey t0, const TKey t1, const TKey t2, real_t p_allowed_velocity_err, real_t p_allowed_precision_error) { - // Remove overlapping keys. - if (Math::is_equal_approx(t0.time, t1.time) || Math::is_equal_approx(t1.time, t2.time)) { - return true; - } - if (abs(t0.value - t1.value) < p_allowed_precision_error && abs(t1.value - t2.value) < p_allowed_precision_error) { - return true; - } - // Calc velocities. - real_t v0 = (t1.value - t0.value) / (t1.time - t0.time); - real_t v1 = (t2.value - t1.value) / (t2.time - t1.time); - // Avoid zero div but check equality. - if (abs(v0 - v1) < p_allowed_precision_error) { - return true; - } else if (abs(v0) < p_allowed_precision_error || abs(v1) < p_allowed_precision_error) { - return false; - } - if (!signbit(v0 * v1)) { - real_t ratio = v0 < v1 ? v0 / v1 : v1 / v0; + double ratio = v0 < v1 ? v0 / v1 : v1 / v0; if (ratio >= 1.0 - p_allowed_velocity_err) { return true; } @@ -4236,6 +4276,125 @@ void Animation::_blend_shape_track_optimize(int p_idx, real_t p_allowed_velocity } } +void Animation::_value_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_err, real_t p_allowed_precision_error) { + ERR_FAIL_INDEX(p_idx, tracks.size()); + ERR_FAIL_COND(tracks[p_idx]->type != TYPE_VALUE); + ValueTrack *tt = static_cast(tracks[p_idx]); + if (tt->values.size() == 0) { + return; + } + Variant::Type type = tt->values[0].value.get_type(); + + // Special case for angle interpolation. + bool is_using_angle = tt->interpolation == Animation::INTERPOLATION_LINEAR_ANGLE || tt->interpolation == Animation::INTERPOLATION_CUBIC_ANGLE; + int i = 0; + while (i < tt->values.size() - 2) { + bool erase = false; + switch (type) { + case Variant::FLOAT: { + TKey t0; + TKey t1; + TKey t2; + t0.time = tt->values[i].time; + t1.time = tt->values[i + 1].time; + t2.time = tt->values[i + 2].time; + t0.value = tt->values[i].value; + t1.value = tt->values[i + 1].value; + t2.value = tt->values[i + 2].value; + if (is_using_angle) { + float diff1 = fmod(t1.value - t0.value, Math_TAU); + t1.value = t0.value + fmod(2.0 * diff1, Math_TAU) - diff1; + float diff2 = fmod(t2.value - t1.value, Math_TAU); + t2.value = t1.value + fmod(2.0 * diff2, Math_TAU) - diff2; + if (abs(abs(diff1) + abs(diff2)) >= Math_PI) { + break; // Rotation is more than 180 deg, keep key. + } + } + erase = _float_track_optimize_key(t0, t1, t2, p_allowed_velocity_err, p_allowed_precision_error); + } break; + case Variant::VECTOR2: { + TKey t0; + TKey t1; + TKey t2; + t0.time = tt->values[i].time; + t1.time = tt->values[i + 1].time; + t2.time = tt->values[i + 2].time; + t0.value = tt->values[i].value; + t1.value = tt->values[i + 1].value; + t2.value = tt->values[i + 2].value; + erase = _vector2_track_optimize_key(t0, t1, t2, p_allowed_velocity_err, p_allowed_angular_err, p_allowed_precision_error); + } break; + case Variant::VECTOR3: { + TKey t0; + TKey t1; + TKey t2; + t0.time = tt->values[i].time; + t1.time = tt->values[i + 1].time; + t2.time = tt->values[i + 2].time; + t0.value = tt->values[i].value; + t1.value = tt->values[i + 1].value; + t2.value = tt->values[i + 2].value; + erase = _vector3_track_optimize_key(t0, t1, t2, p_allowed_velocity_err, p_allowed_angular_err, p_allowed_precision_error); + } break; + case Variant::QUATERNION: { + TKey t0; + TKey t1; + TKey t2; + t0.time = tt->values[i].time; + t1.time = tt->values[i + 1].time; + t2.time = tt->values[i + 2].time; + t0.value = tt->values[i].value; + t1.value = tt->values[i + 1].value; + t2.value = tt->values[i + 2].value; + erase = _quaternion_track_optimize_key(t0, t1, t2, p_allowed_velocity_err, p_allowed_angular_err, p_allowed_precision_error); + } break; + default: { + } break; + } + + if (erase) { + tt->values.remove_at(i + 1); + } else { + i++; + } + } + + if (tt->values.size() == 2) { + bool single_key = false; + switch (type) { + case Variant::FLOAT: { + float val_0 = tt->values[0].value; + float val_1 = tt->values[1].value; + if (is_using_angle) { + float diff1 = fmod(val_1 - val_0, Math_TAU); + val_1 = val_0 + fmod(2.0 * diff1, Math_TAU) - diff1; + } + single_key = abs(val_0 - val_1) < p_allowed_precision_error; + } break; + case Variant::VECTOR2: { + Vector2 val_0 = tt->values[0].value; + Vector2 val_1 = tt->values[1].value; + single_key = (val_0 - val_1).length() < p_allowed_precision_error; + } break; + case Variant::VECTOR3: { + Vector3 val_0 = tt->values[0].value; + Vector3 val_1 = tt->values[1].value; + single_key = (val_0 - val_1).length() < p_allowed_precision_error; + } break; + case Variant::QUATERNION: { + Quaternion val_0 = tt->values[0].value; + Quaternion val_1 = tt->values[1].value; + single_key = (val_0 - val_1).length() < p_allowed_precision_error; + } break; + default: { + } break; + } + if (single_key) { + tt->values.remove_at(1); + } + } +} + void Animation::optimize(real_t p_allowed_velocity_err, real_t p_allowed_angular_err, int p_precision) { real_t precision = Math::pow(0.1, p_precision); for (int i = 0; i < tracks.size(); i++) { @@ -4250,6 +4409,8 @@ void Animation::optimize(real_t p_allowed_velocity_err, real_t p_allowed_angular _scale_track_optimize(i, p_allowed_velocity_err, p_allowed_angular_err, precision); } else if (tracks[i]->type == TYPE_BLEND_SHAPE) { _blend_shape_track_optimize(i, p_allowed_velocity_err, precision); + } else if (tracks[i]->type == TYPE_VALUE) { + _value_track_optimize(i, p_allowed_velocity_err, p_allowed_angular_err, precision); } } } diff --git a/scene/resources/animation.h b/scene/resources/animation.h index 8fa02752308..46a88df130b 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -366,14 +366,16 @@ private: return idxr; } + bool _float_track_optimize_key(const TKey t0, const TKey t1, const TKey t2, real_t p_allowed_velocity_err, real_t p_allowed_precision_error); + bool _vector2_track_optimize_key(const TKey t0, const TKey t1, const TKey t2, real_t p_alowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error); bool _vector3_track_optimize_key(const TKey t0, const TKey t1, const TKey t2, real_t p_alowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error); bool _quaternion_track_optimize_key(const TKey t0, const TKey t1, const TKey t2, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error); - bool _float_track_optimize_key(const TKey t0, const TKey t1, const TKey t2, real_t p_allowed_velocity_err, real_t p_allowed_precision_error); void _position_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_err, real_t p_allowed_precision_error); void _rotation_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_error, real_t p_allowed_precision_error); void _scale_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_err, real_t p_allowed_precision_error); void _blend_shape_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_precision_error); + void _value_track_optimize(int p_idx, real_t p_allowed_velocity_err, real_t p_allowed_angular_err, real_t p_allowed_precision_error); protected: bool _set(const StringName &p_name, const Variant &p_value);