Archived
forked from RDTKEditor/RDTKEditor
添加对 Core 的直接引用
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user