Implemented basic keyframes

This commit is contained in:
Robert committed 2020-01-09 21:13:29 +01:00
1 parent e8570a6dd9
commit 0958c3af9f
41 files changed
+951 -123

No files matched your search

+1
View File
@@ -212,6 +212,7 @@
<Compile Include="Services\Storage\ProfileService.cs" />
<Compile Include="Services\Storage\Interfaces\ISurfaceService.cs" />
<Compile Include="Services\Storage\SurfaceService.cs" />
<Compile Include="Utilities\Easings.cs" />
<Compile Include="Utilities\EnumUtilities.cs" />
</ItemGroup>
<ItemGroup>
@@ -9,10 +9,13 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
{
public sealed override List<Type> CompatibleTypes { get; } = new List<Type> {typeof(float)};
public override object GetCurrentValue()
protected override object GetInterpolatedValue()
{
// Nothing fancy for now, just return the base value
return ((LayerProperty<float>) LayerProperty).Value;
var currentKeyframe = (Keyframe<float>) CurrentKeyframe;
var nextKeyframe = (Keyframe<float>) NextKeyframe;
var diff = nextKeyframe.Value - currentKeyframe.Value;
return currentKeyframe.Value + diff * KeyframeProgress;
}
}
}
@@ -9,10 +9,13 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
{
public sealed override List<Type> CompatibleTypes { get; } = new List<Type> {typeof(int)};
public override object GetCurrentValue()
protected override object GetInterpolatedValue()
{
// Nothing fancy for now, just return the base value
return ((LayerProperty<int>) LayerProperty).Value;
var currentKeyframe = (Keyframe<int>) CurrentKeyframe;
var nextKeyframe = (Keyframe<int>) NextKeyframe;
var diff = nextKeyframe.Value - currentKeyframe.Value;
return currentKeyframe.Value + diff * KeyframeProgress;
}
}
}
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Artemis.Core.Exceptions;
using Artemis.Core.Models.Profile.LayerProperties;
@@ -15,12 +16,27 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
/// <summary>
/// The layer property this keyframe engine applies to.
/// </summary>
public BaseLayerProperty LayerProperty { get; set; }
public BaseLayerProperty LayerProperty { get; private set; }
/// <summary>
/// The keyframe progress in milliseconds.
/// The total progress
/// </summary>
public double Progress { get; set; }
public TimeSpan Progress { get; private set; }
/// <summary>
/// The progress from the current keyframe to the next 0 to 1
/// </summary>
public float KeyframeProgress { get; private set; }
/// <summary>
/// The current keyframe
/// </summary>
public BaseKeyframe CurrentKeyframe { get; private set; }
/// <summary>
/// The next keyframe
/// </summary>
public BaseKeyframe NextKeyframe { get; private set; }
/// <summary>
/// The types this keyframe engine supports.
@@ -39,6 +55,7 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
throw new ArtemisCoreException($"This property engine does not support the provided type {layerProperty.Type.Name}");
LayerProperty = layerProperty;
LayerProperty.KeyframeEngine = this;
Initialized = true;
}
@@ -51,15 +68,52 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
if (!Initialized)
return;
Progress += deltaTime;
Progress = Progress.Add(TimeSpan.FromMilliseconds(deltaTime));
// TODO Keep them sorted somewhere else, iterating all keyframes multiple times sucks
var sortedKeyframes = LayerProperty.UntypedKeyframes.ToList().OrderBy(k => k.Position).ToList();
CurrentKeyframe = sortedKeyframes.LastOrDefault(k => k.Position <= Progress);
NextKeyframe = sortedKeyframes.FirstOrDefault(k => k.Position > Progress);
if (CurrentKeyframe == null)
KeyframeProgress = 0;
else if (NextKeyframe == null)
KeyframeProgress = 1;
else
{
var timeDiff = NextKeyframe.Position - CurrentKeyframe.Position;
KeyframeProgress = (float) ((Progress - CurrentKeyframe.Position).TotalMilliseconds / timeDiff.TotalMilliseconds);
}
// TODO Apply easing and store it separately
// LayerProperty determines what's next: reset, stop, continue
}
/// <summary>
/// Overrides the engine's progress to the provided value
/// </summary>
/// <param name="progress"></param>
public void OverrideProgress(TimeSpan progress)
{
Progress = TimeSpan.Zero;
Update(progress.TotalMilliseconds);
}
/// <summary>
/// Gets the current value, if the progress is in between two keyframes the value will be interpolated
/// </summary>
/// <returns></returns>
public abstract object GetCurrentValue();
public object GetCurrentValue()
{
if (CurrentKeyframe == null)
return LayerProperty.BaseValue;
if (NextKeyframe == null)
return CurrentKeyframe.BaseValue;
return GetInterpolatedValue();
}
protected abstract object GetInterpolatedValue();
}
}
@@ -10,10 +10,14 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
{
public sealed override List<Type> CompatibleTypes { get; } = new List<Type> {typeof(SKPoint)};
public override object GetCurrentValue()
protected override object GetInterpolatedValue()
{
// Nothing fancy for now, just return the base value
return ((LayerProperty<SKPoint>) LayerProperty).Value;
var currentKeyframe = (Keyframe<SKPoint>) CurrentKeyframe;
var nextKeyframe = (Keyframe<SKPoint>) NextKeyframe;
var xDiff = nextKeyframe.Value.X - currentKeyframe.Value.X;
var yDiff = nextKeyframe.Value.Y - currentKeyframe.Value.Y;
return new SKPoint(currentKeyframe.Value.X + xDiff * KeyframeProgress, currentKeyframe.Value.Y + yDiff * KeyframeProgress);
}
}
}
@@ -10,10 +10,14 @@ namespace Artemis.Core.Models.Profile.KeyframeEngines
{
public sealed override List<Type> CompatibleTypes { get; } = new List<Type> {typeof(SKSize)};
public override object GetCurrentValue()
protected override object GetInterpolatedValue()
{
// Nothing fancy for now, just return the base value
return ((LayerProperty<SKSize>) LayerProperty).Value;
var currentKeyframe = (Keyframe<SKSize>)CurrentKeyframe;
var nextKeyframe = (Keyframe<SKSize>)NextKeyframe;
var widthDiff = nextKeyframe.Value.Width - currentKeyframe.Value.Width;
var heightDiff = nextKeyframe.Value.Height - currentKeyframe.Value.Height;
return new SKSize(currentKeyframe.Value.Width + widthDiff * KeyframeProgress, currentKeyframe.Value.Height + heightDiff * KeyframeProgress);
}
}
}
@@ -4,7 +4,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
{
public class BaseKeyframe
{
protected internal BaseKeyframe(Layer layer, BaseLayerProperty property)
protected BaseKeyframe(Layer layer, BaseLayerProperty property)
{
Layer = layer;
BaseProperty = property;
@@ -12,7 +12,7 @@ namespace Artemis.Core.Models.Profile.LayerProperties
{
private object _baseValue;
protected internal BaseLayerProperty(Layer layer, BaseLayerProperty parent, string id, string name, string description, Type type)
protected BaseLayerProperty(Layer layer, BaseLayerProperty parent, string id, string name, string description, Type type)
{
Layer = layer;
Parent = parent;
@@ -125,7 +125,37 @@ namespace Artemis.Core.Models.Profile.LayerProperties
BaseKeyframes.Clear();
foreach (var keyframeEntity in propertyEntity.KeyframeEntities)
BaseKeyframes.Add(new BaseKeyframe(Layer, this) {BaseValue = DeserializePropertyValue(keyframeEntity.Value)});
{
// Create a strongly typed keyframe or else it cannot be cast later on
var keyframeType = typeof(Keyframe<>);
var keyframe = (BaseKeyframe) Activator.CreateInstance(keyframeType.MakeGenericType(Type), Layer, this);
keyframe.BaseValue = DeserializePropertyValue(keyframeEntity.Value);
BaseKeyframes.Add(keyframe);
}
}
/// <summary>
/// Creates a new keyframe for this base property without knowing the type
/// </summary>
/// <returns></returns>
public BaseKeyframe CreateNewKeyframe(TimeSpan position)
{
// Create a strongly typed keyframe or else it cannot be cast later on
var keyframeType = typeof(Keyframe<>);
var keyframe = (BaseKeyframe) Activator.CreateInstance(keyframeType.MakeGenericType(Type), Layer, this);
keyframe.Position = position;
keyframe.BaseValue = BaseValue;
BaseKeyframes.Add(keyframe);
return keyframe;
}
/// <summary>
/// Removes all keyframes from the property.
/// </summary>
public void ClearKeyframes()
{
BaseKeyframes.Clear();
}
public override string ToString()
@@ -18,6 +18,11 @@ namespace Artemis.Core.Models.Profile.LayerProperties
set => BaseValue = value;
}
/// <summary>
/// The value of the property with keyframes applied
/// </summary>
public T CurrentValue => (T) KeyframeEngine.GetCurrentValue();
/// <summary>
/// A list of keyframes defining different values of the property in time, this list contains the strongly typed
/// <see cref="Keyframe{T}" />
@@ -43,20 +48,12 @@ namespace Artemis.Core.Models.Profile.LayerProperties
}
/// <summary>
/// Removes all keyframes from the property.
/// </summary>
public void ClearKeyframes()
{
BaseKeyframes.Clear();
}
/// <summary>
/// Gets the current value using the keyframes
/// Gets the current value using the keyframes
/// </summary>
/// <returns></returns>
public T GetCurrentValue()
{
if (KeyframeEngine == null)
if (KeyframeEngine == null || !Keyframes.Any())
return Value;
return (T) KeyframeEngine.GetCurrentValue();
@@ -60,7 +60,7 @@ namespace Artemis.Core.Models.Profile.LayerShapes
Layer.SizeProperty.Value = new SKSize((float) (100f / width * rect.Width) / 100f, (float) (100f / height * rect.Height) / 100f);
// TODO: Update keyframes
CalculateRenderProperties(Layer.PositionProperty.Value, Layer.SizeProperty.Value);
CalculateRenderProperties(Layer.PositionProperty.CurrentValue, Layer.SizeProperty.CurrentValue);
}
public SKRect GetUnscaledRectangle()
@@ -74,10 +74,10 @@ namespace Artemis.Core.Models.Profile.LayerShapes
var height = Layer.Leds.Max(l => l.RgbLed.AbsoluteLedRectangle.Location.Y + l.RgbLed.AbsoluteLedRectangle.Size.Height) - y;
return SKRect.Create(
(float) (x + width * Layer.PositionProperty.Value.X),
(float) (y + height * Layer.PositionProperty.Value.Y),
(float) (width * Layer.SizeProperty.Value.Width),
(float) (height * Layer.SizeProperty.Value.Height)
(float) (x + width * Layer.PositionProperty.CurrentValue.X),
(float) (y + height * Layer.PositionProperty.CurrentValue.Y),
(float) (width * Layer.SizeProperty.CurrentValue.Width),
(float) (height * Layer.SizeProperty.CurrentValue.Height)
);
}
}
+10
View File
@@ -1,5 +1,6 @@
using System.IO;
using Artemis.Core.Exceptions;
using Artemis.Core.Models.Profile.KeyframeEngines;
using Artemis.Core.Plugins.Models;
using Artemis.Core.Services.Interfaces;
using Artemis.Storage.Repositories.Interfaces;
@@ -50,6 +51,15 @@ namespace Artemis.Core.Ninject
.Configure(c => c.InSingletonScope());
});
// Bind all keyframe engines
Kernel.Bind(x =>
{
x.FromAssemblyContaining<KeyframeEngine>()
.SelectAllClasses()
.InheritedFrom<KeyframeEngine>()
.BindAllBaseClasses();
});
Kernel.Bind<PluginSettings>().ToProvider<PluginSettingsProvider>();
Kernel.Bind<ILogger>().ToProvider<LoggerProvider>();
}
@@ -1,4 +1,6 @@
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.KeyframeEngines;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Plugins.LayerBrush;
namespace Artemis.Core.Services.Interfaces
@@ -14,5 +16,19 @@ namespace Artemis.Core.Services.Interfaces
/// <param name="settings">JSON settings to be deserialized and injected into the layer brush</param>
/// <returns></returns>
LayerBrush InstantiateLayerBrush(Layer layer, LayerBrushDescriptor brushDescriptor, string settings = null);
/// <summary>
/// Instantiates and adds a compatible <see cref="KeyframeEngine" /> to the provided <see cref="LayerProperty{T}" />
/// </summary>
/// <param name="layerProperty">The layer property to apply the keyframe engine to.</param>
/// <returns>The resulting keyframe engine, if a compatible engine was found.</returns>
KeyframeEngine InstantiateKeyframeEngine<T>(LayerProperty<T> layerProperty);
/// <summary>
/// Instantiates and adds a compatible <see cref="KeyframeEngine" /> to the provided <see cref="BaseLayerProperty" />.
/// </summary>
/// <param name="layerProperty">The layer property to apply the keyframe engine to.</param>
/// <returns>The resulting keyframe engine, if a compatible engine was found.</returns>
KeyframeEngine InstantiateKeyframeEngine(BaseLayerProperty layerProperty);
}
}
+21
View File
@@ -1,7 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Artemis.Core.Models.Profile;
using Artemis.Core.Models.Profile.KeyframeEngines;
using Artemis.Core.Models.Profile.LayerProperties;
using Artemis.Core.Plugins.Exceptions;
using Artemis.Core.Plugins.LayerBrush;
using Artemis.Core.Services.Interfaces;
@@ -74,6 +77,24 @@ namespace Artemis.Core.Services
return layerElement;
}
public KeyframeEngine InstantiateKeyframeEngine<T>(LayerProperty<T> layerProperty)
{
return InstantiateKeyframeEngine((BaseLayerProperty) layerProperty);
}
public KeyframeEngine InstantiateKeyframeEngine(BaseLayerProperty layerProperty)
{
// This creates an instance of each keyframe engine, which is pretty cheap since all the expensive stuff is done during
// Initialize() call but it's not ideal
var keyframeEngines = _kernel.Get<List<KeyframeEngine>>();
var keyframeEngine = keyframeEngines.FirstOrDefault(k => k.CompatibleTypes.Contains(layerProperty.Type));
if (keyframeEngine == null)
return null;
keyframeEngine.Initialize(layerProperty);
return keyframeEngine;
}
public void RemoveLayerBrush(Layer layer, LayerBrush layerElement)
{
var brush = layer.LayerBrush;
@@ -70,6 +70,7 @@ namespace Artemis.Core.Services.Storage
if (_surfaceService.ActiveSurface != null)
profile.PopulateLeds(_surfaceService.ActiveSurface);
return profile;
}
@@ -78,7 +79,10 @@ namespace Artemis.Core.Services.Storage
{
module.ChangeActiveProfile(profile, _surfaceService.ActiveSurface);
if (profile != null)
{
InstantiateProfileLayerBrushes(profile);
InstantiateProfileKeyframeEngines(profile);
}
}
public void DeleteProfile(Profile profile)
@@ -118,6 +122,15 @@ namespace Artemis.Core.Services.Storage
}
}
private void InstantiateProfileKeyframeEngines(Profile profile)
{
// Only instantiate engines for properties without an existing engine instance
foreach (var layerProperty in profile.GetAllLayers().SelectMany(l => l.Properties).Where(p => p.KeyframeEngine == null))
{
_layerService.InstantiateKeyframeEngine(layerProperty);
}
}
private void ActiveProfilesPopulateLeds(ArtemisSurface surface)
{
var profileModules = _pluginService.GetPluginsOfType<ProfileModule>();
@@ -132,6 +145,13 @@ namespace Artemis.Core.Services.Storage
InstantiateProfileLayerBrushes(profileModule.ActiveProfile);
}
private void ActiveProfilesInstantiateKeyframeEngines()
{
var profileModules = _pluginService.GetPluginsOfType<ProfileModule>();
foreach (var profileModule in profileModules.Where(p => p.ActiveProfile != null).ToList())
InstantiateProfileKeyframeEngines(profileModule.ActiveProfile);
}
#region Event handlers
private void OnActiveSurfaceConfigurationChanged(object sender, SurfaceConfigurationEventArgs e)
@@ -148,7 +168,10 @@ namespace Artemis.Core.Services.Storage
private void OnPluginLoaded(object sender, PluginEventArgs e)
{
if (e.PluginInfo.Instance is LayerBrushProvider)
{
ActiveProfilesInstantiateProfileLayerBrushes();
ActiveProfilesInstantiateKeyframeEngines();
}
}
#endregion
+398
View File
@@ -0,0 +1,398 @@
using System;
namespace Artemis.Core.Utilities
{
public static class Easings
{
/// <summary>
/// Easing Functions enumeration
/// </summary>
public enum Functions
{
Linear,
QuadraticEaseIn,
QuadraticEaseOut,
QuadraticEaseInOut,
CubicEaseIn,
CubicEaseOut,
CubicEaseInOut,
QuarticEaseIn,
QuarticEaseOut,
QuarticEaseInOut,
QuinticEaseIn,
QuinticEaseOut,
QuinticEaseInOut,
SineEaseIn,
SineEaseOut,
SineEaseInOut,
CircularEaseIn,
CircularEaseOut,
CircularEaseInOut,
ExponentialEaseIn,
ExponentialEaseOut,
ExponentialEaseInOut,
ElasticEaseIn,
ElasticEaseOut,
ElasticEaseInOut,
BackEaseIn,
BackEaseOut,
BackEaseInOut,
BounceEaseIn,
BounceEaseOut,
BounceEaseInOut
}
/// <summary>
/// Constant Pi.
/// </summary>
private const double PI = Math.PI;
/// <summary>
/// Constant Pi / 2.
/// </summary>
private const double HALFPI = Math.PI / 2.0;
/// <summary>
/// Interpolate using the specified function.
/// </summary>
public static double Interpolate(double p, Functions function)
{
switch (function)
{
default:
case Functions.Linear: return Linear(p);
case Functions.QuadraticEaseOut: return QuadraticEaseOut(p);
case Functions.QuadraticEaseIn: return QuadraticEaseIn(p);
case Functions.QuadraticEaseInOut: return QuadraticEaseInOut(p);
case Functions.CubicEaseIn: return CubicEaseIn(p);
case Functions.CubicEaseOut: return CubicEaseOut(p);
case Functions.CubicEaseInOut: return CubicEaseInOut(p);
case Functions.QuarticEaseIn: return QuarticEaseIn(p);
case Functions.QuarticEaseOut: return QuarticEaseOut(p);
case Functions.QuarticEaseInOut: return QuarticEaseInOut(p);
case Functions.QuinticEaseIn: return QuinticEaseIn(p);
case Functions.QuinticEaseOut: return QuinticEaseOut(p);
case Functions.QuinticEaseInOut: return QuinticEaseInOut(p);
case Functions.SineEaseIn: return SineEaseIn(p);
case Functions.SineEaseOut: return SineEaseOut(p);
case Functions.SineEaseInOut: return SineEaseInOut(p);
case Functions.CircularEaseIn: return CircularEaseIn(p);
case Functions.CircularEaseOut: return CircularEaseOut(p);
case Functions.CircularEaseInOut: return CircularEaseInOut(p);
case Functions.ExponentialEaseIn: return ExponentialEaseIn(p);
case Functions.ExponentialEaseOut: return ExponentialEaseOut(p);
case Functions.ExponentialEaseInOut: return ExponentialEaseInOut(p);
case Functions.ElasticEaseIn: return ElasticEaseIn(p);
case Functions.ElasticEaseOut: return ElasticEaseOut(p);
case Functions.ElasticEaseInOut: return ElasticEaseInOut(p);
case Functions.BackEaseIn: return BackEaseIn(p);
case Functions.BackEaseOut: return BackEaseOut(p);
case Functions.BackEaseInOut: return BackEaseInOut(p);
case Functions.BounceEaseIn: return BounceEaseIn(p);
case Functions.BounceEaseOut: return BounceEaseOut(p);
case Functions.BounceEaseInOut: return BounceEaseInOut(p);
}
}
/// <summary>
/// Modeled after the line y = x
/// </summary>
public static double Linear(double p)
{
return p;
}
/// <summary>
/// Modeled after the parabola y = x^2
/// </summary>
public static double QuadraticEaseIn(double p)
{
return p * p;
}
/// <summary>
/// Modeled after the parabola y = -x^2 + 2x
/// </summary>
public static double QuadraticEaseOut(double p)
{
return -(p * (p - 2));
}
/// <summary>
/// Modeled after the piecewise quadratic
/// y = (1/2)((2x)^2) ; [0, 0.5)
/// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
/// </summary>
public static double QuadraticEaseInOut(double p)
{
if (p < 0.5)
return 2 * p * p;
return -2 * p * p + 4 * p - 1;
}
/// <summary>
/// Modeled after the cubic y = x^3
/// </summary>
public static double CubicEaseIn(double p)
{
return p * p * p;
}
/// <summary>
/// Modeled after the cubic y = (x - 1)^3 + 1
/// </summary>
public static double CubicEaseOut(double p)
{
var f = p - 1;
return f * f * f + 1;
}
/// <summary>
/// Modeled after the piecewise cubic
/// y = (1/2)((2x)^3) ; [0, 0.5)
/// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
/// </summary>
public static double CubicEaseInOut(double p)
{
if (p < 0.5)
return 4 * p * p * p;
var f = 2 * p - 2;
return 0.5 * f * f * f + 1;
}
/// <summary>
/// Modeled after the quartic x^4
/// </summary>
public static double QuarticEaseIn(double p)
{
return p * p * p * p;
}
/// <summary>
/// Modeled after the quartic y = 1 - (x - 1)^4
/// </summary>
public static double QuarticEaseOut(double p)
{
var f = p - 1;
return f * f * f * (1 - p) + 1;
}
/// <summary>
// Modeled after the piecewise quartic
// y = (1/2)((2x)^4) ; [0, 0.5)
// y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
/// </summary>
public static double QuarticEaseInOut(double p)
{
if (p < 0.5)
return 8 * p * p * p * p;
var f = p - 1;
return -8 * f * f * f * f + 1;
}
/// <summary>
/// Modeled after the quintic y = x^5
/// </summary>
public static double QuinticEaseIn(double p)
{
return p * p * p * p * p;
}
/// <summary>
/// Modeled after the quintic y = (x - 1)^5 + 1
/// </summary>
public static double QuinticEaseOut(double p)
{
var f = p - 1;
return f * f * f * f * f + 1;
}
/// <summary>
/// Modeled after the piecewise quintic
/// y = (1/2)((2x)^5) ; [0, 0.5)
/// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
/// </summary>
public static double QuinticEaseInOut(double p)
{
if (p < 0.5)
return 16 * p * p * p * p * p;
var f = 2 * p - 2;
return 0.5 * f * f * f * f * f + 1;
}
/// <summary>
/// Modeled after quarter-cycle of sine wave
/// </summary>
public static double SineEaseIn(double p)
{
return Math.Sin((p - 1) * HALFPI) + 1;
}
/// <summary>
/// Modeled after quarter-cycle of sine wave (different phase)
/// </summary>
public static double SineEaseOut(double p)
{
return Math.Sin(p * HALFPI);
}
/// <summary>
/// Modeled after half sine wave
/// </summary>
public static double SineEaseInOut(double p)
{
return 0.5 * (1 - Math.Cos(p * PI));
}
/// <summary>
/// Modeled after shifted quadrant IV of unit circle
/// </summary>
public static double CircularEaseIn(double p)
{
return 1 - Math.Sqrt(1 - p * p);
}
/// <summary>
/// Modeled after shifted quadrant II of unit circle
/// </summary>
public static double CircularEaseOut(double p)
{
return Math.Sqrt((2 - p) * p);
}
/// <summary>
/// Modeled after the piecewise circular function
/// y = (1/2)(1 - Math.Sqrt(1 - 4x^2)) ; [0, 0.5)
/// y = (1/2)(Math.Sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
/// </summary>
public static double CircularEaseInOut(double p)
{
if (p < 0.5)
return 0.5 * (1 - Math.Sqrt(1 - 4 * (p * p)));
return 0.5 * (Math.Sqrt(-(2 * p - 3) * (2 * p - 1)) + 1);
}
/// <summary>
/// Modeled after the exponential function y = 2^(10(x - 1))
/// </summary>
public static double ExponentialEaseIn(double p)
{
return p == 0.0 ? p : Math.Pow(2, 10 * (p - 1));
}
/// <summary>
/// Modeled after the exponential function y = -2^(-10x) + 1
/// </summary>
public static double ExponentialEaseOut(double p)
{
return p == 1.0 ? p : 1 - Math.Pow(2, -10 * p);
}
/// <summary>
/// Modeled after the piecewise exponential
/// y = (1/2)2^(10(2x - 1)) ; [0,0.5)
/// y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]
/// </summary>
public static double ExponentialEaseInOut(double p)
{
if (p == 0.0 || p == 1.0) return p;
if (p < 0.5)
return 0.5 * Math.Pow(2, 20 * p - 10);
return -0.5 * Math.Pow(2, -20 * p + 10) + 1;
}
/// <summary>
/// Modeled after the damped sine wave y = sin(13pi/2*x)*Math.Pow(2, 10 * (x - 1))
/// </summary>
public static double ElasticEaseIn(double p)
{
return Math.Sin(13 * HALFPI * p) * Math.Pow(2, 10 * (p - 1));
}
/// <summary>
/// Modeled after the damped sine wave y = sin(-13pi/2*(x + 1))*Math.Pow(2, -10x) + 1
/// </summary>
public static double ElasticEaseOut(double p)
{
return Math.Sin(-13 * HALFPI * (p + 1)) * Math.Pow(2, -10 * p) + 1;
}
/// <summary>
/// Modeled after the piecewise exponentially-damped sine wave:
/// y = (1/2)*sin(13pi/2*(2*x))*Math.Pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
/// y = (1/2)*(sin(-13pi/2*((2x-1)+1))*Math.Pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
/// </summary>
public static double ElasticEaseInOut(double p)
{
if (p < 0.5)
return 0.5 * Math.Sin(13 * HALFPI * (2 * p)) * Math.Pow(2, 10 * (2 * p - 1));
return 0.5 * (Math.Sin(-13 * HALFPI * (2 * p - 1 + 1)) * Math.Pow(2, -10 * (2 * p - 1)) + 2);
}
/// <summary>
/// Modeled after the overshooting cubic y = x^3-x*sin(x*pi)
/// </summary>
public static double BackEaseIn(double p)
{
return p * p * p - p * Math.Sin(p * PI);
}
/// <summary>
/// Modeled after overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
/// </summary>
public static double BackEaseOut(double p)
{
var f = 1 - p;
return 1 - (f * f * f - f * Math.Sin(f * PI));
}
/// <summary>
/// Modeled after the piecewise overshooting cubic function:
/// y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5)
/// y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]
/// </summary>
public static double BackEaseInOut(double p)
{
if (p < 0.5)
{
var f = 2 * p;
return 0.5 * (f * f * f - f * Math.Sin(f * PI));
}
else
{
var f = 1 - (2 * p - 1);
return 0.5 * (1 - (f * f * f - f * Math.Sin(f * PI))) + 0.5;
}
}
/// <summary>
/// </summary>
public static double BounceEaseIn(double p)
{
return 1 - BounceEaseOut(1 - p);
}
/// <summary>
/// </summary>
public static double BounceEaseOut(double p)
{
if (p < 4 / 11.0)
return 121 * p * p / 16.0;
if (p < 8 / 11.0)
return 363 / 40.0 * p * p - 99 / 10.0 * p + 17 / 5.0;
if (p < 9 / 10.0)
return 4356 / 361.0 * p * p - 35442 / 1805.0 * p + 16061 / 1805.0;
return 54 / 5.0 * p * p - 513 / 25.0 * p + 268 / 25.0;
}
/// <summary>
/// </summary>
public static double BounceEaseInOut(double p)
{
if (p < 0.5)
return 0.5 * BounceEaseIn(p * 2);
return 0.5 * BounceEaseOut(p * 2 - 1) + 0.5;
}
}
}