|
//------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation, 2008
//
// File: BounceEase.cs
//------------------------------------------------------------------------------
using MS.Internal;
namespace System.Windows.Media.Animation
{
/// <summary>
/// This class implements an easing function that can be used to simulate bouncing
/// </summary>
public class BounceEase : EasingFunctionBase
{
public BounceEase()
{
}
/// <summary>
/// Bounces Property
/// </summary>
public static readonly DependencyProperty BouncesProperty =
DependencyProperty.Register(
"Bounces",
typeof(int),
typeof(BounceEase),
new PropertyMetadata(3));
/// <summary>
/// Specifies the number of bounces. This does not include the final half bounce.
/// </summary>
public int Bounces
{
get
{
return (int)GetValue(BouncesProperty);
}
set
{
SetValueInternal(BouncesProperty, value);
}
}
/// <summary>
/// Bounciness Property
/// </summary>
public static readonly DependencyProperty BouncinessProperty =
DependencyProperty.Register(
"Bounciness",
typeof(double),
typeof(BounceEase),
new PropertyMetadata(2.0));
/// <summary>
/// Specifies the amount of bounciness. This corresponds to the scale difference between a bounce and the next bounce.
/// For example, Bounciness = 2.0 correspondes to the next bounce being twices as high and taking twice as long.
/// </summary>
public double Bounciness
{
get
{
return (double)GetValue(BouncinessProperty);
}
set
{
SetValueInternal(BouncinessProperty, value);
}
}
protected override double EaseInCore(double normalizedTime)
{
// The math below is complicated because we have a few requirements to get the correct look for bounce:
// 1) The bounces should be symetrical
// 2) Bounciness should control both the amplitude and the period of the bounces
// 3) Bounces should control the number of bounces without including the final half bounce to get you back to 1.0
//
// Note: Simply modulating a expo or power curve with a abs(sin(...)) wont work because it violates 1) above.
//
// Constants
double bounces = Math.Max(0.0, (double)Bounces);
double bounciness = Bounciness;
// Clamp the bounciness so we dont hit a divide by zero
if (bounciness < 1.0 || DoubleUtil.IsOne(bounciness))
{
// Make it just over one. In practice, this will look like 1.0 but avoid divide by zeros.
bounciness = 1.001;
}
double pow = Math.Pow(bounciness, bounces);
double oneMinusBounciness = 1.0 - bounciness;
// 'unit' space calculations.
// Our bounces grow in the x axis exponentially. we define the first bounce as having a 'unit' width of 1.0 and compute
// the total number of 'units' using a geometric series.
// We then compute which 'unit' the current time is in.
double sumOfUnits = (1.0 - pow) / oneMinusBounciness + pow * 0.5; // geometric series with only half the last sum
double unitAtT = normalizedTime * sumOfUnits;
// 'bounce' space calculations.
// Now that we know which 'unit' the current time is in, we can determine which bounce we're in by solving the geometric equation:
// unitAtT = (1 - bounciness^bounce) / (1 - bounciness), for bounce.
double bounceAtT = Math.Log(-unitAtT * (1.0-bounciness) + 1.0, bounciness);
double start = Math.Floor(bounceAtT);
double end = start + 1.0;
// 'time' space calculations.
// We then project the start and end of the bounce into 'time' space
double startTime = (1.0 - Math.Pow(bounciness, start)) / (oneMinusBounciness * sumOfUnits);
double endTime = (1.0 - Math.Pow(bounciness, end)) / (oneMinusBounciness * sumOfUnits);
// Curve fitting for bounce.
double midTime = (startTime + endTime) * 0.5;
double timeRelativeToPeak = normalizedTime - midTime;
double radius = midTime - startTime;
double amplitude = Math.Pow(1.0 / bounciness, (bounces - start));
// Evaluate a quadratic that hits (startTime,0), (endTime, 0), and peaks at amplitude.
return (-amplitude / (radius * radius)) * (timeRelativeToPeak - radius) * (timeRelativeToPeak + radius);
}
protected override Freezable CreateInstanceCore()
{
return new BounceEase();
}
}
}
|