I\'m trying to emulate an animation effect in code (almost any language would do as it appears to be math rather than language). Essentially, it is the emulation of a mass-sprin
Skip the physics and just go straight to the equation.
parameters: “Here's what I will know in advance - the pixel distance [D] and number of seconds [T0] it takes to get from point A to point B, the number of seconds for oscillation [T1].” Also, I'll add as free parameters: the maximum size of oscillation, Amax, the damping time constant, Tc, and a frame rate, Rf, that is, at what times does one want a new position value. I assume you don't want to calculate this forever, so I'll just do 10 seconds, Ttotal, but there are a variety of reasonable stop conditions...
code:
Here's the code (in Python). The main thing is the equation, found in def Y(t)
:
from numpy import pi, arange, sin, exp
Ystart, D = 900., 900.-150. # all time units in seconds, distance in pixels, Rf in frames/second
T0, T1, Tc, Amax, Rf, Ttotal = 5., 2., 2., 90., 30., 10.
A0 = Amax*(D/T0)*(4./(900-150)) # basically a momentum... scales the size of the oscillation with the speed
def Y(t):
if t<T0: # linear part
y = Ystart-(D/T0)*t
else: # decaying oscillations
y = Ystart-D-A0*sin((2*pi/T1)*(t-T0))*exp(-abs(T0-t)/Tc)
return y
y_result = []
for t in arange(0, Ttotal, 1./Rf): # or one could do "for i in range(int(Ttotal*Rf))" to stick with ints
y = Y(t)
y_result.append(y)
The idea is linear motion up to the point, followed by a decaying oscillation. The oscillation is provided by the sin
and the decay by multiplying it by the exp
. Of course, change the parameters to get any distance, oscillation size, etc, that you want.
notes:
At the risk of making this too long, I realized I could make a gif in GIMP, so this is what it looks like:
I can post the full code to make the plots if there's interest, but basically I'm just calling Y with different D and T0 values for each timestep. If I were to do this again, I could increase the damping (i.e., decrease Tc), but it's a bit of a hassle so I'm leaving it as is.
I was thinking along the same lines as @tom10. (I also considered an IEasingFunction
which took an IList<IEasingFunction>
, but it would be tricky to hack the desired behaviour out of the existing ones).
// Based on the example at
// http://msdn.microsoft.com/en-us/library/system.windows.media.animation.easingfunctionbase.aspx
namespace Org.CheddarMonk
{
public class OtakuEasingFunction : EasingFunctionBase
{
// The time proportion at which the cutoff from linear movement to
// bounce occurs. E.g. for a 4 second movement followed by a 16
// second bounce this would be 4 / (4 + 16) = 0.2.
private double _CutoffPoint;
public double CutoffPoint {
get { return _CutoffPoint; }
set {
if (value <= 0 || value => 1 || double.IsNaN(value)) {
throw new ArgumentException();
}
_CutoffPoint = value;
}
}
// The size of the initial bounce envelope, as a proportion of the
// animation distance. E.g. if the animation moves from 900 to 150
// and you want the maximum bounce to be no more than 35 you would
// set this to 35 / (900 - 150) ~= 0.0467.
private double _EnvelopeHeight;
public double EnvelopeHeight {
get { return _EnvelopeHeight; }
set {
if (value <= 0 || double.IsNaN(value)) {
throw new ArgumentException();
}
_EnvelopeHeight = value;
}
}
// A parameter controlling how fast the bounce height should decay.
// The higher the decay, the sooner the bounce becomes negligible.
private double _EnvelopeDecay;
public double EnvelopeDecay {
get { return _EnvelopeDecay; }
set {
if (value <= 0 || double.IsNaN(value)) {
throw new ArgumentException();
}
_EnvelopeDecay = value;
}
}
// The number of half-bounces.
private int _Oscillations;
public int Oscillations {
get { return _Oscillations; }
set {
if (value <= 0) {
throw new ArgumentException();
}
_Oscillations = value;
}
}
public OtakuEasingFunction() {
// Sensible default values.
CutoffPoint = 0.7;
EnvelopeHeight = 0.3;
EnvelopeDecay = 1;
Oscillations = 3;
}
protected override double EaseInCore(double normalizedTime) {
// If we get an out-of-bounds value, be nice.
if (normalizedTime < 0) return 0;
if (normalizedTime > 1) return 1;
if (normalizedTime < _CutoffPoint) {
return normalizedTime / _CutoffPoint;
}
// Renormalise the time.
double t = (normalizedTime - _CutoffPoint) / (1 - _CutoffPoint);
double envelope = EnvelopeHeight * Math.Exp(-t * EnvelopeDecay);
double bounce = Math.Sin(t * Oscillations * Math.PI);
return envelope * bounce;
}
protected override Freezable CreateInstanceCore() {
return new OtakuEasingFunction();
}
}
}
This is untested code, but it shouldn't be too bad to debug if there are problems. I'm not sure what attributes (if any) need to be added to the properties for the XAML editor to handle them properly.