Skip to content

Commit

Permalink
Add linear/cubic angle interpolation to Animation interpolation type
Browse files Browse the repository at this point in the history
  • Loading branch information
TokageItLab committed Aug 26, 2022
1 parent 4808d01 commit 931fb4d
Show file tree
Hide file tree
Showing 12 changed files with 238 additions and 18 deletions.
60 changes: 60 additions & 0 deletions core/math/math_funcs.h
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,35 @@ class Math {
(-p_pre + 3.0f * p_from - 3.0f * p_to + p_post) * (p_weight * p_weight * p_weight));
}

static _ALWAYS_INLINE_ double cubic_interpolate_angle(double p_from, double p_to, double p_pre, double p_post, double p_weight) {
double from_rot = fmod(p_from, Math_TAU);

double pre_diff = fmod(p_pre - from_rot, Math_TAU);
double pre_rot = from_rot + fmod(2.0 * pre_diff, Math_TAU) - pre_diff;

double to_diff = fmod(p_to - from_rot, Math_TAU);
double to_rot = from_rot + fmod(2.0 * to_diff, Math_TAU) - to_diff;

double post_diff = fmod(p_post - to_rot, Math_TAU);
double post_rot = to_rot + fmod(2.0 * post_diff, Math_TAU) - post_diff;

return cubic_interpolate(from_rot, to_rot, pre_rot, post_rot, p_weight);
}
static _ALWAYS_INLINE_ float cubic_interpolate_angle(float p_from, float p_to, float p_pre, float p_post, float p_weight) {
float from_rot = fmod(p_from, (float)Math_TAU);

float pre_diff = fmod(p_pre - from_rot, (float)Math_TAU);
float pre_rot = from_rot + fmod(2.0f * pre_diff, (float)Math_TAU) - pre_diff;

float to_diff = fmod(p_to - from_rot, (float)Math_TAU);
float to_rot = from_rot + fmod(2.0f * to_diff, (float)Math_TAU) - to_diff;

float post_diff = fmod(p_post - to_rot, (float)Math_TAU);
float post_rot = to_rot + fmod(2.0f * post_diff, (float)Math_TAU) - post_diff;

return cubic_interpolate(from_rot, to_rot, pre_rot, post_rot, p_weight);
}

