using JetBrains.Annotations; using UnityEngine; namespace PrimeTween { /// /// A wrapper struct that encapsulates three available easing methods: standard Ease, AnimationCurve, or Parametric Easing.
/// Use static methods to create an Easing struct, for example: Easing.Standard(Ease.OutBounce), Easing.Curve(animationCurve), /// Easing.Elastic(strength, period), etc. ///
[PublicAPI] public readonly struct Easing { internal readonly Ease ease; internal readonly AnimationCurve curve; internal readonly ParametricEase parametricEase; internal readonly float parametricEaseStrength; internal readonly float parametricEasePeriod; Easing(ParametricEase type, float strength, float period = float.NaN) { ease = Ease.Custom; curve = null; parametricEase = type; parametricEaseStrength = strength; parametricEasePeriod = period; } Easing(Ease ease, [CanBeNull] AnimationCurve curve) { if (ease == Ease.Custom) { if (curve == null || !TweenSettings.ValidateCustomCurveKeyframes(curve)) { Debug.LogError("Ease is Ease.Custom, but AnimationCurve is not configured correctly. Using Ease.Default instead."); ease = Ease.Default; } } this.ease = ease; this.curve = curve; parametricEase = ParametricEase.None; parametricEaseStrength = float.NaN; parametricEasePeriod = float.NaN; } public static implicit operator Easing(Ease ease) => Standard(ease); /// Standard Robert Penner's easing method. Or simply use Ease enum instead. public static Easing Standard(Ease ease) { Assert.AreNotEqual(Ease.Custom, ease); if (ease == Ease.Default) { ease = PrimeTweenConfig.defaultEase; } return new Easing(ease, null); } public static implicit operator Easing([NotNull] AnimationCurve curve) => Curve(curve); /// AnimationCurve to use as an easing function. Or simply use AnimationCurve instead. public static Easing Curve([NotNull] AnimationCurve curve) => new Easing(Ease.Custom, curve); /// Customizes the bounce of Ease.OutBounce. public static Easing Bounce(float strength) => new Easing(ParametricEase.Bounce, strength); /// Customizes the exact of the first bounce in meters/angles. public static Easing BounceExact(float amplitude) => new Easing(ParametricEase.BounceExact, amplitude); /// Customizes the overshoot of Ease.OutBack. public static Easing Overshoot(float strength) => new Easing(ParametricEase.Overshoot, strength * StandardEasing.backEaseConst); /// Customizes the and oscillation of Ease.OutElastic. public static Easing Elastic(float strength, float period = StandardEasing.defaultElasticEasePeriod) { if (strength < 1) { strength = Mathf.Lerp(0.2f, 1f, strength); // remap strength to limit decayFactor } return new Easing(ParametricEase.Elastic, strength, Mathf.Max(0.1f, period)); } internal static float Evaluate(float t, ParametricEase parametricEase, float strength, float period, float duration) { switch (parametricEase) { case ParametricEase.Overshoot: t -= 1.0f; return t * t * ((strength + 1) * t + strength) + 1.0f; case ParametricEase.Elastic: const float twoPi = 2 * Mathf.PI; float decayFactor; if (strength >= 1) { decayFactor = 1f; } else { decayFactor = 1 / strength; strength = 1; } float decay = Mathf.Pow(2, -10f * t * decayFactor); if (duration == 0) { return 1; } period /= duration; float phase = period / twoPi * Mathf.Asin(1f / strength); return t > 0.9999f ? 1 : strength * decay * Mathf.Sin((t - phase) * twoPi / period) + 1f; case ParametricEase.Bounce: return Bounce(t, strength); case ParametricEase.BounceExact: case ParametricEase.None: default: throw new System.Exception(); } } internal static float Evaluate(float t, [NotNull] ReusableTween tween) { var settings = tween.settings; var parametricEase = settings.parametricEase; var strength = settings.parametricEaseStrength; if (parametricEase == ParametricEase.BounceExact) { var fullAmplitude = tween.propType == PropType.Quaternion ? Quaternion.Angle(tween.startValue.QuaternionVal, tween.endValue.QuaternionVal) : tween.diff.Vector4Val.magnitude; // todo account for double /*double calcFullAmplitude() { switch (tween.propType) { case PropType.Quaternion: return Quaternion.Angle(tween.startValue.QuaternionVal, tween.endValue.QuaternionVal); case PropType.Double: return tween.startValue.DoubleVal - tween.endValue.DoubleVal; default: return tween.diff.Vector4Val.magnitude; } }*/ float strengthFactor = fullAmplitude < 0.0001f ? 1f : 1f / (fullAmplitude * (1f - firstBounceAmpl)); return Bounce(t, strength * strengthFactor); } return Evaluate(t, parametricEase, strength, settings.parametricEasePeriod, settings.duration); } const float firstBounceAmpl = 0.75f; static float Bounce(float t, float strength) { const float n1 = 7.5625f; const float d1 = 2.75f; if (t < 1 / d1) { return n1 * t * t; } return 1 - (1 - bounce()) * strength; float bounce() { if (t < 2 / d1) { return n1 * (t -= 1.5f / d1) * t + firstBounceAmpl; } if (t < 2.5 / d1) { return n1 * (t -= 2.25f / d1) * t + 0.9375f; } return n1 * (t -= 2.625f / d1) * t + 0.984375f; } } #if PRIME_TWEEN_DOTWEEN_ADAPTER /// Can't be public API because ParametricEase.BounceExact can only be evaluated with these params: propType, startValue, endValue /// internal float Evaluate(float interpolationFactor) { if (ease == Ease.Custom) { if (parametricEase != ParametricEase.None) { Assert.AreNotEqual(ParametricEase.BounceExact, parametricEase); return Evaluate(interpolationFactor, parametricEase, parametricEaseStrength, parametricEasePeriod, 1f); } Assert.IsNull(curve); return curve.Evaluate(interpolationFactor); } return Evaluate(interpolationFactor, ease); } #endif public static float Evaluate(float interpolationFactor, Ease ease) { switch (ease) { case Ease.Custom: Debug.LogError("Ease.Custom is an invalid type for Easing.Evaluate(). Please choose another Ease type instead."); return interpolationFactor; case Ease.Default: #if UNITY_EDITOR if (Constants.warnNoInstance) { return interpolationFactor; } #endif return StandardEasing.Evaluate(interpolationFactor, PrimeTweenManager.Instance.defaultEase); default: return StandardEasing.Evaluate(interpolationFactor, ease); } } } internal enum ParametricEase { None = 0, Overshoot = 5, Bounce = 7, Elastic = 11, BounceExact } }