添加对 Core 的直接引用

This commit is contained in:
OLDREDSTONE
2025-03-25 15:49:57 +08:00
parent 24907abedb
commit 06c19bf10a
296 changed files with 24961 additions and 2 deletions
+282
View File
@@ -0,0 +1,282 @@
namespace RhythmBase.Components.Easing
{
///<summary>
/// The EaseType enumeration represents various types of easing functions.
/// These functions are used to create smooth transitions in animations.
///</summary>
public enum EaseType
{
/// <summary>
/// Unset.
/// </summary>
Unset = -1,
///<summary>
///Ease Linear.
///</summary>
Linear,
///<summary>
///Ease InSine.
///</summary>
InSine,
///<summary>
///Ease OutSine.
///</summary>
OutSine,
///<summary>
///Ease InOutSine.
///</summary>
InOutSine,
///<summary>
///Ease InQuad.
///</summary>
InQuad,
///<summary>
///Ease OutQuad.
///</summary>
OutQuad,
///<summary>
///Ease InOutQuad.
///</summary>
InOutQuad,
///<summary>
///Ease InCubic.
///</summary>
InCubic,
///<summary>
///Ease OutCubic.
///</summary>
OutCubic,
///<summary>
///Ease InOutCubic.
///</summary>
InOutCubic,
///<summary>
///Ease InQuart.
///</summary>
InQuart,
///<summary>
///Ease OutQuart.
///</summary>
OutQuart,
///<summary>
///Ease InOutQuart.
///</summary>
InOutQuart,
///<summary>
///Ease InQuint.
///</summary>
InQuint,
///<summary>
///Ease OutQuint.
///</summary>
OutQuint,
///<summary>
///Ease InOutQuint.
///</summary>
InOutQuint,
///<summary>
///Ease InExpo.
///</summary>
InExpo,
///<summary>
///Ease OutExpo.
///</summary>
OutExpo,
///<summary>
///Ease InOutExpo.
///</summary>
InOutExpo,
///<summary>
///Ease InCirc.
///</summary>
InCirc,
///<summary>
///Ease OutCirc.
///</summary>
OutCirc,
///<summary>
///Ease InOutCirc.
///</summary>
InOutCirc,
///<summary>
///Ease InElastic.
///</summary>
InElastic,
///<summary>
///Ease OutElastic.
///</summary>
OutElastic,
///<summary>
///Ease InOutElastic.
///</summary>
InOutElastic,
///<summary>
///Ease InBack.
///</summary>
InBack,
///<summary>
///Ease OutBack.
///</summary>
OutBack,
///<summary>
///Ease InOutBack.
///</summary>
InOutBack,
///<summary>
///Ease InBounce.
///</summary>
InBounce,
///<summary>
///Ease OutBounce.
///</summary>
OutBounce,
///<summary>
///Ease InOutBounce.
///</summary>
InOutBounce,
///<summary>
///Ease SmoothStep.
///</summary>
SmoothStep,
}
/// <summary>
/// Ease Calculate module.
/// </summary>
public static class Ease
{
/// <summary>
/// Calculates the value with the specified ease type.
/// </summary>
/// <param name="type">Ease type.</param>
/// <param name="x">A doubleing-point number in the range of 0 to 1.</param>
/// <returns>Easing result.</returns>
public static double Calculate(this EaseType type, double x) => type switch
{
EaseType.Unset => EaseFunction.None(x),
EaseType.Linear => EaseFunction.Linear(x),
EaseType.InSine => EaseFunction.InSine(x),
EaseType.OutSine => EaseFunction.OutSine(x),
EaseType.InOutSine => EaseFunction.InOutSine(x),
EaseType.InQuad => EaseFunction.InQuad(x),
EaseType.OutQuad => EaseFunction.OutQuad(x),
EaseType.InOutQuad => EaseFunction.InOutQuad(x),
EaseType.InCubic => EaseFunction.InCubic(x),
EaseType.OutCubic => EaseFunction.OutCubic(x),
EaseType.InOutCubic => EaseFunction.InOutCubic(x),
EaseType.InQuart => EaseFunction.InQuart(x),
EaseType.OutQuart => EaseFunction.OutQuart(x),
EaseType.InOutQuart => EaseFunction.InOutQuart(x),
EaseType.InQuint => EaseFunction.InQuint(x),
EaseType.OutQuint => EaseFunction.OutQuint(x),
EaseType.InOutQuint => EaseFunction.InOutQuint(x),
EaseType.InExpo => EaseFunction.InExpo(x),
EaseType.OutExpo => EaseFunction.OutExpo(x),
EaseType.InOutExpo => EaseFunction.InOutExpo(x),
EaseType.InCirc => EaseFunction.InCirc(x),
EaseType.OutCirc => EaseFunction.OutCirc(x),
EaseType.InOutCirc => EaseFunction.InOutCirc(x),
EaseType.InElastic => EaseFunction.InElastic(x),
EaseType.OutElastic => EaseFunction.OutElastic(x),
EaseType.InOutElastic => EaseFunction.InOutElastic(x),
EaseType.InBack => EaseFunction.InBack(x),
EaseType.OutBack => EaseFunction.OutBack(x),
EaseType.InOutBack => EaseFunction.InOutBack(x),
EaseType.InBounce => EaseFunction.InBounce(x),
EaseType.OutBounce => EaseFunction.OutBounce(x),
EaseType.InOutBounce => EaseFunction.InOutBounce(x),
EaseType.SmoothStep => EaseFunction.SmoothStep(x),
_ => 0,
};
/// <summary>
/// Calculates the value with the specified ease type.
/// </summary>
/// <param name="Type">Ease type.</param>
/// <param name="x">A doubleing-point number in the range of 0 to 1.</param>
/// <param name="from">The starting value of the easing result.</param>
/// <param name="to">The endding value of the easing result</param>
/// <returns>Easing result.</returns>
public static double Calculate(this EaseType Type, double x, double from, double to) => Type.Calculate(x) * (to - from) + from;
/// <summary>
/// Ease types.
/// </summary>
private static class EaseFunction
{
private const double c1 = 1.525;
private const double c2 = 1.70158;
public static double None(double x) => 0;
public static double Linear(double x) => x;
public static double InSine(double x) => 1 - Math.Cos(x * double.Pi / 2);
public static double OutSine(double x) => Math.Sin(x * double.Pi / 2);
public static double InOutSine(double x) => -(Math.Cos(x * double.Pi) - 1) / 2;
public static double InQuad(double x) => Math.Pow(x, 2);
public static double OutQuad(double x) => 1 - Math.Pow(1 - x, 2);
public static double InOutQuad(double x) => x < 0.5 ? 2 * Math.Pow(x, 2) : 1 - Math.Pow(-2 * x + 2, 2) / 2;
public static double InCubic(double x) => Math.Pow(x, 3);
public static double OutCubic(double x) => 1 - Math.Pow(1 - x, 3);
public static double InOutCubic(double x) => x < 0.5 ? 4 * Math.Pow(x, 3) : 1 - Math.Pow(-2 * x + 2, 3) / 2;
public static double InQuart(double x) => Math.Pow(x, 4);
public static double OutQuart(double x) => 1 - Math.Pow(1 - x, 4);
public static double InOutQuart(double x) => x < 0.5 ? 8 * Math.Pow(x, 4) : 1 - Math.Pow(-2 * x + 2, 4) / 2;
public static double InQuint(double x) => Math.Pow(x, 5);
public static double OutQuint(double x) => 1 - Math.Pow(1 - x, 5);
public static double InOutQuint(double x) => x < 0.5 ? 16 * Math.Pow(x, 5) : 1 - Math.Pow(-2 * x + 2, 5) / 2;
public static double InExpo(double x) => x == 0 ? 0 : Math.Pow(2, 10 * x - 10);
public static double OutExpo(double x) => x == 1 ? 1 : 1 - Math.Pow(2, -10 * x);
public static double InOutExpo(double x) => x == 0
? 0
: x == 1
? 1
: x < 0.5 ? Math.Pow(2, 20 * x - 10) / 2
: (2 - Math.Pow(2, -20 * x + 10)) / 2;
public static double InCirc(double x) => 1 - Math.Sqrt(1 - Math.Pow(x, 2));
public static double OutCirc(double x) => Math.Sqrt(1 - Math.Pow(x - 1, 2));
public static double InOutCirc(double x) => x < 0.5
? (1 - Math.Sqrt(1 - Math.Pow(2 * x, 2))) / 2
: (Math.Sqrt(1 - Math.Pow(-2 * x + 2, 2)) + 1) / 2;
public static double InElastic(double x) => x == 0
? 0
: x == 1
? 1
: -Math.Pow(2, 10 * x - 10) * Math.Sin((x * 10 - 10.75) * (2 * double.Pi / 3));
public static double OutElastic(double x) => x == 0
? 0
: x == 1
? 1
: Math.Pow(2, -10 * x) * Math.Sin((x * 10 - 0.75) * (2 * double.Pi / 3)) + 1;
public static double InOutElastic(double x) => x == 0
? 0
: x == 1
? 1
: x < 0.5
? -(Math.Pow(2, 20 * x - 10) * Math.Sin((20 * x - 11.125) * (2 * double.Pi / 4.5))) / 2
: Math.Pow(2, -20 * x + 10) * Math.Sin((20 * x - 11.125) * (2 * double.Pi / 4.5)) / 2 + 1;
public static double InBack(double x) => (c2 + 1) * x * x * x - c2 * x * x;
public static double OutBack(double x) => 1 + (c2 + 1) * Math.Pow(x - 1, 3) + c2 * Math.Pow(x - 1, 2);
public static double InOutBack(double x) => x < 0.5
? Math.Pow(2 * x, 2) * ((c2 * c1 + 1) * 2 * x - c2 * c1) / 2
: (Math.Pow(2 * x - 2, 2) * ((c2 * c1 + 1) * (x * 2 - 2) + c2 * c1) + 2) / 2;
public static double InBounce(double x) =>
1 - OutBounce(1 - x);
public static double OutBounce(double x)
{
const double n1 = 7.5625;
const double d1 = 2.75;
return x switch
{
< 1 / d1 => n1 * x * x,
< 2 / d1 => n1 * (x -= 1.5 / d1) * x + 0.75,
< 2.5 / d1 => n1 * (x -= 2.25 / d1) * x + 0.9375,
_ => n1 * (x -= 2.625 / d1) * x + 0.984375,
};
}
public static double InOutBounce(double x) =>
x < 0.5
? (1 - OutBounce(1 - 2 * x)) / 2
: (1 + OutBounce(2 * x - 1)) / 2;
public static double SmoothStep(double x) =>
(3 - 2 * x) * Math.Pow(x, 2);
}
}
}
@@ -0,0 +1,29 @@
namespace RhythmBase.Components.Easing
{
/// <summary>
/// Represents a node in the easing process.
/// </summary>
/// <param name="target">The target value of the easing node.</param>
public struct EaseNode(float target)
{
/// <summary>
/// Gets or sets the time at which the easing node starts.
/// </summary>
public float Time { get; set; } = 0;
/// <summary>
/// Gets or sets the target value of the easing node.
/// </summary>
public float Target { get; set; } = target;
/// <summary>
/// Gets or sets the duration of the easing node.
/// </summary>
public float Duration { get; set; } = 0;
/// <summary>
/// Gets or sets the type of easing to be applied.
/// </summary>
public EaseType Type { get; set; } = EaseType.Linear;
}
}
@@ -0,0 +1,10 @@
namespace RhythmBase.Components.Easing
{
/// <summary>
/// An attribute to mark properties for easing functions.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class EasePropertyAttribute : Attribute
{
}
}
@@ -0,0 +1,63 @@
using RhythmBase.Events;
using System.Reflection;
namespace RhythmBase.Components.Easing
{
/// <summary>
/// Represents an easing property with a color value.
/// </summary>
public class EasePropertyColor : IEaseProperty<RDColor>
{
private EaseValue _r;
private EaseValue _g;
private EaseValue _b;
private EaseValue _a;
/// <inheritdoc/>
public RDColor GetValue(RDBeat beat) => RDColor.FromRgba(
(byte)Math.Clamp(_r.GetValue(beat.BeatOnly), 0, 255),
(byte)Math.Clamp(_g.GetValue(beat.BeatOnly), 0, 255),
(byte)Math.Clamp(_b.GetValue(beat.BeatOnly), 0, 255),
(byte)Math.Clamp(_a.GetValue(beat.BeatOnly), 0, 255));
/// <inheritdoc/>
public static bool CanConvert(object data) => data is RDColor;
/// <inheritdoc/>
public static EaseNode?[] Convert(IEaseEvent data, PropertyInfo property)
{
RDColor? value = (RDColor?)property.GetValue(data);
return [
value?.R is byte vr ? new(vr) {
Duration = data.Duration,
Time = ((BaseEvent)data).Beat.BeatOnly,
Type = data.Ease
} : null,
value?.G is byte vg ? new(vg) {
Duration = data.Duration,
Time = ((BaseEvent)data).Beat.BeatOnly,
Type = data.Ease
} : null,
value?.B is byte vb ? new(vb) {
Duration = data.Duration,
Time = ((BaseEvent)data).Beat.BeatOnly,
Type = data.Ease
} : null,
value?.A is byte va ? new(va) {
Duration = data.Duration,
Time = ((BaseEvent)data).Beat.BeatOnly,
Type = data.Ease
} : null,
];
}
/// <inheritdoc/>
public static IEaseProperty<RDColor> CreateEaseProperty(RDColor originalValue, IEaseEvent[] data, PropertyInfo property)
{
EaseNode?[][] nodes = [.. data.Select(d => Convert(d, property))];
return new EasePropertyColor()
{
_r = new(originalValue.R, [.. nodes.Select(i => i[0]).Where(i => i is not null).Cast<EaseNode>()]),
_g = new(originalValue.G, [.. nodes.Select(i => i[1]).Where(i => i is not null).Cast<EaseNode>()]),
_b = new(originalValue.B, [.. nodes.Select(i => i[2]).Where(i => i is not null).Cast<EaseNode>()]),
_a = new(originalValue.A, [.. nodes.Select(i => i[3]).Where(i => i is not null).Cast<EaseNode>()])
};
}
}
}
@@ -0,0 +1,38 @@
using RhythmBase.Events;
using System.Reflection;
namespace RhythmBase.Components.Easing
{
/// <summary>
/// Represents an easing property with a float value.
/// </summary>
public class EasePropertyFloat : IEaseProperty<float>
{
private EaseValue _value;
/// <inheritdoc/>
public float GetValue(RDBeat beat) => _value.GetValue(beat.BeatOnly);
/// <inheritdoc/>
public static bool CanConvert(object data) => data is float;
/// <inheritdoc/>
public static EaseNode?[] Convert(IEaseEvent data, PropertyInfo property)
{
RDExpression? value = (RDExpression?)property.GetValue(data);
return [
value is RDExpression v? new EaseNode(v.Value) {
Duration = data.Duration,
Time = ((BaseEvent)data).Beat.BeatOnly,
Type = data.Ease
} : null,
];
}
/// <inheritdoc/>
public static IEaseProperty<float> CreateEaseProperty(float originalValue, IEaseEvent[] data, PropertyInfo property)
{
EaseNode?[][] nodes = [.. data.Select(d => Convert(d, property))];
return new EasePropertyFloat()
{
_value = new(originalValue, [.. nodes.Select(i => i[0]).Where(i => i is not null).Cast<EaseNode>()])
};
}
}
}
@@ -0,0 +1,45 @@
using RhythmBase.Events;
using System.Reflection;
namespace RhythmBase.Components.Easing
{
/// <summary>
/// Represents an easing property with a point value.
/// </summary>
public class EasePropertyPoint : IEaseProperty<RDPointN>
{
private EaseValue _x;
private EaseValue _y;
/// <inheritdoc/>
public RDPointN GetValue(RDBeat beat) => new(_x.GetValue(beat.BeatOnly), _y.GetValue(beat.BeatOnly));
/// <inheritdoc/>
public static bool CanConvert(object data) => data is RDPoint;
/// <inheritdoc/>
public static EaseNode?[] Convert(IEaseEvent data, PropertyInfo property)
{
RDPointE? value = (RDPointE?)property.GetValue(data);
return [
value?.X?.Value is float vx ? new(vx) {
Duration = data.Duration,
Time = ((BaseEvent)data).Beat.BeatOnly,
Type = data.Ease
} : null,
value?.Y?.Value is float vy ? new(vy) {
Duration = data.Duration,
Time = ((BaseEvent)data).Beat.BeatOnly,
Type = data.Ease
} : null,
];
}
/// <inheritdoc/>
public static IEaseProperty<RDPointN> CreateEaseProperty(RDPointN originalValue, IEaseEvent[] data, PropertyInfo property)
{
EaseNode?[][] nodes = [.. data.Select(d => Convert(d, property))];
return new EasePropertyPoint()
{
_x = new(originalValue.X, [.. nodes.Select(i => i[0]).Where(i => i is not null).Cast<EaseNode>()]),
_y = new(originalValue.Y, [.. nodes.Select(i => i[1]).Where(i => i is not null).Cast<EaseNode>()])
};
}
}
}
@@ -0,0 +1,45 @@
using RhythmBase.Events;
using System.Reflection;
namespace RhythmBase.Components.Easing
{
/// <summary>
/// Represents an easing property with a size value.
/// </summary>
public class EasePropertySize : IEaseProperty<RDSizeN>
{
private EaseValue _width;
private EaseValue _height;
/// <inheritdoc/>
public RDSizeN GetValue(RDBeat beat) => new(_width.GetValue(beat.BeatOnly), _height.GetValue(beat.BeatOnly));
/// <inheritdoc/>
public static bool CanConvert(object data) => data is RDSize;
/// <inheritdoc/>
public static EaseNode?[] Convert(IEaseEvent data, PropertyInfo property)
{
RDSizeE? value = (RDSizeE?)property.GetValue(data);
return [
value?.Width?.Value is float vx ? new(vx) {
Duration = data.Duration,
Time = ((BaseEvent)data).Beat.BeatOnly,
Type = data.Ease
} : null,
value?.Height?.Value is float vy ? new(vy) {
Duration = data.Duration,
Time = ((BaseEvent)data).Beat.BeatOnly,
Type = data.Ease
} : null,
];
}
/// <inheritdoc/>
public static IEaseProperty<RDSizeN> CreateEaseProperty(RDSizeN originalValue, IEaseEvent[] data, PropertyInfo property)
{
EaseNode?[][] nodes = [.. data.Select(d => Convert(d, property))];
return new EasePropertySize()
{
_width = new(originalValue.Width, [.. nodes.Select(i => i[0]).Where(i => i is not null).Cast<EaseNode>()]),
_height = new(originalValue.Height, [.. nodes.Select(i => i[1]).Where(i => i is not null).Cast<EaseNode>()])
};
}
}
}
@@ -0,0 +1,180 @@
using System.Data;
namespace RhythmBase.Components.Easing
{
/// <summary>
/// Represents a value that changes over time according to a series of easing nodes.
/// </summary>
/// <remarks>
/// Initializes a new instance of the <see cref="EaseValue"/> class with the specified original value and easing nodes.
/// </remarks>
/// <param name="originalValue">The original value before any easing is applied.</param>
/// <param name="nodes">The collection of easing nodes that define how the value changes over time.</param>
public struct EaseValue(float originalValue, IEnumerable<EaseNode> nodes)
{
/// <summary>
/// Gets or sets the collection of easing nodes, ordered by their start time.
/// </summary>
public EaseNode[] Nodes { get; set; } = [.. nodes.OrderBy(x => x.Time)];
/// <summary>
/// Gets or sets the original value before any easing is applied.
/// </summary>
public float OriginalValue { get; set; } = originalValue;
/// <summary>
/// Gets the value at the specified time, taking into account the easing nodes.
/// </summary>
/// <param name="time">The time at which to get the value.</param>
/// <returns>The value at the specified time.</returns>
public readonly float GetValue(float time)
{
if (Nodes.Length == 0 || time <= Nodes[0].Time)
return OriginalValue;
int i = Nodes.Length - 1;
while (i >= 0 && time <= Nodes[i].Time)
i--;
if (Nodes[i].Time + Nodes[i].Duration <= time)
return Nodes[i].Target;
Stack<EaseNode> ns = new([Nodes[i]]);
while (--i > -1)
{
if (IsInRange(Nodes[i], ns.Peek().Time))
{
ns.Push(Nodes[i]);
}
}
float origin = i == -1 ? OriginalValue : Nodes[i + 1].Target;
while (ns.Count > 1)
{
EaseNode node = ns.Pop();
origin = GetValue(node, ns.Peek().Time, origin);
}
origin = GetValue(ns.Pop(), time, origin);
return origin;
}
/// <summary>
/// Determines whether the specified time is within the range of the easing node.
/// </summary>
/// <param name="node">The easing node to check.</param>
/// <param name="time">The time to check.</param>
/// <returns><c>true</c> if the time is within the range of the easing node; otherwise, <c>false</c>.</returns>
private static bool IsInRange(EaseNode node, float time) => node.Time <= time && time <= node.Time + node.Duration;
/// <summary>
/// Calculates the value of the easing node at the specified time.
/// </summary>
/// <param name="node">The easing node to calculate the value for.</param>
/// <param name="time">The time at which to calculate the value.</param>
/// <param name="origin">The original value before any easing is applied.</param>
/// <returns>The calculated value at the specified time.</returns>
private static float GetValue(EaseNode node, float time, float origin) => node.Duration == 0 ? node.Target : (float)node.Type.Calculate((time - node.Time) / node.Duration, origin, node.Target);
/// <summary>
/// Fits the data points to an easing function with the specified precision.
/// </summary>
/// <param name="originalValue">The original value before any easing is applied.</param>
/// <param name="values">The array of time and value pairs to fit.</param>
/// <param name="precision">The precision for fitting the data points.</param>
/// <returns>An <see cref="EaseValue"/> instance that fits the data points.</returns>
public static EaseValue Fit(float originalValue, (float time, float value)[] values, float precision = 3f) => Fit(originalValue, values, eases, precision);
/// <summary>
/// Fits the data points to an easing function with the specified precision.
/// </summary>
/// <param name="originalValue">The original value before any easing is applied.</param>
/// <param name="values">The array of time and value pairs to fit.</param>
/// <param name="precision">The precision for fitting the data points.</param>
/// <param name="easeTypes">The array of easing types to consider.</param>
/// <returns>An <see cref="EaseValue"/> instance that fits the data points.</returns>
public static EaseValue Fit(float originalValue, (float time, float value)[] values, EaseType[] easeTypes, float precision = 3f)
{
values = [.. values.OrderBy(x => x.time)];
if (values.Length == 0)
return new EaseValue(originalValue, []);
if (values.Length == 1)
return new EaseValue(originalValue, [new EaseNode(values[0].value) { Duration = values[0].time, Type = EaseType.InOutSine }]);
if (values.Length == 2)
return new EaseValue(originalValue, [new EaseNode(values[0].value) { Time = values[0].time, Duration = values[1].time, Type = EaseType.InOutSine }]);
return Fit([(0, originalValue), .. values], easeTypes, precision);
}
/// <summary>
/// Fits the data points to an easing function with the specified precision.
/// </summary>
/// <param name="values">The array of time and value pairs to fit.</param>
/// <param name="precision">The precision for fitting the data points.</param>
/// <returns>An <see cref="EaseValue"/> instance that fits the data points.</returns>
public static EaseValue Fit((float time, float value)[] values, float precision = 3f) => Fit(values, eases, precision);
/// <summary>
/// Fits the data points to an easing function with the specified precision and ease types.
/// </summary>
/// <param name="values">The array of time and value pairs to fit.</param>
/// <param name="easeTypes">The array of easing types to consider.</param>
/// <param name="precision">The precision for fitting the data points.</param>
/// <remarks>Special thanks to <b>mfgujhgh</b> for the algorithm!</remarks>
/// <returns>An <see cref="EaseValue"/> instance that fits the data points.</returns>
public static EaseValue Fit((float time, float value)[] values, EaseType[] easeTypes, float precision = 3f)
{
if (easeTypes.Length == 0)
throw new ArgumentException("At least one ease type must be specified.", nameof(easeTypes));
values = [.. values.OrderBy(x => x.time)];
int[] steps = new int[values.Length],
p = new int[values.Length];
EaseType[] pe = new EaseType[values.Length];
steps[0] = 0;
for (int i = 1; i < values.Length; i++)
{
steps[i] = steps[i - 1] + 1;
p[i] = i - 1;
pe[i] = easeTypes[0];
for (int j = 0; j < i - 1; j++)
{
if (steps[j] + 1 >= steps[i])
continue;
EaseType besttype = easeTypes[0];
float bests = float.MaxValue;
foreach (var e in easeTypes)
{
float s = Check(values, j, i, e, precision);
if (s < bests)
{
bests = s;
besttype = e;
}
}
if (bests < precision && steps[i] > steps[j] + 1)
{
steps[i] = steps[j] + 1;
p[i] = j;
pe[i] = besttype;
}
}
}
EaseNode[] ns = new EaseNode[steps[^1]];
int k = values.Length - 1;
while (k != 0)
{
ns[--steps[k]] = new EaseNode(values[k].value)
{
Time = values[p[k]].time,
Duration = values[k].time - values[p[k]].time,
Type = pe[k]
};
k = p[k];
}
return new EaseValue(values[0].value, ns);
}
private static float Check((float time, float value)[] data, int start, int end, EaseType type, float precision)
{
float s = 0;
for (int i = start + 1; i <= end; i++)
{
float v = data[start].value
+ (float)type.Calculate((data[i].time - data[start].time) / (data[end].time - data[start].time))
* (data[end].value - data[start].value);
s = Math.Max(s, Math.Abs(v - data[i].value));
if (s > precision)
{
return s;
}
}
return s;
}
private static readonly EaseType[] eases = Enum.GetValues<EaseType>();
}
}
@@ -0,0 +1,54 @@
using RhythmBase.Events;
using System.Reflection;
namespace RhythmBase.Components.Easing
{
/// <summary>
/// Represents an easing property.
/// </summary>
public interface IEaseProperty
{
}
/// <summary>
/// Represents an easing property with a specific value type.
/// </summary>
/// <typeparam name="TValue">The type of the value.</typeparam>
public interface IEaseProperty<TValue> : IEaseProperty where TValue : new()
{
/// <summary>
/// Gets the type of the value.
/// </summary>
static Type Type => typeof(TValue);
/// <summary>
/// Gets the value at the specified beat.
/// </summary>
/// <param name="beat">The beat at which to get the value.</param>
/// <returns>The value at the specified beat.</returns>
abstract TValue GetValue(RDBeat beat);
/// <summary>
/// Determines whether the specified data can be converted to the value type.
/// </summary>
/// <param name="data">The data to check.</param>
/// <returns><c>true</c> if the data can be converted; otherwise, <c>false</c>.</returns>
static abstract bool CanConvert(object data);
/// <summary>
/// Converts the specified easing event data to an array of easing nodes.
/// </summary>
/// <param name="data">The easing event data to convert.</param>
/// <param name="property">The property information of the easing event.</param>
/// <returns>An array of easing nodes.</returns>
static abstract EaseNode?[] Convert(IEaseEvent data, PropertyInfo property);
/// <summary>
/// Creates an easing property with the specified original value and easing event data.
/// </summary>
/// <param name="originalValue">The original value before any easing is applied.</param>
/// <param name="data">The array of easing event data.</param>
/// <param name="property">The property information of the easing event.</param>
/// <returns>An easing property instance.</returns>
static abstract IEaseProperty<TValue> CreateEaseProperty(TValue originalValue, IEaseEvent[] data, PropertyInfo property);
}
}