diff options
-rw-r--r-- | core/math/math_funcs.h | 23 | ||||
-rw-r--r-- | core/math/quaternion.cpp | 51 | ||||
-rw-r--r-- | core/math/quaternion.h | 1 | ||||
-rw-r--r-- | core/math/vector2.h | 8 | ||||
-rw-r--r-- | core/math/vector3.h | 9 | ||||
-rw-r--r-- | core/math/vector4.cpp | 9 | ||||
-rw-r--r-- | core/math/vector4.h | 1 | ||||
-rw-r--r-- | core/variant/variant_call.cpp | 4 | ||||
-rw-r--r-- | core/variant/variant_utility.cpp | 6 | ||||
-rw-r--r-- | doc/classes/@GlobalScope.xml | 15 | ||||
-rw-r--r-- | doc/classes/Animation.xml | 3 | ||||
-rw-r--r-- | doc/classes/Quaternion.xml | 14 | ||||
-rw-r--r-- | doc/classes/Vector2.xml | 14 | ||||
-rw-r--r-- | doc/classes/Vector3.xml | 14 | ||||
-rw-r--r-- | doc/classes/Vector4.xml | 14 | ||||
-rw-r--r-- | editor/animation_track_editor.cpp | 10 | ||||
-rw-r--r-- | editor/animation_track_editor.h | 5 | ||||
-rw-r--r-- | editor/icons/InterpCubicInTime.svg | 1 | ||||
-rw-r--r-- | scene/resources/animation.cpp | 165 | ||||
-rw-r--r-- | scene/resources/animation.h | 8 |
20 files changed, 352 insertions, 23 deletions
diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h index 53deb9bd42..463e119add 100644 --- a/core/math/math_funcs.h +++ b/core/math/math_funcs.h @@ -253,6 +253,29 @@ public: (-p_pre + 3.0f * p_from - 3.0f * p_to + p_post) * (p_weight * p_weight * 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 */ + double t = Math::lerp(0.0, p_to_t, p_weight); + double a1 = Math::lerp(p_pre, p_from, p_pre_t == 0 ? 0.0 : (t - p_pre_t) / -p_pre_t); + double a2 = Math::lerp(p_from, p_to, p_to_t == 0 ? 0.5 : t / p_to_t); + double a3 = Math::lerp(p_to, p_post, p_post_t - p_to_t == 0 ? 1.0 : (t - p_to_t) / (p_post_t - p_to_t)); + double b1 = Math::lerp(a1, a2, p_to_t - p_pre_t == 0 ? 0.0 : (t - p_pre_t) / (p_to_t - p_pre_t)); + double b2 = Math::lerp(a2, a3, p_post_t == 0 ? 1.0 : t / p_post_t); + return Math::lerp(b1, b2, p_to_t == 0 ? 0.5 : t / p_to_t); + } + static _ALWAYS_INLINE_ float cubic_interpolate_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) { + /* Barry-Goldman method */ + float t = Math::lerp(0.0f, p_to_t, p_weight); + float a1 = Math::lerp(p_pre, p_from, p_pre_t == 0 ? 0.0f : (t - p_pre_t) / -p_pre_t); + float a2 = Math::lerp(p_from, p_to, p_to_t == 0 ? 0.5f : t / p_to_t); + float a3 = Math::lerp(p_to, p_post, p_post_t - p_to_t == 0 ? 1.0f : (t - p_to_t) / (p_post_t - p_to_t)); + float b1 = Math::lerp(a1, a2, p_to_t - p_pre_t == 0 ? 0.0f : (t - p_pre_t) / (p_to_t - p_pre_t)); + float b2 = Math::lerp(a2, a3, p_post_t == 0 ? 1.0f : t / p_post_t); + return Math::lerp(b1, b2, p_to_t == 0 ? 0.5f : t / p_to_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); diff --git a/core/math/quaternion.cpp b/core/math/quaternion.cpp index c681c60694..36358f6feb 100644 --- a/core/math/quaternion.cpp +++ b/core/math/quaternion.cpp @@ -233,6 +233,57 @@ Quaternion Quaternion::spherical_cubic_interpolate(const Quaternion &p_b, const return q1.slerp(q2, p_weight); } +Quaternion Quaternion::spherical_cubic_interpolate_in_time(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, const real_t &p_weight, + const real_t &p_b_t, const real_t &p_pre_a_t, const real_t &p_post_b_t) const { +#ifdef MATH_CHECKS + ERR_FAIL_COND_V_MSG(!is_normalized(), Quaternion(), "The start quaternion must be normalized."); + ERR_FAIL_COND_V_MSG(!p_b.is_normalized(), Quaternion(), "The end quaternion must be normalized."); +#endif + Quaternion from_q = *this; + Quaternion pre_q = p_pre_a; + Quaternion to_q = p_b; + Quaternion post_q = p_post_b; + + // Align flip phases. + from_q = Basis(from_q).get_rotation_quaternion(); + pre_q = Basis(pre_q).get_rotation_quaternion(); + to_q = Basis(to_q).get_rotation_quaternion(); + post_q = Basis(post_q).get_rotation_quaternion(); + + // Flip quaternions to shortest path if necessary. + bool flip1 = signbit(from_q.dot(pre_q)); + pre_q = flip1 ? -pre_q : pre_q; + bool flip2 = signbit(from_q.dot(to_q)); + to_q = flip2 ? -to_q : to_q; + bool flip3 = flip2 ? to_q.dot(post_q) <= 0 : signbit(to_q.dot(post_q)); + post_q = flip3 ? -post_q : post_q; + + // Calc by Expmap in from_q space. + Quaternion ln_from = Quaternion(0, 0, 0, 0); + Quaternion ln_to = (from_q.inverse() * to_q).log(); + Quaternion ln_pre = (from_q.inverse() * pre_q).log(); + Quaternion ln_post = (from_q.inverse() * post_q).log(); + Quaternion ln = Quaternion(0, 0, 0, 0); + ln.x = Math::cubic_interpolate_in_time(ln_from.x, ln_to.x, ln_pre.x, ln_post.x, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + ln.y = Math::cubic_interpolate_in_time(ln_from.y, ln_to.y, ln_pre.y, ln_post.y, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + ln.z = Math::cubic_interpolate_in_time(ln_from.z, ln_to.z, ln_pre.z, ln_post.z, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + Quaternion q1 = from_q * ln.exp(); + + // Calc by Expmap in to_q space. + ln_from = (to_q.inverse() * from_q).log(); + ln_to = Quaternion(0, 0, 0, 0); + ln_pre = (to_q.inverse() * pre_q).log(); + ln_post = (to_q.inverse() * post_q).log(); + ln = Quaternion(0, 0, 0, 0); + ln.x = Math::cubic_interpolate_in_time(ln_from.x, ln_to.x, ln_pre.x, ln_post.x, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + ln.y = Math::cubic_interpolate_in_time(ln_from.y, ln_to.y, ln_pre.y, ln_post.y, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + ln.z = Math::cubic_interpolate_in_time(ln_from.z, ln_to.z, ln_pre.z, ln_post.z, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + Quaternion q2 = to_q * ln.exp(); + + // To cancel error made by Expmap ambiguity, do blends. + return q1.slerp(q2, p_weight); +} + Quaternion::operator String() const { return "(" + String::num_real(x, false) + ", " + String::num_real(y, false) + ", " + String::num_real(z, false) + ", " + String::num_real(w, false) + ")"; } diff --git a/core/math/quaternion.h b/core/math/quaternion.h index cb54a6f540..43d7bffcfc 100644 --- a/core/math/quaternion.h +++ b/core/math/quaternion.h @@ -72,6 +72,7 @@ struct _NO_DISCARD_ Quaternion { Quaternion slerp(const Quaternion &p_to, const real_t &p_weight) const; Quaternion slerpni(const Quaternion &p_to, const real_t &p_weight) const; Quaternion spherical_cubic_interpolate(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, const real_t &p_weight) const; + Quaternion spherical_cubic_interpolate_in_time(const Quaternion &p_b, const Quaternion &p_pre_a, const Quaternion &p_post_b, const real_t &p_weight, const real_t &p_b_t, const real_t &p_pre_a_t, const real_t &p_post_b_t) const; Vector3 get_axis() const; real_t get_angle() const; diff --git a/core/math/vector2.h b/core/math/vector2.h index 91d3d3a56b..caa6b226e7 100644 --- a/core/math/vector2.h +++ b/core/math/vector2.h @@ -114,6 +114,7 @@ struct _NO_DISCARD_ Vector2 { _FORCE_INLINE_ Vector2 lerp(const Vector2 &p_to, const real_t p_weight) const; _FORCE_INLINE_ Vector2 slerp(const Vector2 &p_to, const real_t p_weight) const; _FORCE_INLINE_ Vector2 cubic_interpolate(const Vector2 &p_b, const Vector2 &p_pre_a, const Vector2 &p_post_b, const real_t p_weight) const; + _FORCE_INLINE_ Vector2 cubic_interpolate_in_time(const Vector2 &p_b, const Vector2 &p_pre_a, const Vector2 &p_post_b, const real_t p_weight, const real_t &p_b_t, const real_t &p_pre_a_t, const real_t &p_post_b_t) const; _FORCE_INLINE_ Vector2 bezier_interpolate(const Vector2 &p_control_1, const Vector2 &p_control_2, const Vector2 &p_end, const real_t p_t) const; Vector2 move_toward(const Vector2 &p_to, const real_t p_delta) const; @@ -270,6 +271,13 @@ Vector2 Vector2::cubic_interpolate(const Vector2 &p_b, const Vector2 &p_pre_a, c return res; } +Vector2 Vector2::cubic_interpolate_in_time(const Vector2 &p_b, const Vector2 &p_pre_a, const Vector2 &p_post_b, const real_t p_weight, const real_t &p_b_t, const real_t &p_pre_a_t, const real_t &p_post_b_t) const { + Vector2 res = *this; + res.x = Math::cubic_interpolate_in_time(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + res.y = Math::cubic_interpolate_in_time(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + return res; +} + Vector2 Vector2::bezier_interpolate(const Vector2 &p_control_1, const Vector2 &p_control_2, const Vector2 &p_end, const real_t p_t) const { Vector2 res = *this; diff --git a/core/math/vector3.h b/core/math/vector3.h index 4ce01da60e..eb654008b2 100644 --- a/core/math/vector3.h +++ b/core/math/vector3.h @@ -105,6 +105,7 @@ struct _NO_DISCARD_ Vector3 { _FORCE_INLINE_ Vector3 lerp(const Vector3 &p_to, const real_t p_weight) const; _FORCE_INLINE_ Vector3 slerp(const Vector3 &p_to, const real_t p_weight) const; _FORCE_INLINE_ Vector3 cubic_interpolate(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, const real_t p_weight) const; + _FORCE_INLINE_ Vector3 cubic_interpolate_in_time(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, const real_t p_weight, const real_t &p_b_t, const real_t &p_pre_a_t, const real_t &p_post_b_t) const; _FORCE_INLINE_ Vector3 bezier_interpolate(const Vector3 &p_control_1, const Vector3 &p_control_2, const Vector3 &p_end, const real_t p_t) const; Vector3 move_toward(const Vector3 &p_to, const real_t p_delta) const; @@ -246,6 +247,14 @@ Vector3 Vector3::cubic_interpolate(const Vector3 &p_b, const Vector3 &p_pre_a, c return res; } +Vector3 Vector3::cubic_interpolate_in_time(const Vector3 &p_b, const Vector3 &p_pre_a, const Vector3 &p_post_b, const real_t p_weight, const real_t &p_b_t, const real_t &p_pre_a_t, const real_t &p_post_b_t) const { + Vector3 res = *this; + res.x = Math::cubic_interpolate_in_time(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + res.y = Math::cubic_interpolate_in_time(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + res.z = Math::cubic_interpolate_in_time(res.z, p_b.z, p_pre_a.z, p_post_b.z, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + return res; +} + Vector3 Vector3::bezier_interpolate(const Vector3 &p_control_1, const Vector3 &p_control_2, const Vector3 &p_end, const real_t p_t) const { Vector3 res = *this; diff --git a/core/math/vector4.cpp b/core/math/vector4.cpp index 1dd5adad2b..273a111891 100644 --- a/core/math/vector4.cpp +++ b/core/math/vector4.cpp @@ -138,6 +138,15 @@ Vector4 Vector4::cubic_interpolate(const Vector4 &p_b, const Vector4 &p_pre_a, c return res; } +Vector4 Vector4::cubic_interpolate_in_time(const Vector4 &p_b, const Vector4 &p_pre_a, const Vector4 &p_post_b, const real_t p_weight, const real_t &p_b_t, const real_t &p_pre_a_t, const real_t &p_post_b_t) const { + Vector4 res = *this; + res.x = Math::cubic_interpolate_in_time(res.x, p_b.x, p_pre_a.x, p_post_b.x, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + res.y = Math::cubic_interpolate_in_time(res.y, p_b.y, p_pre_a.y, p_post_b.y, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + res.z = Math::cubic_interpolate_in_time(res.z, p_b.z, p_pre_a.z, p_post_b.z, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + res.w = Math::cubic_interpolate_in_time(res.w, p_b.w, p_pre_a.w, p_post_b.w, p_weight, p_b_t, p_pre_a_t, p_post_b_t); + return res; +} + Vector4 Vector4::posmod(const real_t p_mod) const { return Vector4(Math::fposmod(x, p_mod), Math::fposmod(y, p_mod), Math::fposmod(z, p_mod), Math::fposmod(w, p_mod)); } diff --git a/core/math/vector4.h b/core/math/vector4.h index d26fe15941..17d0de18e1 100644 --- a/core/math/vector4.h +++ b/core/math/vector4.h @@ -89,6 +89,7 @@ struct _NO_DISCARD_ Vector4 { Vector4 round() const; Vector4 lerp(const Vector4 &p_to, const real_t p_weight) const; Vector4 cubic_interpolate(const Vector4 &p_b, const Vector4 &p_pre_a, const Vector4 &p_post_b, const real_t p_weight) const; + Vector4 cubic_interpolate_in_time(const Vector4 &p_b, const Vector4 &p_pre_a, const Vector4 &p_post_b, const real_t p_weight, const real_t &p_b_t, const real_t &p_pre_a_t, const real_t &p_post_b_t) const; Vector4 posmod(const real_t p_mod) const; Vector4 posmodv(const Vector4 &p_modv) const; diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index eba12b68bb..995dc6df99 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1608,6 +1608,7 @@ static void _register_variant_builtin_methods() { bind_method(Vector2, lerp, sarray("to", "weight"), varray()); bind_method(Vector2, slerp, sarray("to", "weight"), varray()); bind_method(Vector2, cubic_interpolate, sarray("b", "pre_a", "post_b", "weight"), varray()); + bind_method(Vector2, cubic_interpolate_in_time, sarray("b", "pre_a", "post_b", "weight", "b_t", "pre_a_t", "post_b_t"), varray()); bind_method(Vector2, bezier_interpolate, sarray("control_1", "control_2", "end", "t"), varray()); bind_method(Vector2, max_axis_index, sarray(), varray()); bind_method(Vector2, min_axis_index, sarray(), varray()); @@ -1696,6 +1697,7 @@ static void _register_variant_builtin_methods() { bind_method(Vector3, lerp, sarray("to", "weight"), varray()); bind_method(Vector3, slerp, sarray("to", "weight"), varray()); bind_method(Vector3, cubic_interpolate, sarray("b", "pre_a", "post_b", "weight"), varray()); + bind_method(Vector3, cubic_interpolate_in_time, sarray("b", "pre_a", "post_b", "weight", "b_t", "pre_a_t", "post_b_t"), varray()); bind_method(Vector3, bezier_interpolate, sarray("control_1", "control_2", "end", "t"), varray()); bind_method(Vector3, move_toward, sarray("to", "delta"), varray()); bind_method(Vector3, dot, sarray("with"), varray()); @@ -1738,6 +1740,7 @@ static void _register_variant_builtin_methods() { bind_method(Vector4, round, sarray(), varray()); bind_method(Vector4, lerp, sarray("to", "weight"), varray()); bind_method(Vector4, cubic_interpolate, sarray("b", "pre_a", "post_b", "weight"), varray()); + bind_method(Vector4, cubic_interpolate_in_time, sarray("b", "pre_a", "post_b", "weight", "b_t", "pre_a_t", "post_b_t"), varray()); bind_method(Vector4, posmod, sarray("mod"), varray()); bind_method(Vector4, posmodv, sarray("modv"), varray()); bind_method(Vector4, snapped, sarray("step"), varray()); @@ -1789,6 +1792,7 @@ static void _register_variant_builtin_methods() { bind_method(Quaternion, slerp, sarray("to", "weight"), varray()); bind_method(Quaternion, slerpni, sarray("to", "weight"), varray()); bind_method(Quaternion, spherical_cubic_interpolate, sarray("b", "pre_a", "post_b", "weight"), varray()); + bind_method(Quaternion, spherical_cubic_interpolate_in_time, sarray("b", "pre_a", "post_b", "weight", "b_t", "pre_a_t", "post_b_t"), varray()); bind_method(Quaternion, get_euler, sarray(), varray()); bind_method(Quaternion, get_axis, sarray(), varray()); bind_method(Quaternion, get_angle, sarray(), varray()); diff --git a/core/variant/variant_utility.cpp b/core/variant/variant_utility.cpp index 1f1439ab24..21c9c483a5 100644 --- a/core/variant/variant_utility.cpp +++ b/core/variant/variant_utility.cpp @@ -367,6 +367,11 @@ struct VariantUtilityFunctions { return Math::cubic_interpolate(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 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); } @@ -1414,6 +1419,7 @@ 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_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); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index e6901bbe27..83dc8c1413 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -260,6 +260,21 @@ Cubic interpolates between two values by the factor defined in [param weight] with pre and post values. </description> </method> + <method name="cubic_interpolate_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 values by the factor defined in [param weight] with pre and post values. + It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values. + </description> + </method> <method name="db2linear"> <return type="float" /> <param index="0" name="db" type="float" /> diff --git a/doc/classes/Animation.xml b/doc/classes/Animation.xml index 859b4a8a5f..fef65181ae 100644 --- a/doc/classes/Animation.xml +++ b/doc/classes/Animation.xml @@ -619,6 +619,9 @@ <constant name="INTERPOLATION_CUBIC" value="2" enum="InterpolationType"> Cubic interpolation. </constant> + <constant name="INTERPOLATION_CUBIC_IN_TIME" value="3" enum="InterpolationType"> + Cubic interpolation with uniformed time. + </constant> <constant name="UPDATE_CONTINUOUS" value="0" enum="UpdateMode"> Update between keyframes. </constant> diff --git a/doc/classes/Quaternion.xml b/doc/classes/Quaternion.xml index bc0ffbefe2..a521af5709 100644 --- a/doc/classes/Quaternion.xml +++ b/doc/classes/Quaternion.xml @@ -171,6 +171,20 @@ Performs a spherical cubic interpolation between quaternions [param pre_a], this vector, [param b], and [param post_b], by the given amount [param weight]. </description> </method> + <method name="spherical_cubic_interpolate_in_time" qualifiers="const"> + <return type="Quaternion" /> + <param index="0" name="b" type="Quaternion" /> + <param index="1" name="pre_a" type="Quaternion" /> + <param index="2" name="post_b" type="Quaternion" /> + <param index="3" name="weight" type="float" /> + <param index="4" name="b_t" type="float" /> + <param index="5" name="pre_a_t" type="float" /> + <param index="6" name="post_b_t" type="float" /> + <description> + Performs a spherical cubic interpolation between quaternions [param pre_a], this vector, [param b], and [param post_b], by the given amount [param weight]. + It can perform smoother interpolation than [code]spherical_cubic_interpolate()[/code] by the time values. + </description> + </method> </methods> <members> <member name="w" type="float" setter="" getter="" default="1.0"> diff --git a/doc/classes/Vector2.xml b/doc/classes/Vector2.xml index 904fc6d9e9..19fe2e2bfc 100644 --- a/doc/classes/Vector2.xml +++ b/doc/classes/Vector2.xml @@ -135,6 +135,20 @@ Cubically interpolates between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight]. [param weight] is on the range of 0.0 to 1.0, representing the amount of interpolation. </description> </method> + <method name="cubic_interpolate_in_time" qualifiers="const"> + <return type="Vector2" /> + <param index="0" name="b" type="Vector2" /> + <param index="1" name="pre_a" type="Vector2" /> + <param index="2" name="post_b" type="Vector2" /> + <param index="3" name="weight" type="float" /> + <param index="4" name="b_t" type="float" /> + <param index="5" name="pre_a_t" type="float" /> + <param index="6" name="post_b_t" type="float" /> + <description> + Cubically interpolates between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight]. [param weight] is on the range of 0.0 to 1.0, representing the amount of interpolation. + It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values. + </description> + </method> <method name="direction_to" qualifiers="const"> <return type="Vector2" /> <param index="0" name="to" type="Vector2" /> diff --git a/doc/classes/Vector3.xml b/doc/classes/Vector3.xml index 208e9935e3..150d53845c 100644 --- a/doc/classes/Vector3.xml +++ b/doc/classes/Vector3.xml @@ -109,6 +109,20 @@ Performs a cubic interpolation between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight]. [param weight] is on the range of 0.0 to 1.0, representing the amount of interpolation. </description> </method> + <method name="cubic_interpolate_in_time" qualifiers="const"> + <return type="Vector3" /> + <param index="0" name="b" type="Vector3" /> + <param index="1" name="pre_a" type="Vector3" /> + <param index="2" name="post_b" type="Vector3" /> + <param index="3" name="weight" type="float" /> + <param index="4" name="b_t" type="float" /> + <param index="5" name="pre_a_t" type="float" /> + <param index="6" name="post_b_t" type="float" /> + <description> + Performs a cubic interpolation between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight]. [param weight] is on the range of 0.0 to 1.0, representing the amount of interpolation. + It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values. + </description> + </method> <method name="direction_to" qualifiers="const"> <return type="Vector3" /> <param index="0" name="to" type="Vector3" /> diff --git a/doc/classes/Vector4.xml b/doc/classes/Vector4.xml index 538cdd4138..b9f509cfe7 100644 --- a/doc/classes/Vector4.xml +++ b/doc/classes/Vector4.xml @@ -73,6 +73,20 @@ Performs a cubic interpolation between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight]. [param weight] is on the range of 0.0 to 1.0, representing the amount of interpolation. </description> </method> + <method name="cubic_interpolate_in_time" qualifiers="const"> + <return type="Vector4" /> + <param index="0" name="b" type="Vector4" /> + <param index="1" name="pre_a" type="Vector4" /> + <param index="2" name="post_b" type="Vector4" /> + <param index="3" name="weight" type="float" /> + <param index="4" name="b_t" type="float" /> + <param index="5" name="pre_a_t" type="float" /> + <param index="6" name="post_b_t" type="float" /> + <description> + Performs a cubic interpolation between this vector and [param b] using [param pre_a] and [param post_b] as handles, and returns the result at position [param weight]. [param weight] is on the range of 0.0 to 1.0, representing the amount of interpolation. + It can perform smoother interpolation than [code]cubic_interpolate()[/code] by the time values. + </description> + </method> <method name="direction_to" qualifiers="const"> <return type="Vector4" /> <param index="0" name="to" type="Vector4" /> diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 0db82551cb..5580d3d5b1 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -2114,11 +2114,11 @@ 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[4] = { 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("InterpCubic"), SNAME("EditorIcons")), + get_theme_icon(SNAME("InterpCubicInTime"), SNAME("EditorIcons")) }; Ref<Texture2D> cont_icon[4] = { get_theme_icon(SNAME("TrackContinuous"), SNAME("EditorIcons")), @@ -2831,6 +2831,7 @@ 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); + menu->add_icon_item(get_theme_icon(SNAME("InterpCubicInTime"), SNAME("EditorIcons")), TTR("CubicInTime"), MENU_INTERPOLATION_CUBIC_IN_TIME); menu->reset_size(); Vector2 popup_pos = get_screen_position() + interp_mode_rect.position + Vector2(0, interp_mode_rect.size.height); @@ -3171,7 +3172,8 @@ 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_CUBIC_IN_TIME: { 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); diff --git a/editor/animation_track_editor.h b/editor/animation_track_editor.h index b0553c54a5..a785f5aec7 100644 --- a/editor/animation_track_editor.h +++ b/editor/animation_track_editor.h @@ -143,6 +143,7 @@ class AnimationTrackEdit : public Control { MENU_INTERPOLATION_NEAREST, MENU_INTERPOLATION_LINEAR, MENU_INTERPOLATION_CUBIC, + MENU_INTERPOLATION_CUBIC_IN_TIME, MENU_LOOP_WRAP, MENU_LOOP_CLAMP, MENU_KEY_INSERT, @@ -486,9 +487,9 @@ 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_IN_TIME; Animation::UpdateMode update_mode = Animation::UPDATE_CAPTURE; - Animation::LoopMode loop_mode = Animation::LOOP_LINEAR; + Animation::LoopMode loop_mode = Animation::LOOP_PINGPONG; bool loop_wrap = false; bool enabled = false; diff --git a/editor/icons/InterpCubicInTime.svg b/editor/icons/InterpCubicInTime.svg new file mode 100644 index 0000000000..81027f798a --- /dev/null +++ b/editor/icons/InterpCubicInTime.svg @@ -0,0 +1 @@ +<svg enable-background="new -595.5 420.5 16 8" height="8" viewBox="-595.5 420.5 16 8" width="16" xmlns="http://www.w3.org/2000/svg"><path d="m-593.5 426.5c1-4 3.5-5.5 6-2s5 2 6-2" fill="none" stroke="#ff92cb" stroke-linecap="round" stroke-width="2"/></svg> diff --git a/scene/resources/animation.cpp b/scene/resources/animation.cpp index 69b30b72b0..289d026ddb 100644 --- a/scene/resources/animation.cpp +++ b/scene/resources/animation.cpp @@ -967,7 +967,6 @@ int Animation::find_track(const NodePath &p_path, const TrackType p_type) const void Animation::track_set_interpolation_type(int p_track, InterpolationType p_interp) { ERR_FAIL_INDEX(p_track, tracks.size()); - ERR_FAIL_INDEX(p_interp, 3); tracks[p_track]->interpolation = p_interp; emit_changed(); } @@ -2283,6 +2282,8 @@ int Animation::_find(const Vector<K> &p_keys, double p_time, bool p_backward) co return middle; } +// Linear interpolation for anytype. + Vector3 Animation::_interpolate(const Vector3 &p_a, const Vector3 &p_b, real_t p_c) const { return p_a.lerp(p_b, p_c); } @@ -2301,6 +2302,8 @@ real_t Animation::_interpolate(const real_t &p_a, const real_t &p_b, real_t p_c) return p_a * (1.0 - p_c) + p_b * p_c; } +// Cubic interpolation for anytype. + Vector3 Animation::_cubic_interpolate(const Vector3 &p_pre_a, const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_post_b, real_t p_c) const { return p_a.cubic_interpolate(p_b, p_pre_a, p_post_b, p_c); } @@ -2389,6 +2392,96 @@ real_t Animation::_cubic_interpolate(const real_t &p_pre_a, const real_t &p_a, c return _interpolate(p_a, p_b, p_c); } +// Cubic interpolation in time for anytype. + +Vector3 Animation::_cubic_interpolate_in_time(const Vector3 &p_pre_a, const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const { + return p_a.cubic_interpolate_in_time(p_b, p_pre_a, p_post_b, p_c, p_b_t, p_pre_a_t, p_post_b_t); +} + +Quaternion Animation::_cubic_interpolate_in_time(const Quaternion &p_pre_a, const Quaternion &p_a, const Quaternion &p_b, const Quaternion &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const { + return p_a.spherical_cubic_interpolate_in_time(p_b, p_pre_a, p_post_b, p_c, p_b_t, p_pre_a_t, p_post_b_t); +} + +Variant Animation::_cubic_interpolate_in_time(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const { + Variant::Type type_a = p_a.get_type(); + Variant::Type type_b = p_b.get_type(); + Variant::Type type_pa = p_pre_a.get_type(); + Variant::Type type_pb = p_post_b.get_type(); + + //make int and real play along + + uint32_t vformat = 1 << type_a; + vformat |= 1 << type_b; + vformat |= 1 << type_pa; + vformat |= 1 << type_pb; + + if (vformat == ((1 << Variant::INT) | (1 << Variant::FLOAT)) || vformat == (1 << Variant::FLOAT)) { + //mix of real and int + real_t a = p_a; + real_t b = p_b; + real_t pa = p_pre_a; + real_t pb = p_post_b; + + return Math::cubic_interpolate_in_time(a, b, pa, pb, p_c, p_b_t, p_pre_a_t, p_post_b_t); + } else if ((vformat & (vformat - 1))) { + return p_a; //can't interpolate, mix of types + } + + switch (type_a) { + case Variant::VECTOR2: { + Vector2 a = p_a; + Vector2 b = p_b; + Vector2 pa = p_pre_a; + Vector2 pb = p_post_b; + + return a.cubic_interpolate_in_time(b, pa, pb, p_c, p_b_t, p_pre_a_t, p_post_b_t); + } + case Variant::RECT2: { + Rect2 a = p_a; + Rect2 b = p_b; + Rect2 pa = p_pre_a; + Rect2 pb = p_post_b; + + return Rect2( + a.position.cubic_interpolate_in_time(b.position, pa.position, pb.position, p_c, p_b_t, p_pre_a_t, p_post_b_t), + a.size.cubic_interpolate_in_time(b.size, pa.size, pb.size, p_c, p_b_t, p_pre_a_t, p_post_b_t)); + } + case Variant::VECTOR3: { + Vector3 a = p_a; + Vector3 b = p_b; + Vector3 pa = p_pre_a; + Vector3 pb = p_post_b; + + return a.cubic_interpolate_in_time(b, pa, pb, p_c, p_b_t, p_pre_a_t, p_post_b_t); + } + case Variant::QUATERNION: { + Quaternion a = p_a; + Quaternion b = p_b; + Quaternion pa = p_pre_a; + Quaternion pb = p_post_b; + + return a.spherical_cubic_interpolate_in_time(b, pa, pb, p_c, p_b_t, p_pre_a_t, p_post_b_t); + } + case Variant::AABB: { + AABB a = p_a; + AABB b = p_b; + AABB pa = p_pre_a; + AABB pb = p_post_b; + + return AABB( + a.position.cubic_interpolate_in_time(b.position, pa.position, pb.position, p_c, p_b_t, p_pre_a_t, p_post_b_t), + a.size.cubic_interpolate_in_time(b.size, pa.size, pb.size, p_c, p_b_t, p_pre_a_t, p_post_b_t)); + } + default: { + return _interpolate(p_a, p_b, p_c); + } + } +} + +real_t Animation::_cubic_interpolate_in_time(const real_t &p_pre_a, const real_t &p_a, const real_t &p_b, const real_t &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const { + return _interpolate(p_a, p_b, p_c); +} + template <class T> T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward) const { int len = _find(p_keys, length) + 1; // try to find last key (there may be more past the end) @@ -2568,26 +2661,65 @@ T Animation::_interpolate(const Vector<TKey<T>> &p_keys, double p_time, Interpol case INTERPOLATION_LINEAR: { return _interpolate(p_keys[idx].value, p_keys[next].value, c); } break; - case INTERPOLATION_CUBIC: { - int pre = idx - 1; - if (pre < 0) { - if (loop_mode == LOOP_LINEAR && p_loop_wrap) { - pre = len - 1; - } else { - pre = 0; + case INTERPOLATION_CUBIC: + case INTERPOLATION_CUBIC_IN_TIME: { + int pre = 0; + int post = 0; + if (!p_backward) { + pre = idx - 1; + if (pre < 0) { + if (loop_mode == LOOP_LINEAR && p_loop_wrap) { + pre = len - 1; + } else { + pre = 0; + } } - } - int post = next + 1; - if (post >= len) { - if (loop_mode == LOOP_LINEAR && p_loop_wrap) { - post = 0; - } else { - post = next; + post = next + 1; + if (post >= len) { + if (loop_mode == LOOP_LINEAR && p_loop_wrap) { + post = 0; + } else { + post = next; + } + } + } else { + pre = idx + 1; + if (pre >= len) { + if (loop_mode == LOOP_LINEAR && p_loop_wrap) { + pre = 0; + } else { + pre = idx; + } + } + post = next - 1; + if (post < 0) { + if (loop_mode == LOOP_LINEAR && p_loop_wrap) { + post = len - 1; + } else { + post = 0; + } } } - return _cubic_interpolate(p_keys[pre].value, p_keys[idx].value, p_keys[next].value, p_keys[post].value, c); + if (loop_mode == LOOP_LINEAR && p_loop_wrap) { + if (p_interp == INTERPOLATION_CUBIC) { + return _cubic_interpolate(p_keys[pre].value, p_keys[idx].value, p_keys[next].value, p_keys[post].value, c); + } + return _cubic_interpolate_in_time( + p_keys[pre].value, p_keys[idx].value, p_keys[next].value, p_keys[post].value, c, + pre > idx ? -length + p_keys[pre].time - p_keys[idx].time : p_keys[pre].time - p_keys[idx].time, + next < idx ? length + p_keys[next].time - p_keys[idx].time : p_keys[next].time - p_keys[idx].time, + next < idx || post <= idx ? length + p_keys[post].time - p_keys[idx].time : p_keys[post].time - p_keys[idx].time); + } + if (p_interp == INTERPOLATION_CUBIC) { + return _cubic_interpolate(p_keys[pre].value, p_keys[idx].value, p_keys[next].value, p_keys[post].value, c); + } + return _cubic_interpolate_in_time( + p_keys[pre].value, p_keys[idx].value, p_keys[next].value, p_keys[post].value, c, + p_keys[pre].time - p_keys[idx].time, + p_keys[next].time - p_keys[idx].time, + p_keys[post].time - p_keys[idx].time); } break; default: return p_keys[idx].value; @@ -3839,6 +3971,7 @@ void Animation::_bind_methods() { BIND_ENUM_CONSTANT(INTERPOLATION_NEAREST); BIND_ENUM_CONSTANT(INTERPOLATION_LINEAR); BIND_ENUM_CONSTANT(INTERPOLATION_CUBIC); + BIND_ENUM_CONSTANT(INTERPOLATION_CUBIC_IN_TIME); BIND_ENUM_CONSTANT(UPDATE_CONTINUOUS); BIND_ENUM_CONSTANT(UPDATE_DISCRETE); diff --git a/scene/resources/animation.h b/scene/resources/animation.h index bf9f786a0d..b141b4e15f 100644 --- a/scene/resources/animation.h +++ b/scene/resources/animation.h @@ -56,7 +56,8 @@ public: enum InterpolationType { INTERPOLATION_NEAREST, INTERPOLATION_LINEAR, - INTERPOLATION_CUBIC + INTERPOLATION_CUBIC, + INTERPOLATION_CUBIC_IN_TIME, }; enum UpdateMode { @@ -231,6 +232,11 @@ private: _FORCE_INLINE_ Variant _cubic_interpolate(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c) const; _FORCE_INLINE_ real_t _cubic_interpolate(const real_t &p_pre_a, const real_t &p_a, const real_t &p_b, const real_t &p_post_b, real_t p_c) const; + _FORCE_INLINE_ Vector3 _cubic_interpolate_in_time(const Vector3 &p_pre_a, const Vector3 &p_a, const Vector3 &p_b, const Vector3 &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const; + _FORCE_INLINE_ Quaternion _cubic_interpolate_in_time(const Quaternion &p_pre_a, const Quaternion &p_a, const Quaternion &p_b, const Quaternion &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const; + _FORCE_INLINE_ Variant _cubic_interpolate_in_time(const Variant &p_pre_a, const Variant &p_a, const Variant &p_b, const Variant &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const; + _FORCE_INLINE_ real_t _cubic_interpolate_in_time(const real_t &p_pre_a, const real_t &p_a, const real_t &p_b, const real_t &p_post_b, real_t p_c, real_t p_pre_a_t, real_t p_b_t, real_t p_post_b_t) const; + template <class T> _FORCE_INLINE_ T _interpolate(const Vector<TKey<T>> &p_keys, double p_time, InterpolationType p_interp, bool p_loop_wrap, bool *p_ok, bool p_backward = false) const; |