static _ALWAYS_INLINE_ double cubic_interpolate_in_time(double p_from, double p_to, double p_pre, double p_post, double p_weight,
double p_to_t, double p_pre_t, double p_post_t) {
/* Barry-Goldman method */
Expand All @@ -276,6 +305,37 @@ class Math {
return Math::lerp(b1, b2, p_to_t == 0 ? 0.5f : t / p_to_t);
}

static _ALWAYS_INLINE_ double cubic_interpolate_angle_in_time(double p_from, double p_to, double p_pre, double p_post, double p_weight,
double p_to_t, double p_pre_t, double p_post_t) {
double from_rot = fmod(p_from, Math_TAU);

double pre_diff = fmod(p_pre - from_rot, Math_TAU);
double pre_rot = from_rot + fmod(2.0 * pre_diff, Math_TAU) - pre_diff;

double to_diff = fmod(p_to - from_rot, Math_TAU);
double to_rot = from_rot + fmod(2.0 * to_diff, Math_TAU) - to_diff;

double post_diff = fmod(p_post - to_rot, Math_TAU);
double post_rot = to_rot + fmod(2.0 * post_diff, Math_TAU) - post_diff;

return cubic_interpolate_in_time(from_rot, to_rot, pre_rot, post_rot, p_weight, p_to_t, p_pre_t, p_post_t);
}
static _ALWAYS_INLINE_ float cubic_interpolate_angle_in_time(float p_from, float p_to, float p_pre, float p_post, float p_weight,
float p_to_t, float p_pre_t, float p_post_t) {
float from_rot = fmod(p_from, (float)Math_TAU);

float pre_diff = fmod(p_pre - from_rot, (float)Math_TAU);
float pre_rot = from_rot + fmod(2.0f * pre_diff, (float)Math_TAU) - pre_diff;

float to_diff = fmod(p_to - from_rot, (float)Math_TAU);
float to_rot = from_rot + fmod(2.0f * to_diff, (float)Math_TAU) - to_diff;

float post_diff = fmod(p_post - to_rot, (float)Math_TAU);
float post_rot = to_rot + fmod(2.0f * post_diff, (float)Math_TAU) - post_diff;

return cubic_interpolate_in_time(from_rot, to_rot, pre_rot, post_rot, p_weight, p_to_t, p_pre_t, p_post_t);
}

static _ALWAYS_INLINE_ double bezier_interpolate(double p_start, double p_control_1, double p_control_2, double p_end, double p_t) {
/* Formula from Wikipedia article on Bezier curves. */
double omt = (1.0 - p_t);
Expand Down
11 changes: 11 additions & 0 deletions core/variant/variant_utility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -367,11 +367,20 @@ struct VariantUtilityFunctions {
return Math::cubic_interpolate(from, to, pre, post, weight);
}

static inline double cubic_interpolate_angle(double from, double to, double pre, double post, double weight) {
return Math::cubic_interpolate_angle(from, to, pre, post, weight);
}

static inline double cubic_interpolate_in_time(double from, double to, double pre, double post, double weight,
double to_t, double pre_t, double post_t) {
return Math::cubic_interpolate_in_time(from, to, pre, post, weight, to_t, pre_t, post_t);
}

static inline double cubic_interpolate_angle_in_time(double from, double to, double pre, double post, double weight,
double to_t, double pre_t, double post_t) {
return Math::cubic_interpolate_angle_in_time(from, to, pre, post, weight, to_t, pre_t, post_t);
}

static inline double bezier_interpolate(double p_start, double p_control_1, double p_control_2, double p_end, double p_t) {
return Math::bezier_interpolate(p_start, p_control_1, p_control_2, p_end, p_t);
}
Expand Down Expand Up @@ -1419,7 +1428,9 @@ void Variant::_register_variant_utility_functions() {
FUNCBINDVR3(lerp, sarray("from", "to", "weight"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(lerpf, sarray("from", "to", "weight"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(cubic_interpolate, sarray("from", "to", "pre", "post", "weight"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(cubic_interpolate_angle, sarray("from", "to", "pre", "post", "weight"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(cubic_interpolate_in_time, sarray("from", "to", "pre", "post", "weight", "to_t", "pre_t", "post_t"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(cubic_interpolate_angle_in_time, sarray("from", "to", "pre", "post", "weight", "to_t", "pre_t", "post_t"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(bezier_interpolate, sarray("start", "control_1", "control_2", "end", "t"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(lerp_angle, sarray("from", "to", "weight"), Variant::UTILITY_FUNC_TYPE_MATH);
FUNCBINDR(inverse_lerp, sarray("from", "to", "weight"), Variant::UTILITY_FUNC_TYPE_MATH);
Expand Down
26 changes: 26 additions & 0 deletions doc/classes/@GlobalScope.xml
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,32 @@
Cubic interpolates between two values by the factor defined in [param weight] with pre and post values.
</description>
</method>
<method name="cubic_interpolate_angle">
<return type="float" />
<param index="0" name="from" type="float" />
<param index="1" name="to" type="float" />
<param index="2" name="pre" type="float" />
<param index="3" name="post" type="float" />
<param index="4" name="weight" type="float" />
<description>
Cubic interpolates between two rotation values with shortest path by the factor defined in [param weight] with pre and post values. See also [method lerp_angle].
</description>
</method>
<method name="cubic_interpolate_angle_in_time">
<return type="float" />
<param index="0" name="from" type="float" />
<param index="1" name="to" type="float" />
<param index="2" name="pre" type="float" />
<param index="3" name="post" type="float" />
<param index="4" name="weight" type="float" />
<param index="5" name="to_t" type="float" />
<param index="6" name="pre_t" type="float" />
<param index="7" name="post_t" type="float" />
<description>
Cubic interpolates between two rotation values with shortest path by the factor defined in [param weight] with pre and post values. See also [method lerp_angle].
It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values.
</description>
</method>
<method name="cubic_interpolate_in_time">
<return type="float" />
<param index="0" name="from" type="float" />
Expand Down
8 changes: 8 additions & 0 deletions doc/classes/Animation.xml
Original file line number Diff line number Diff line change
Expand Up @@ -600,6 +600,14 @@
<constant name="INTERPOLATION_CUBIC" value="2" enum="InterpolationType">
Cubic interpolation.
</constant>
<constant name="INTERPOLATION_LINEAR_ANGLE" value="3" enum="InterpolationType">
Linear interpolation with shortest path rotation.
[b]Note:[/b] The result value is always normalized and may not match the key value.
</constant>
<constant name="INTERPOLATION_CUBIC_ANGLE" value="4" enum="InterpolationType">
Cubic interpolation with shortest path rotation.
[b]Note:[/b] The result value is always normalized and may not match the key value.
</constant>
<constant name="UPDATE_CONTINUOUS" value="0" enum="UpdateMode">
Update between keyframes.
</constant>
Expand Down
44 changes: 39 additions & 5 deletions editor/animation_track_editor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2122,10 +2122,12 @@ void AnimationTrackEdit::_notification(int p_what) {
get_theme_icon(SNAME("InterpWrapClamp"), SNAME("EditorIcons")),
get_theme_icon(SNAME("InterpWrapLoop"), SNAME("EditorIcons")),
};
Ref<Texture2D> interp_icon[3] = {
Ref<Texture2D> interp_icon[5] = {
get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")),
get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")),
get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")),
get_theme_icon(SNAME("InterpLinearAngle"), SNAME("EditorIcons")),
get_theme_icon(SNAME("InterpCubicAngle"), SNAME("EditorIcons")),
};
Ref<Texture2D> cont_icon[4] = {
get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")),
Expand Down Expand Up @@ -2848,6 +2850,23 @@ void AnimationTrackEdit::gui_input(const Ref<InputEvent> &p_event) {
menu->add_icon_item(get_theme_icon(SNAME("InterpRaw"), SNAME("EditorIcons")), TTR("Nearest"), MENU_INTERPOLATION_NEAREST);
menu->add_icon_item(get_theme_icon(SNAME("InterpLinear"), SNAME("EditorIcons")), TTR("Linear"), MENU_INTERPOLATION_LINEAR);
menu->add_icon_item(get_theme_icon(SNAME("InterpCubic"), SNAME("EditorIcons")), TTR("Cubic"), MENU_INTERPOLATION_CUBIC);
// Check is angle property.
AnimationPlayerEditor *ape = AnimationPlayerEditor::get_singleton();
if (ape) {
AnimationPlayer *ap = ape->get_player();
if (ap) {
NodePath path = animation->track_get_path(track);
Node *nd = ap->get_node(ap->get_root())->get_node(NodePath(path.get_concatenated_names()));
StringName prop = path.get_concatenated_subnames();
PropertyInfo prop_info;
ClassDB::get_property_info(nd->get_class(), prop, &prop_info);
bool is_angle = prop_info.type == Variant::FLOAT && prop_info.hint_string.find("radians") != -1;
if (is_angle) {
menu->add_icon_item(get_theme_icon(SNAME("InterpLinearAngle"), SNAME("EditorIcons")), TTR("Linear Angle"), MENU_INTERPOLATION_LINEAR_ANGLE);
menu->add_icon_item(get_theme_icon(SNAME("InterpCubicAngle"), SNAME("EditorIcons")), TTR("Cubic Angle"), MENU_INTERPOLATION_CUBIC_ANGLE);
}
}
}
menu->reset_size();

Vector2 popup_pos = get_screen_position() + interp_mode_rect.position + Vector2(0, interp_mode_rect.size.height);
Expand Down Expand Up @@ -3188,7 +3207,9 @@ void AnimationTrackEdit::_menu_selected(int p_index) {
} break;
case MENU_INTERPOLATION_NEAREST:
case MENU_INTERPOLATION_LINEAR:
case MENU_INTERPOLATION_CUBIC: {
case MENU_INTERPOLATION_CUBIC:
case MENU_INTERPOLATION_LINEAR_ANGLE:
case MENU_INTERPOLATION_CUBIC_ANGLE: {
Animation::InterpolationType interp_mode = Animation::InterpolationType(p_index - MENU_INTERPOLATION_NEAREST);
undo_redo->create_action(TTR("Change Animation Interpolation Mode"));
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", track, interp_mode);
Expand Down Expand Up @@ -6042,6 +6063,9 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
Vector<int> keys = E->value;
int len = keys.size() - 1;

// Special case for angle interpolation.
bool is_using_angle = animation->track_get_interpolation_type(track) == Animation::INTERPOLATION_LINEAR_ANGLE || animation->track_get_interpolation_type(track) == Animation::INTERPOLATION_CUBIC_ANGLE;

// Make insert queue.
Vector<Pair<double, Variant>> insert_queue;
for (int i = 0; i < len; i++) {
Expand All @@ -6051,6 +6075,12 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
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]);
if (is_using_angle) {
real_t a = from_v;
real_t b = to_v;
real_t to_diff = fmod(b - a, Math_TAU);
to_v = a + fmod(2.0 * to_diff, Math_TAU) - to_diff;
}
Variant delta_v;
Variant::sub(to_v, from_v, delta_v);
double duration = to_t - from_t;
Expand Down Expand Up @@ -6192,10 +6222,14 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
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.
Animation::InterpolationType it = animation->track_get_interpolation_type(i);
if (it == Animation::INTERPOLATION_NEAREST) {
continue; // Nearest and Angle interpolation cannot be baked.
}

// Special case for angle interpolation.
bool is_using_angle = it == Animation::INTERPOLATION_LINEAR_ANGLE || it == Animation::INTERPOLATION_CUBIC_ANGLE;

// Make insert queue.
Vector<Pair<double, Variant>> insert_queue;

Expand Down Expand Up @@ -6259,7 +6293,7 @@ void AnimationTrackEditor::_edit_menu_pressed(int p_option) {
}

// Insert keys.
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", i, Animation::INTERPOLATION_LINEAR);
undo_redo->add_do_method(animation.ptr(), "track_set_interpolation_type", i, is_using_angle ? Animation::INTERPOLATION_LINEAR_ANGLE : 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);
Expand Down
4 changes: 3 additions & 1 deletion editor/animation_track_editor.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,8 @@ class AnimationTrackEdit : public Control {
MENU_INTERPOLATION_NEAREST,
MENU_INTERPOLATION_LINEAR,
MENU_INTERPOLATION_CUBIC,
MENU_INTERPOLATION_LINEAR_ANGLE,
MENU_INTERPOLATION_CUBIC_ANGLE,
MENU_LOOP_WRAP,
MENU_LOOP_CLAMP,
MENU_KEY_INSERT,
Expand Down Expand Up @@ -500,7 +502,7 @@ class AnimationTrackEditor : public VBoxContainer {
NodePath full_path;
NodePath base_path;
Animation::TrackType track_type = Animation::TYPE_ANIMATION;
Animation::InterpolationType interp_type = Animation::INTERPOLATION_CUBIC;
Animation::InterpolationType interp_type = Animation::INTERPOLATION_CUBIC_ANGLE;
Animation::UpdateMode update_mode = Animation::UPDATE_CAPTURE;
Animation::LoopMode loop_mode = Animation::LOOP_PINGPONG;
bool loop_wrap = false;
Expand Down
1 change: 1 addition & 0 deletions editor/icons/InterpCubicAngle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions editor/icons/InterpLinearAngle.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
30 changes: 28 additions & 2 deletions scene/animation/animation_tree.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,8 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
track_value->object = child;
}

track_value->is_using_angle = anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE;

track_value->subpath = leftover_path;
track_value->object_id = track_value->object->get_instance_id();

Expand Down Expand Up @@ -804,6 +806,10 @@ bool AnimationTree::_update_caches(AnimationPlayer *player) {
default: {
}
}
} else if (track_cache_type == Animation::TYPE_VALUE) {
// If it has at least one angle interpolation, it also uses angle interpolation for blending.
TrackCacheValue *track_value = memnew(TrackCacheValue);
track_value->is_using_angle |= anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_LINEAR_ANGLE || anim->track_get_interpolation_type(i) == Animation::INTERPOLATION_CUBIC_ANGLE;
}

track->setup_pass = setup_pass;
Expand Down Expand Up @@ -1353,8 +1359,28 @@ void AnimationTree::_process_graph(double p_delta) {
t->value = t->init_value;
}

Variant::sub(value, t->init_value, value);
Variant::blend(t->value, value, blend, t->value);
// Special case for angle interpolation.
if (t->is_using_angle) {
// For blending consistency, it prevents rotation of more than 180 degrees from init_value.
// This is the same as for Quaternion blends.
float rot_a = t->value;
float rot_b = value;
float rot_init = t->init_value;
rot_a = Math::fposmod(rot_a, (float)Math_TAU);
rot_b = Math::fposmod(rot_b, (float)Math_TAU);
rot_init = Math::fposmod(rot_init, (float)Math_TAU);
if (rot_init < Math_PI) {
rot_a = rot_a > rot_init + Math_PI ? rot_a - Math_TAU : rot_a;
rot_b = rot_b > rot_init + Math_PI ? rot_b - Math_TAU : rot_b;
} else {
rot_a = rot_a < rot_init - Math_PI ? rot_a + Math_TAU : rot_a;
rot_b = rot_b < rot_init - Math_PI ? rot_b + Math_TAU : rot_b;
}
t->value = Math::fposmod(rot_a + (rot_b - rot_init) * (float)blend, (float)Math_TAU);
} else {
Variant::sub(value, t->init_value, value);
Variant::blend(t->value, value, blend, t->value);
}
} else {
if (blend < CMP_EPSILON) {
continue; //nothing to blend
Expand Down
1 change: 1 addition & 0 deletions scene/animation/animation_tree.h
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,7 @@ class AnimationTree : public Node {
Variant init_value;
Variant value;
Vector<StringName> subpath;
bool is_using_angle = false;
TrackCacheValue() { type = Animation::TYPE_VALUE; }
};

Expand Down
Loading

0 comments on commit 931fb4d

Please sign in to comment.