// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
namespace FlaxEngine
{
///
/// An animation spline represented by a set of keyframes, each representing an endpoint of an curve.
///
/// The animated value type.
public abstract class CurveBase where T : new()
{
///
/// The keyframes data access interface.
///
/// The type of the keyframe data.
protected interface IKeyframeAccess where U : new()
{
///
/// Gets the Bezier curve tangent.
///
/// The value.
/// The tangent.
/// The length divided by 3.
/// The result.
void GetTangent(ref U value, ref U tangent, float lengthThird, out U result);
///
/// Calculates the linear interpolation at the specified alpha.
///
/// The start value (alpha=0).
/// The end value (alpha=1).
/// The alpha.
/// The result.
void Linear(ref U a, ref U b, float alpha, out U result);
///
/// Calculates the Bezier curve value at the specified alpha.
///
/// The p0.
/// The p1.
/// The p2.
/// The p3.
/// The alpha.
/// The result.
void Bezier(ref U p0, ref U p1, ref U p2, ref U p3, float alpha, out U result);
}
private class KeyframeAccess :
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess,
IKeyframeAccess
{
public void GetTangent(ref bool value, ref bool tangent, float lengthThird, out bool result)
{
result = value;
}
public void Linear(ref bool a, ref bool b, float alpha, out bool result)
{
result = a;
}
public void Bezier(ref bool p0, ref bool p1, ref bool p2, ref bool p3, float alpha, out bool result)
{
result = p0;
}
public void GetTangent(ref int value, ref int tangent, float lengthThird, out int result)
{
result = value + (int)(tangent * lengthThird);
}
public void Linear(ref int a, ref int b, float alpha, out int result)
{
result = Mathf.Lerp(a, b, alpha);
}
public void Bezier(ref int p0, ref int p1, ref int p2, ref int p3, float alpha, out int result)
{
var p01 = Mathf.Lerp(p0, p1, alpha);
var p12 = Mathf.Lerp(p1, p2, alpha);
var p23 = Mathf.Lerp(p2, p3, alpha);
var p012 = Mathf.Lerp(p01, p12, alpha);
var p123 = Mathf.Lerp(p12, p23, alpha);
result = Mathf.Lerp(p012, p123, alpha);
}
public void GetTangent(ref double value, ref double tangent, float lengthThird, out double result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref double a, ref double b, float alpha, out double result)
{
result = Mathf.Lerp(a, b, alpha);
}
public void Bezier(ref double p0, ref double p1, ref double p2, ref double p3, float alpha, out double result)
{
var p01 = Mathf.Lerp(p0, p1, alpha);
var p12 = Mathf.Lerp(p1, p2, alpha);
var p23 = Mathf.Lerp(p2, p3, alpha);
var p012 = Mathf.Lerp(p01, p12, alpha);
var p123 = Mathf.Lerp(p12, p23, alpha);
result = Mathf.Lerp(p012, p123, alpha);
}
public void GetTangent(ref float value, ref float tangent, float lengthThird, out float result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref float a, ref float b, float alpha, out float result)
{
result = Mathf.Lerp(a, b, alpha);
}
public void Bezier(ref float p0, ref float p1, ref float p2, ref float p3, float alpha, out float result)
{
var p01 = Mathf.Lerp(p0, p1, alpha);
var p12 = Mathf.Lerp(p1, p2, alpha);
var p23 = Mathf.Lerp(p2, p3, alpha);
var p012 = Mathf.Lerp(p01, p12, alpha);
var p123 = Mathf.Lerp(p12, p23, alpha);
result = Mathf.Lerp(p012, p123, alpha);
}
public void GetTangent(ref Vector2 value, ref Vector2 tangent, float lengthThird, out Vector2 result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref Vector2 a, ref Vector2 b, float alpha, out Vector2 result)
{
Vector2.Lerp(ref a, ref b, alpha, out result);
}
public void Bezier(ref Vector2 p0, ref Vector2 p1, ref Vector2 p2, ref Vector2 p3, float alpha, out Vector2 result)
{
Vector2.Lerp(ref p0, ref p1, alpha, out var p01);
Vector2.Lerp(ref p1, ref p2, alpha, out var p12);
Vector2.Lerp(ref p2, ref p3, alpha, out var p23);
Vector2.Lerp(ref p01, ref p12, alpha, out var p012);
Vector2.Lerp(ref p12, ref p23, alpha, out var p123);
Vector2.Lerp(ref p012, ref p123, alpha, out result);
}
public void GetTangent(ref Vector3 value, ref Vector3 tangent, float lengthThird, out Vector3 result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref Vector3 a, ref Vector3 b, float alpha, out Vector3 result)
{
Vector3.Lerp(ref a, ref b, alpha, out result);
}
public void Bezier(ref Vector3 p0, ref Vector3 p1, ref Vector3 p2, ref Vector3 p3, float alpha, out Vector3 result)
{
Vector3.Lerp(ref p0, ref p1, alpha, out var p01);
Vector3.Lerp(ref p1, ref p2, alpha, out var p12);
Vector3.Lerp(ref p2, ref p3, alpha, out var p23);
Vector3.Lerp(ref p01, ref p12, alpha, out var p012);
Vector3.Lerp(ref p12, ref p23, alpha, out var p123);
Vector3.Lerp(ref p012, ref p123, alpha, out result);
}
public void GetTangent(ref Vector4 value, ref Vector4 tangent, float lengthThird, out Vector4 result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref Vector4 a, ref Vector4 b, float alpha, out Vector4 result)
{
Vector4.Lerp(ref a, ref b, alpha, out result);
}
public void Bezier(ref Vector4 p0, ref Vector4 p1, ref Vector4 p2, ref Vector4 p3, float alpha, out Vector4 result)
{
Vector4.Lerp(ref p0, ref p1, alpha, out var p01);
Vector4.Lerp(ref p1, ref p2, alpha, out var p12);
Vector4.Lerp(ref p2, ref p3, alpha, out var p23);
Vector4.Lerp(ref p01, ref p12, alpha, out var p012);
Vector4.Lerp(ref p12, ref p23, alpha, out var p123);
Vector4.Lerp(ref p012, ref p123, alpha, out result);
}
public void GetTangent(ref Float2 value, ref Float2 tangent, float lengthThird, out Float2 result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref Float2 a, ref Float2 b, float alpha, out Float2 result)
{
Float2.Lerp(ref a, ref b, alpha, out result);
}
public void Bezier(ref Float2 p0, ref Float2 p1, ref Float2 p2, ref Float2 p3, float alpha, out Float2 result)
{
Float2.Lerp(ref p0, ref p1, alpha, out var p01);
Float2.Lerp(ref p1, ref p2, alpha, out var p12);
Float2.Lerp(ref p2, ref p3, alpha, out var p23);
Float2.Lerp(ref p01, ref p12, alpha, out var p012);
Float2.Lerp(ref p12, ref p23, alpha, out var p123);
Float2.Lerp(ref p012, ref p123, alpha, out result);
}
public void GetTangent(ref Float3 value, ref Float3 tangent, float lengthThird, out Float3 result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref Float3 a, ref Float3 b, float alpha, out Float3 result)
{
Float3.Lerp(ref a, ref b, alpha, out result);
}
public void Bezier(ref Float3 p0, ref Float3 p1, ref Float3 p2, ref Float3 p3, float alpha, out Float3 result)
{
Float3.Lerp(ref p0, ref p1, alpha, out var p01);
Float3.Lerp(ref p1, ref p2, alpha, out var p12);
Float3.Lerp(ref p2, ref p3, alpha, out var p23);
Float3.Lerp(ref p01, ref p12, alpha, out var p012);
Float3.Lerp(ref p12, ref p23, alpha, out var p123);
Float3.Lerp(ref p012, ref p123, alpha, out result);
}
public void GetTangent(ref Float4 value, ref Float4 tangent, float lengthThird, out Float4 result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref Float4 a, ref Float4 b, float alpha, out Float4 result)
{
Float4.Lerp(ref a, ref b, alpha, out result);
}
public void Bezier(ref Float4 p0, ref Float4 p1, ref Float4 p2, ref Float4 p3, float alpha, out Float4 result)
{
Float4.Lerp(ref p0, ref p1, alpha, out var p01);
Float4.Lerp(ref p1, ref p2, alpha, out var p12);
Float4.Lerp(ref p2, ref p3, alpha, out var p23);
Float4.Lerp(ref p01, ref p12, alpha, out var p012);
Float4.Lerp(ref p12, ref p23, alpha, out var p123);
Float4.Lerp(ref p012, ref p123, alpha, out result);
}
public void GetTangent(ref Double2 value, ref Double2 tangent, float lengthThird, out Double2 result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref Double2 a, ref Double2 b, float alpha, out Double2 result)
{
Double2.Lerp(ref a, ref b, alpha, out result);
}
public void Bezier(ref Double2 p0, ref Double2 p1, ref Double2 p2, ref Double2 p3, float alpha, out Double2 result)
{
Double2.Lerp(ref p0, ref p1, alpha, out var p01);
Double2.Lerp(ref p1, ref p2, alpha, out var p12);
Double2.Lerp(ref p2, ref p3, alpha, out var p23);
Double2.Lerp(ref p01, ref p12, alpha, out var p012);
Double2.Lerp(ref p12, ref p23, alpha, out var p123);
Double2.Lerp(ref p012, ref p123, alpha, out result);
}
public void GetTangent(ref Double3 value, ref Double3 tangent, float lengthThird, out Double3 result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref Double3 a, ref Double3 b, float alpha, out Double3 result)
{
Double3.Lerp(ref a, ref b, alpha, out result);
}
public void Bezier(ref Double3 p0, ref Double3 p1, ref Double3 p2, ref Double3 p3, float alpha, out Double3 result)
{
Double3.Lerp(ref p0, ref p1, alpha, out var p01);
Double3.Lerp(ref p1, ref p2, alpha, out var p12);
Double3.Lerp(ref p2, ref p3, alpha, out var p23);
Double3.Lerp(ref p01, ref p12, alpha, out var p012);
Double3.Lerp(ref p12, ref p23, alpha, out var p123);
Double3.Lerp(ref p012, ref p123, alpha, out result);
}
public void GetTangent(ref Double4 value, ref Double4 tangent, float lengthThird, out Double4 result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref Double4 a, ref Double4 b, float alpha, out Double4 result)
{
Double4.Lerp(ref a, ref b, alpha, out result);
}
public void Bezier(ref Double4 p0, ref Double4 p1, ref Double4 p2, ref Double4 p3, float alpha, out Double4 result)
{
Double4.Lerp(ref p0, ref p1, alpha, out var p01);
Double4.Lerp(ref p1, ref p2, alpha, out var p12);
Double4.Lerp(ref p2, ref p3, alpha, out var p23);
Double4.Lerp(ref p01, ref p12, alpha, out var p012);
Double4.Lerp(ref p12, ref p23, alpha, out var p123);
Double4.Lerp(ref p012, ref p123, alpha, out result);
}
public void GetTangent(ref Quaternion value, ref Quaternion tangent, float lengthThird, out Quaternion result)
{
Quaternion.Slerp(ref value, ref tangent, 1.0f / 3.0f, out result);
}
public void Linear(ref Quaternion a, ref Quaternion b, float alpha, out Quaternion result)
{
Quaternion.Slerp(ref a, ref b, alpha, out result);
}
public void Bezier(ref Quaternion p0, ref Quaternion p1, ref Quaternion p2, ref Quaternion p3, float alpha, out Quaternion result)
{
Quaternion.Slerp(ref p0, ref p1, alpha, out var p01);
Quaternion.Slerp(ref p1, ref p2, alpha, out var p12);
Quaternion.Slerp(ref p2, ref p3, alpha, out var p23);
Quaternion.Slerp(ref p01, ref p12, alpha, out var p012);
Quaternion.Slerp(ref p12, ref p23, alpha, out var p123);
Quaternion.Slerp(ref p012, ref p123, alpha, out result);
}
public void GetTangent(ref Color32 value, ref Color32 tangent, float lengthThird, out Color32 result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref Color32 a, ref Color32 b, float alpha, out Color32 result)
{
Color32.Lerp(ref a, ref b, alpha, out result);
}
public void Bezier(ref Color32 p0, ref Color32 p1, ref Color32 p2, ref Color32 p3, float alpha, out Color32 result)
{
Color32.Lerp(ref p0, ref p1, alpha, out var p01);
Color32.Lerp(ref p1, ref p2, alpha, out var p12);
Color32.Lerp(ref p2, ref p3, alpha, out var p23);
Color32.Lerp(ref p01, ref p12, alpha, out var p012);
Color32.Lerp(ref p12, ref p23, alpha, out var p123);
Color32.Lerp(ref p012, ref p123, alpha, out result);
}
public void GetTangent(ref Color value, ref Color tangent, float lengthThird, out Color result)
{
result = value + tangent * lengthThird;
}
public void Linear(ref Color a, ref Color b, float alpha, out Color result)
{
Color.Lerp(ref a, ref b, alpha, out result);
}
public void Bezier(ref Color p0, ref Color p1, ref Color p2, ref Color p3, float alpha, out Color result)
{
Color.Lerp(ref p0, ref p1, alpha, out var p01);
Color.Lerp(ref p1, ref p2, alpha, out var p12);
Color.Lerp(ref p2, ref p3, alpha, out var p23);
Color.Lerp(ref p01, ref p12, alpha, out var p012);
Color.Lerp(ref p12, ref p23, alpha, out var p123);
Color.Lerp(ref p012, ref p123, alpha, out result);
}
}
///
/// The keyframes data accessor.
///
[NoSerialize]
protected readonly IKeyframeAccess _accessor = new KeyframeAccess() as IKeyframeAccess;
///
/// Evaluates the animation curve value at the specified time.
///
/// The interpolated value from the curve at provided time.
/// The time to evaluate the curve at.
/// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.
public abstract void Evaluate(out T result, float time, bool loop = true);
///
/// Trims the curve keyframes to the specified time range.
///
/// The time start.
/// The time end.
public abstract void Trim(float start, float end);
///
/// Applies the linear transformation (scale and offset) to the keyframes time values.
///
/// The time scale.
/// The time offset.
public abstract void TransformTime(float timeScale, float timeOffset);
///
/// Returns a pair of keys that can be used for interpolating to field the value at the provided time.
///
/// The time for which to find the relevant keys from. It is expected to be clamped to a valid range within the curve.
/// The index of the key to interpolate from.
/// The index of the key to interpolate to.
public abstract void FindKeys(float time, out int leftKey, out int rightKey);
///
/// Wraps the time for the curve sampling with looping mode.
///
/// The time to wrap.
/// The start time.
/// The end time.
/// If set to true loops the curve.
protected static void WrapTime(ref float time, float start, float end, bool loop)
{
float length = end - start;
if (Mathf.NearEqual(length, 0.0f))
{
time = 0.0f;
return;
}
// Clamp to start or loop
if (time < start)
{
if (loop)
time += (Mathf.Floor(end - time) / length) * length;
else
time = start;
}
// Clamp to end or loop
if (time > end)
{
if (loop)
time -= Mathf.Floor((time - start) / length) * length;
else
time = end;
}
}
}
///
/// An animation spline represented by a set of keyframes, each representing an endpoint of a linear curve.
///
/// The animated value type.
public class LinearCurve : CurveBase where T : new()
{
///
/// A single keyframe that can be injected into linear curve.
///
[StructLayout(LayoutKind.Sequential)]
public struct Keyframe : IComparable, IComparable
{
///
/// The time of the keyframe.
///
[EditorOrder(0), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("The time of the keyframe.")]
public float Time;
///
/// The value of the curve at keyframe.
///
[EditorOrder(1), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("The value of the curve at keyframe.")]
public T Value;
///
/// Initializes a new instance of the struct.
///
/// The time.
/// The value.
public Keyframe(float time, T value)
{
Time = time;
Value = value;
}
///
public int CompareTo(object obj)
{
if (obj is Keyframe other)
return Time > other.Time ? 1 : 0;
return 1;
}
///
public int CompareTo(Keyframe other)
{
return Time > other.Time ? 1 : 0;
}
///
public override string ToString()
{
return Value?.ToString() ?? string.Empty;
}
}
///
/// The keyframes collection. Can be directly modified but ensure to sort it after editing so keyframes are organized by ascending time value.
///
[Serialize]
public Keyframe[] Keyframes;
///
/// Initializes a new instance of the class.
///
public LinearCurve()
{
Keyframes = Utils.GetEmptyArray();
}
///
/// Initializes a new instance of the class.
///
/// The keyframes.
public LinearCurve(params Keyframe[] keyframes)
{
Keyframes = keyframes;
}
///
/// Initializes a new instance of the class.
///
/// The keyframes.
public LinearCurve(IEnumerable keyframes)
{
Keyframes = keyframes.ToArray();
}
///
/// Evaluates the animation curve key at the specified time.
///
/// The interpolated key from the curve at provided time.
/// The time to evaluate the curve at.
/// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.
public void EvaluateKey(out Keyframe result, float time, bool loop = true)
{
if (Keyframes.Length == 0)
{
result = new Keyframe(time, default);
return;
}
float start = Mathf.Min(Keyframes.First().Time, 0.0f);
float end = Keyframes.Last().Time;
WrapTime(ref time, start, end, loop);
FindKeys(time, out var leftKeyIdx, out var rightKeyIdx);
Keyframe leftKey = Keyframes[leftKeyIdx];
Keyframe rightKey = Keyframes[rightKeyIdx];
if (leftKeyIdx == rightKeyIdx)
{
result = leftKey;
return;
}
// Scale from arbitrary range to [0, 1]
float length = rightKey.Time - leftKey.Time;
float t = Mathf.NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length;
// Evaluate the key at the curve
result.Time = leftKey.Time + length * t;
_accessor.Linear(ref leftKey.Value, ref rightKey.Value, t, out result.Value);
}
///
public override void Evaluate(out T result, float time, bool loop = true)
{
if (Keyframes.Length == 0)
{
result = default;
return;
}
float start = Mathf.Min(Keyframes.First().Time, 0.0f);
float end = Keyframes.Last().Time;
WrapTime(ref time, start, end, loop);
FindKeys(time, out var leftKeyIdx, out var rightKeyIdx);
Keyframe leftKey = Keyframes[leftKeyIdx];
Keyframe rightKey = Keyframes[rightKeyIdx];
if (leftKeyIdx == rightKeyIdx)
{
result = leftKey.Value;
return;
}
// Scale from arbitrary range to [0, 1]
float length = rightKey.Time - leftKey.Time;
float t = Mathf.NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length;
// Evaluate the value at the curve
_accessor.Linear(ref leftKey.Value, ref rightKey.Value, t, out result);
}
///
public override void Trim(float start, float end)
{
// Early out
if (Keyframes.Length == 0 || (Keyframes.First().Time >= start && Keyframes.Last().Time <= end))
return;
if (end - start <= Mathf.Epsilon)
{
// Erase the curve
Keyframes = Utils.GetEmptyArray();
return;
}
var result = new List(Keyframes);
EvaluateKey(out var startValue, start, false);
EvaluateKey(out var endValue, end, false);
// Begin
for (int i = 0; i < result.Count && result.Count > 0; i++)
{
if (result[i].Time < start)
{
result.RemoveAt(i);
i--;
}
else
{
break;
}
}
if (result.Count == 0 || !Mathf.NearEqual(result.First().Time, start))
{
Keyframe key = startValue;
key.Time = start;
result.Insert(0, key);
}
// End
for (int i = result.Count - 1; i >= 0 && result.Count > 0; i--)
{
if (result[i].Time > end)
{
result.RemoveAt(i);
}
else
{
break;
}
}
if (result.Count == 0 || !Mathf.NearEqual(result.Last().Time, end))
{
Keyframe key = endValue;
key.Time = end;
result.Add(key);
}
Keyframes = result.ToArray();
// Rebase the keyframes time
if (!Mathf.IsZero(start))
{
for (int i = 0; i < Keyframes.Length; i++)
Keyframes[i].Time -= start;
}
}
///
public override void TransformTime(float timeScale, float timeOffset)
{
for (int i = 0; i < Keyframes.Length; i++)
Keyframes[i].Time = Keyframes[i].Time * timeScale + timeOffset;
}
///
public override void FindKeys(float time, out int leftKey, out int rightKey)
{
int start = 0;
int searchLength = Keyframes.Length;
while (searchLength > 0)
{
int half = searchLength >> 1;
int mid = start + half;
if (time < Keyframes[mid].Time)
{
searchLength = half;
}
else
{
start = mid + 1;
searchLength -= (half + 1);
}
}
leftKey = Mathf.Max(0, start - 1);
rightKey = Mathf.Min(start, Keyframes.Length - 1);
}
}
///
/// An animation spline represented by a set of keyframes, each representing an endpoint of a Bezier curve.
///
/// The animated value type.
public class BezierCurve : CurveBase where T : new()
{
///
/// A single keyframe that can be injected into Bezier curve.
///
[StructLayout(LayoutKind.Sequential)]
public struct Keyframe : IComparable, IComparable
{
///
/// The time of the keyframe.
///
[EditorOrder(0), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("The time of the keyframe.")]
public float Time;
///
/// The value of the curve at keyframe.
///
[EditorOrder(1), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("The value of the curve at keyframe.")]
public T Value;
///
/// The input tangent (going from the previous key to this one) of the key.
///
[EditorOrder(2), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("The input tangent (going from the previous key to this one) of the key."), EditorDisplay(null, "Tangent In")]
public T TangentIn;
///
/// The output tangent (going from this key to next one) of the key.
///
[EditorOrder(3), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("The output tangent (going from this key to next one) of the key.")]
public T TangentOut;
///
/// Initializes a new instance of the struct.
///
/// The time.
/// The value.
public Keyframe(float time, T value)
{
Time = time;
Value = value;
TangentIn = TangentOut = default;
}
///
/// Initializes a new instance of the struct.
///
/// The time.
/// The value.
/// The start tangent.
/// The end tangent.
public Keyframe(float time, T value, T tangentIn, T tangentOut)
{
Time = time;
Value = value;
TangentIn = tangentIn;
TangentOut = tangentOut;
}
///
public int CompareTo(object obj)
{
if (obj is Keyframe other)
return Time > other.Time ? 1 : 0;
return 1;
}
///
public int CompareTo(Keyframe other)
{
return Time > other.Time ? 1 : 0;
}
///
public override string ToString()
{
return Value?.ToString() ?? string.Empty;
}
}
///
/// The keyframes collection. Can be directly modified but ensure to sort it after editing so keyframes are organized by ascending time value.
///
[Serialize]
public Keyframe[] Keyframes;
///
/// Initializes a new instance of the class.
///
public BezierCurve()
{
Keyframes = Utils.GetEmptyArray();
}
///
/// Initializes a new instance of the class.
///
/// The keyframes.
public BezierCurve(params Keyframe[] keyframes)
{
Keyframes = keyframes;
}
///
/// Initializes a new instance of the class.
///
/// The keyframes.
public BezierCurve(IEnumerable keyframes)
{
Keyframes = keyframes.ToArray();
}
///
/// Evaluates the animation curve key at the specified time.
///
/// The interpolated key from the curve at provided time.
/// The time to evaluate the curve at.
/// If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.
public void EvaluateKey(out Keyframe result, float time, bool loop = true)
{
if (Keyframes.Length == 0)
{
result = new Keyframe(time, default);
return;
}
float start = Mathf.Min(Keyframes.First().Time, 0.0f);
float end = Keyframes.Last().Time;
WrapTime(ref time, start, end, loop);
FindKeys(time, out var leftKeyIdx, out var rightKeyIdx);
Keyframe leftKey = Keyframes[leftKeyIdx];
Keyframe rightKey = Keyframes[rightKeyIdx];
if (leftKeyIdx == rightKeyIdx)
{
result = leftKey;
return;
}
// Scale from arbitrary range to [0, 1]
float length = rightKey.Time - leftKey.Time;
float t = Mathf.NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length;
// Evaluate the key at the curve
result.Time = leftKey.Time + length * t;
float lengthThird = length / 3.0f;
_accessor.GetTangent(ref leftKey.Value, ref leftKey.TangentOut, lengthThird, out var leftTangent);
_accessor.GetTangent(ref rightKey.Value, ref rightKey.TangentIn, lengthThird, out var rightTangent);
_accessor.Bezier(ref leftKey.Value, ref leftTangent, ref rightTangent, ref rightKey.Value, t, out result.Value);
result.TangentIn = leftKey.TangentOut;
result.TangentOut = rightKey.TangentIn;
}
///
public override void Evaluate(out T result, float time, bool loop = true)
{
if (Keyframes.Length == 0)
{
result = default;
return;
}
float start = Mathf.Min(Keyframes.First().Time, 0.0f);
float end = Keyframes.Last().Time;
WrapTime(ref time, start, end, loop);
FindKeys(time, out var leftKeyIdx, out var rightKeyIdx);
Keyframe leftKey = Keyframes[leftKeyIdx];
Keyframe rightKey = Keyframes[rightKeyIdx];
if (leftKeyIdx == rightKeyIdx)
{
result = leftKey.Value;
return;
}
// Scale from arbitrary range to [0, 1]
float length = rightKey.Time - leftKey.Time;
float t = Mathf.NearEqual(length, 0.0f) ? 0.0f : (time - leftKey.Time) / length;
// Evaluate the value at the curve
float lengthThird = length / 3.0f;
_accessor.GetTangent(ref leftKey.Value, ref leftKey.TangentOut, lengthThird, out var leftTangent);
_accessor.GetTangent(ref rightKey.Value, ref rightKey.TangentIn, lengthThird, out var rightTangent);
_accessor.Bezier(ref leftKey.Value, ref leftTangent, ref rightTangent, ref rightKey.Value, t, out result);
}
///
public override void Trim(float start, float end)
{
// Early out
if (Keyframes.Length == 0 || (Keyframes.First().Time >= start && Keyframes.Last().Time <= end))
return;
if (end - start <= Mathf.Epsilon)
{
// Erase the curve
Keyframes = Utils.GetEmptyArray();
return;
}
var result = new List(Keyframes);
EvaluateKey(out var startValue, start, false);
EvaluateKey(out var endValue, end, false);
// Begin
for (int i = 0; i < result.Count && result.Count > 0; i++)
{
if (result[i].Time < start)
{
result.RemoveAt(i);
i--;
}
else
{
break;
}
}
if (result.Count == 0 || !Mathf.NearEqual(result.First().Time, start))
{
Keyframe key = startValue;
key.Time = start;
result.Insert(0, key);
}
// End
for (int i = result.Count - 1; i >= 0 && result.Count > 0; i--)
{
if (result[i].Time > end)
{
result.RemoveAt(i);
}
else
{
break;
}
}
if (result.Count == 0 || !Mathf.NearEqual(result.Last().Time, end))
{
Keyframe key = endValue;
key.Time = end;
result.Add(key);
}
Keyframes = result.ToArray();
// Rebase the keyframes time
if (!Mathf.IsZero(start))
{
for (int i = 0; i < Keyframes.Length; i++)
Keyframes[i].Time -= start;
}
}
///
public override void TransformTime(float timeScale, float timeOffset)
{
for (int i = 0; i < Keyframes.Length; i++)
Keyframes[i].Time = Keyframes[i].Time * timeScale + timeOffset;
}
///
public override void FindKeys(float time, out int leftKey, out int rightKey)
{
int start = 0;
int searchLength = Keyframes.Length;
while (searchLength > 0)
{
int half = searchLength >> 1;
int mid = start + half;
if (time < Keyframes[mid].Time)
{
searchLength = half;
}
else
{
start = mid + 1;
searchLength -= (half + 1);
}
}
leftKey = Mathf.Max(0, start - 1);
rightKey = Mathf.Min(start, Keyframes.Length - 1);
}
}
}