# Conflicts: # Source/Engine/Core/Math/Color.cs # Source/Engine/Navigation/Navigation.cpp # Source/Engine/Platform/Win32/Win32Platform.cpp
867 lines
32 KiB
C#
867 lines
32 KiB
C#
// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace FlaxEngine
|
|
{
|
|
/// <summary>
|
|
/// An animation spline represented by a set of keyframes, each representing an endpoint of an curve.
|
|
/// </summary>
|
|
/// <typeparam name="T">The animated value type.</typeparam>
|
|
public abstract class CurveBase<T> where T : new()
|
|
{
|
|
/// <summary>
|
|
/// The keyframes data access interface.
|
|
/// </summary>
|
|
/// <typeparam name="U">The type of the keyframe data.</typeparam>
|
|
protected interface IKeyframeAccess<U> where U : new()
|
|
{
|
|
/// <summary>
|
|
/// Gets the Bezier curve tangent.
|
|
/// </summary>
|
|
/// <param name="value">The value.</param>
|
|
/// <param name="tangent">The tangent.</param>
|
|
/// <param name="lengthThird">The length divided by 3.</param>
|
|
/// <param name="result">The result.</param>
|
|
void GetTangent(ref U value, ref U tangent, float lengthThird, out U result);
|
|
|
|
/// <summary>
|
|
/// Calculates the linear interpolation at the specified alpha.
|
|
/// </summary>
|
|
/// <param name="a">The start value (alpha=0).</param>
|
|
/// <param name="b">The end value (alpha=1).</param>
|
|
/// <param name="alpha">The alpha.</param>
|
|
/// <param name="result">The result.</param>
|
|
void Linear(ref U a, ref U b, float alpha, out U result);
|
|
|
|
/// <summary>
|
|
/// Calculates the Bezier curve value at the specified alpha.
|
|
/// </summary>
|
|
/// <param name="p0">The p0.</param>
|
|
/// <param name="p1">The p1.</param>
|
|
/// <param name="p2">The p2.</param>
|
|
/// <param name="p3">The p3.</param>
|
|
/// <param name="alpha">The alpha.</param>
|
|
/// <param name="result">The result.</param>
|
|
void Bezier(ref U p0, ref U p1, ref U p2, ref U p3, float alpha, out U result);
|
|
}
|
|
|
|
private class KeyframeAccess :
|
|
IKeyframeAccess<bool>,
|
|
IKeyframeAccess<int>,
|
|
IKeyframeAccess<double>,
|
|
IKeyframeAccess<float>,
|
|
IKeyframeAccess<Vector2>,
|
|
IKeyframeAccess<Vector3>,
|
|
IKeyframeAccess<Vector4>,
|
|
IKeyframeAccess<Quaternion>,
|
|
IKeyframeAccess<Color32>,
|
|
IKeyframeAccess<Color>
|
|
{
|
|
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 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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The keyframes data accessor.
|
|
/// </summary>
|
|
[NoSerialize]
|
|
protected readonly IKeyframeAccess<T> _accessor = new KeyframeAccess() as IKeyframeAccess<T>;
|
|
|
|
/// <summary>
|
|
/// Evaluates the animation curve value at the specified time.
|
|
/// </summary>
|
|
/// <param name="result">The interpolated value from the curve at provided time.</param>
|
|
/// <param name="time">The time to evaluate the curve at.</param>
|
|
/// <param name="loop">If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.</param>
|
|
public abstract void Evaluate(out T result, float time, bool loop = true);
|
|
|
|
/// <summary>
|
|
/// Trims the curve keyframes to the specified time range.
|
|
/// </summary>
|
|
/// <param name="start">The time start.</param>
|
|
/// <param name="end">The time end.</param>
|
|
public abstract void Trim(float start, float end);
|
|
|
|
/// <summary>
|
|
/// Applies the linear transformation (scale and offset) to the keyframes time values.
|
|
/// </summary>
|
|
/// <param name="timeScale">The time scale.</param>
|
|
/// <param name="timeOffset">The time offset.</param>
|
|
public abstract void TransformTime(float timeScale, float timeOffset);
|
|
|
|
/// <summary>
|
|
/// Returns a pair of keys that can be used for interpolating to field the value at the provided time.
|
|
/// </summary>
|
|
/// <param name="time">The time for which to find the relevant keys from. It is expected to be clamped to a valid range within the curve.</param>
|
|
/// <param name="leftKey">The index of the key to interpolate from.</param>
|
|
/// <param name="rightKey">The index of the key to interpolate to.</param>
|
|
public abstract void FindKeys(float time, out int leftKey, out int rightKey);
|
|
|
|
/// <summary>
|
|
/// Wraps the time for the curve sampling with looping mode.
|
|
/// </summary>
|
|
/// <param name="time">The time to wrap.</param>
|
|
/// <param name="start">The start time.</param>
|
|
/// <param name="end">The end time.</param>
|
|
/// <param name="loop">If set to <c>true</c> loops the curve.</param>
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// An animation spline represented by a set of keyframes, each representing an endpoint of a linear curve.
|
|
/// </summary>
|
|
/// <typeparam name="T">The animated value type.</typeparam>
|
|
public class LinearCurve<T> : CurveBase<T> where T : new()
|
|
{
|
|
/// <summary>
|
|
/// A single keyframe that can be injected into linear curve.
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct Keyframe : IComparable, IComparable<Keyframe>
|
|
{
|
|
/// <summary>
|
|
/// The time of the keyframe.
|
|
/// </summary>
|
|
[EditorOrder(0), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("The time of the keyframe.")]
|
|
public float Time;
|
|
|
|
/// <summary>
|
|
/// The value of the curve at keyframe.
|
|
/// </summary>
|
|
[EditorOrder(1), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("The value of the curve at keyframe.")]
|
|
public T Value;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Keyframe"/> struct.
|
|
/// </summary>
|
|
/// <param name="time">The time.</param>
|
|
/// <param name="value">The value.</param>
|
|
public Keyframe(float time, T value)
|
|
{
|
|
Time = time;
|
|
Value = value;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public int CompareTo(object obj)
|
|
{
|
|
if (obj is Keyframe other)
|
|
return Time > other.Time ? 1 : 0;
|
|
return 1;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public int CompareTo(Keyframe other)
|
|
{
|
|
return Time > other.Time ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The keyframes collection. Can be directly modified but ensure to sort it after editing so keyframes are organized by ascending time value.
|
|
/// </summary>
|
|
[Serialize]
|
|
public Keyframe[] Keyframes;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="LinearCurve{T}"/> class.
|
|
/// </summary>
|
|
public LinearCurve()
|
|
{
|
|
Keyframes = Utils.GetEmptyArray<Keyframe>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="LinearCurve{T}"/> class.
|
|
/// </summary>
|
|
/// <param name="keyframes">The keyframes.</param>
|
|
public LinearCurve(params Keyframe[] keyframes)
|
|
{
|
|
Keyframes = keyframes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="LinearCurve{T}"/> class.
|
|
/// </summary>
|
|
/// <param name="keyframes">The keyframes.</param>
|
|
public LinearCurve(IEnumerable<Keyframe> keyframes)
|
|
{
|
|
Keyframes = keyframes.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluates the animation curve key at the specified time.
|
|
/// </summary>
|
|
/// <param name="result">The interpolated key from the curve at provided time.</param>
|
|
/// <param name="time">The time to evaluate the curve at.</param>
|
|
/// <param name="loop">If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.</param>
|
|
public void EvaluateKey(out Keyframe result, float time, bool loop = true)
|
|
{
|
|
if (Keyframes.Length == 0)
|
|
{
|
|
result = new Keyframe(time, default);
|
|
return;
|
|
}
|
|
|
|
float start = 0;
|
|
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);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Evaluate(out T result, float time, bool loop = true)
|
|
{
|
|
if (Keyframes.Length == 0)
|
|
{
|
|
result = default;
|
|
return;
|
|
}
|
|
|
|
float start = 0;
|
|
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);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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<Keyframe>();
|
|
return;
|
|
}
|
|
|
|
var result = new List<Keyframe>(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;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void TransformTime(float timeScale, float timeOffset)
|
|
{
|
|
for (int i = 0; i < Keyframes.Length; i++)
|
|
Keyframes[i].Time = Keyframes[i].Time * timeScale + timeOffset;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// An animation spline represented by a set of keyframes, each representing an endpoint of a Bezier curve.
|
|
/// </summary>
|
|
/// <typeparam name="T">The animated value type.</typeparam>
|
|
public class BezierCurve<T> : CurveBase<T> where T : new()
|
|
{
|
|
/// <summary>
|
|
/// A single keyframe that can be injected into Bezier curve.
|
|
/// </summary>
|
|
[StructLayout(LayoutKind.Sequential)]
|
|
public struct Keyframe : IComparable, IComparable<Keyframe>
|
|
{
|
|
/// <summary>
|
|
/// The time of the keyframe.
|
|
/// </summary>
|
|
[EditorOrder(0), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("The time of the keyframe.")]
|
|
public float Time;
|
|
|
|
/// <summary>
|
|
/// The value of the curve at keyframe.
|
|
/// </summary>
|
|
[EditorOrder(1), Limit(float.MinValue, float.MaxValue, 0.01f), Tooltip("The value of the curve at keyframe.")]
|
|
public T Value;
|
|
|
|
/// <summary>
|
|
/// The input tangent (going from the previous key to this one) of the key.
|
|
/// </summary>
|
|
[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;
|
|
|
|
/// <summary>
|
|
/// The output tangent (going from this key to next one) of the key.
|
|
/// </summary>
|
|
[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;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Keyframe"/> struct.
|
|
/// </summary>
|
|
/// <param name="time">The time.</param>
|
|
/// <param name="value">The value.</param>
|
|
public Keyframe(float time, T value)
|
|
{
|
|
Time = time;
|
|
Value = value;
|
|
TangentIn = TangentOut = default;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="Keyframe"/> struct.
|
|
/// </summary>
|
|
/// <param name="time">The time.</param>
|
|
/// <param name="value">The value.</param>
|
|
/// <param name="tangentIn">The start tangent.</param>
|
|
/// <param name="tangentOut">The end tangent.</param>
|
|
public Keyframe(float time, T value, T tangentIn, T tangentOut)
|
|
{
|
|
Time = time;
|
|
Value = value;
|
|
TangentIn = tangentIn;
|
|
TangentOut = tangentOut;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public int CompareTo(object obj)
|
|
{
|
|
if (obj is Keyframe other)
|
|
return Time > other.Time ? 1 : 0;
|
|
return 1;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public int CompareTo(Keyframe other)
|
|
{
|
|
return Time > other.Time ? 1 : 0;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// The keyframes collection. Can be directly modified but ensure to sort it after editing so keyframes are organized by ascending time value.
|
|
/// </summary>
|
|
[Serialize]
|
|
public Keyframe[] Keyframes;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="BezierCurve{T}"/> class.
|
|
/// </summary>
|
|
public BezierCurve()
|
|
{
|
|
Keyframes = Utils.GetEmptyArray<Keyframe>();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="BezierCurve{T}"/> class.
|
|
/// </summary>
|
|
/// <param name="keyframes">The keyframes.</param>
|
|
public BezierCurve(params Keyframe[] keyframes)
|
|
{
|
|
Keyframes = keyframes;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of the <see cref="BezierCurve{T}"/> class.
|
|
/// </summary>
|
|
/// <param name="keyframes">The keyframes.</param>
|
|
public BezierCurve(IEnumerable<Keyframe> keyframes)
|
|
{
|
|
Keyframes = keyframes.ToArray();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Evaluates the animation curve key at the specified time.
|
|
/// </summary>
|
|
/// <param name="result">The interpolated key from the curve at provided time.</param>
|
|
/// <param name="time">The time to evaluate the curve at.</param>
|
|
/// <param name="loop">If true the curve will loop when it goes past the end or beginning. Otherwise the curve value will be clamped.</param>
|
|
public void EvaluateKey(out Keyframe result, float time, bool loop = true)
|
|
{
|
|
if (Keyframes.Length == 0)
|
|
{
|
|
result = new Keyframe(time, default);
|
|
return;
|
|
}
|
|
|
|
float start = 0;
|
|
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;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void Evaluate(out T result, float time, bool loop = true)
|
|
{
|
|
if (Keyframes.Length == 0)
|
|
{
|
|
result = default;
|
|
return;
|
|
}
|
|
|
|
float start = 0;
|
|
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);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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<Keyframe>();
|
|
return;
|
|
}
|
|
|
|
var result = new List<Keyframe>(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;
|
|
}
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public override void TransformTime(float timeScale, float timeOffset)
|
|
{
|
|
for (int i = 0; i < Keyframes.Length; i++)
|
|
Keyframes[i].Time = Keyframes[i].Time * timeScale + timeOffset;
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
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);
|
|
}
|
|
}
|
|
}
|