using System;
using UnityEngine;
namespace Uigenx.Animation
{
///
/// The 23 easing keywords USS transitions accept, plus for an explicit
/// cubic bezier. Wire names are the kebab-case USS spellings.
///
public enum UgxEasingKind
{
Ease = 0,
EaseIn,
EaseOut,
EaseInOut,
Linear,
EaseInSine,
EaseOutSine,
EaseInOutSine,
EaseInCubic,
EaseOutCubic,
EaseInOutCubic,
EaseInCirc,
EaseOutCirc,
EaseInOutCirc,
EaseInElastic,
EaseOutElastic,
EaseInOutElastic,
EaseInBack,
EaseOutBack,
EaseInOutBack,
EaseInBounce,
EaseOutBounce,
EaseInOutBounce,
Bezier,
}
/// An easing keyword, or a cubic bezier when is Bezier.
[Serializable]
public struct UgxEasingCurve
{
[SerializeField] UgxEasingKind kind;
[SerializeField] float x1, y1, x2, y2;
public UgxEasingKind Kind => kind;
public static UgxEasingCurve Of(UgxEasingKind kind) => new UgxEasingCurve { kind = kind };
public static UgxEasingCurve CubicBezier(float x1, float y1, float x2, float y2)
=> new UgxEasingCurve { kind = UgxEasingKind.Bezier, x1 = x1, y1 = y1, x2 = x2, y2 = y2 };
public static UgxEasingCurve Default => Of(UgxEasingKind.Ease);
public float Evaluate(float t)
=> kind == UgxEasingKind.Bezier ? UgxEasing.Bezier(t, x1, y1, x2, y2) : UgxEasing.Evaluate(kind, t);
}
///
/// Easing math shared by the animator and the studio canvas. The keyword curves are the same
/// Penner set Unity uses for USS transitions, so a clip looks identical in both.
///
public static class UgxEasing
{
const float BackConstant = 1.70158f;
const float ElasticPeriod = 0.3f;
public static bool TryParse(string wireName, out UgxEasingKind kind)
{
switch (wireName)
{
case "ease": kind = UgxEasingKind.Ease; return true;
case "ease-in": kind = UgxEasingKind.EaseIn; return true;
case "ease-out": kind = UgxEasingKind.EaseOut; return true;
case "ease-in-out": kind = UgxEasingKind.EaseInOut; return true;
case "linear": kind = UgxEasingKind.Linear; return true;
case "ease-in-sine": kind = UgxEasingKind.EaseInSine; return true;
case "ease-out-sine": kind = UgxEasingKind.EaseOutSine; return true;
case "ease-in-out-sine": kind = UgxEasingKind.EaseInOutSine; return true;
case "ease-in-cubic": kind = UgxEasingKind.EaseInCubic; return true;
case "ease-out-cubic": kind = UgxEasingKind.EaseOutCubic; return true;
case "ease-in-out-cubic": kind = UgxEasingKind.EaseInOutCubic; return true;
case "ease-in-circ": kind = UgxEasingKind.EaseInCirc; return true;
case "ease-out-circ": kind = UgxEasingKind.EaseOutCirc; return true;
case "ease-in-out-circ": kind = UgxEasingKind.EaseInOutCirc; return true;
case "ease-in-elastic": kind = UgxEasingKind.EaseInElastic; return true;
case "ease-out-elastic": kind = UgxEasingKind.EaseOutElastic; return true;
case "ease-in-out-elastic": kind = UgxEasingKind.EaseInOutElastic; return true;
case "ease-in-back": kind = UgxEasingKind.EaseInBack; return true;
case "ease-out-back": kind = UgxEasingKind.EaseOutBack; return true;
case "ease-in-out-back": kind = UgxEasingKind.EaseInOutBack; return true;
case "ease-in-bounce": kind = UgxEasingKind.EaseInBounce; return true;
case "ease-out-bounce": kind = UgxEasingKind.EaseOutBounce; return true;
case "ease-in-out-bounce": kind = UgxEasingKind.EaseInOutBounce; return true;
default: kind = UgxEasingKind.Ease; return false;
}
}
public static float Evaluate(UgxEasingKind kind, float t)
{
if (t <= 0f) return 0f;
if (t >= 1f) return 1f;
switch (kind)
{
case UgxEasingKind.Linear: return t;
case UgxEasingKind.Ease:
case UgxEasingKind.EaseInOut: return InOutQuad(t);
case UgxEasingKind.EaseIn: return t * t;
case UgxEasingKind.EaseOut: return t * (2f - t);
case UgxEasingKind.EaseInSine: return 1f - Mathf.Cos(t * Mathf.PI * 0.5f);
case UgxEasingKind.EaseOutSine: return Mathf.Sin(t * Mathf.PI * 0.5f);
case UgxEasingKind.EaseInOutSine: return -(Mathf.Cos(Mathf.PI * t) - 1f) * 0.5f;
case UgxEasingKind.EaseInCubic: return t * t * t;
case UgxEasingKind.EaseOutCubic: return 1f - Cube(1f - t);
case UgxEasingKind.EaseInOutCubic:
return t < 0.5f ? 4f * t * t * t : 1f - Cube(-2f * t + 2f) * 0.5f;
case UgxEasingKind.EaseInCirc: return 1f - Mathf.Sqrt(Mathf.Max(0f, 1f - t * t));
case UgxEasingKind.EaseOutCirc: return Mathf.Sqrt(Mathf.Max(0f, 1f - Sqr(t - 1f)));
case UgxEasingKind.EaseInOutCirc:
return t < 0.5f
? (1f - Mathf.Sqrt(Mathf.Max(0f, 1f - Sqr(2f * t)))) * 0.5f
: (Mathf.Sqrt(Mathf.Max(0f, 1f - Sqr(-2f * t + 2f))) + 1f) * 0.5f;
case UgxEasingKind.EaseInElastic: return InElastic(t);
case UgxEasingKind.EaseOutElastic: return OutElastic(t);
case UgxEasingKind.EaseInOutElastic:
return t < 0.5f ? InElastic(t * 2f) * 0.5f : OutElastic(t * 2f - 1f) * 0.5f + 0.5f;
case UgxEasingKind.EaseInBack: return t * t * ((BackConstant + 1f) * t - BackConstant);
case UgxEasingKind.EaseOutBack: return 1f - InBack(1f - t);
case UgxEasingKind.EaseInOutBack:
return t < 0.5f ? InBack(t * 2f) * 0.5f : (1f - InBack(2f - t * 2f)) * 0.5f + 0.5f;
case UgxEasingKind.EaseInBounce: return 1f - OutBounce(1f - t);
case UgxEasingKind.EaseOutBounce: return OutBounce(t);
case UgxEasingKind.EaseInOutBounce:
return t < 0.5f ? (1f - OutBounce(1f - t * 2f)) * 0.5f : OutBounce(t * 2f - 1f) * 0.5f + 0.5f;
default: return t;
}
}
///
/// CSS cubic-bezier(x1, y1, x2, y2). Newton iterations followed by bisection, the same
/// scheme browsers use; costs about 12 multiplies and allocates nothing.
///
public static float Bezier(float t, float x1, float y1, float x2, float y2)
{
if (t <= 0f) return 0f;
if (t >= 1f) return 1f;
float u = t;
for (int i = 0; i < 8; i++)
{
float x = BezierAxis(u, x1, x2) - t;
if (Mathf.Abs(x) < 1e-5f) return BezierAxis(u, y1, y2);
float d = BezierSlope(u, x1, x2);
if (Mathf.Abs(d) < 1e-6f) break;
u -= x / d;
}
float lo = 0f, hi = 1f;
u = t;
for (int i = 0; i < 20; i++)
{
float x = BezierAxis(u, x1, x2);
if (Mathf.Abs(x - t) < 1e-5f) break;
if (x > t) hi = u; else lo = u;
u = (lo + hi) * 0.5f;
}
return BezierAxis(u, y1, y2);
}
static float BezierAxis(float u, float a, float b)
{
float inv = 1f - u;
return 3f * inv * inv * u * a + 3f * inv * u * u * b + u * u * u;
}
static float BezierSlope(float u, float a, float b)
{
float inv = 1f - u;
return 3f * inv * inv * a + 6f * inv * u * (b - a) + 3f * u * u * (1f - b);
}
static float InOutQuad(float t) => t < 0.5f ? 2f * t * t : 1f - Sqr(-2f * t + 2f) * 0.5f;
static float InBack(float t) => t * t * ((BackConstant + 1f) * t - BackConstant);
static float InElastic(float t) => 1f - OutElastic(1f - t);
static float OutElastic(float t)
{
const float twoPiOverPeriod = 2f * Mathf.PI / ElasticPeriod;
return Mathf.Pow(2f, -10f * t) * Mathf.Sin((t - ElasticPeriod * 0.25f) * twoPiOverPeriod) + 1f;
}
static float OutBounce(float t)
{
const float n = 7.5625f;
const float d = 2.75f;
if (t < 1f / d) return n * t * t;
if (t < 2f / d) { t -= 1.5f / d; return n * t * t + 0.75f; }
if (t < 2.5f / d) { t -= 2.25f / d; return n * t * t + 0.9375f; }
t -= 2.625f / d;
return n * t * t + 0.984375f;
}
static float Sqr(float v) => v * v;
static float Cube(float v) => v * v * v;
}
}