123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524 |
- unit amEasing;
- (*
- * Copyright © 2006 Anders Melander
- *
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/.
- *)
- // -----------------------------------------------------------------------------
- //
- // Easing / Tweening animation
- //
- // -----------------------------------------------------------------------------
- // See:
- // - Robert Penner's Easing Functions
- // http://robertpenner.com/easing/
- //
- // - Motion, Tweening, and Easing
- // http://robertpenner.com/easing/penner_chapter7_tweening.pdf
- //
- // - Easing Functions Cheat Sheet
- // http://easings.net
- // -----------------------------------------------------------------------------
- // WEAKPACKAGEUNIT so we can include the unit in a design time package.
- {$WEAKPACKAGEUNIT ON}
- interface
- type
- // Ease function prototype.
- // Value: [0..1]
- // Result: [0..1]
- TEaseFunc = function(Value: Double): Double;
- // Ease/Tween performer prototype.
- // Value: [0..1]
- TEasePerformer = reference to procedure(Value: Double; var Continue: boolean);
- // Tween/Easing engine
- procedure AnimatedTween(EaseFunc: TEaseFunc; Duration: integer; Performer: TEasePerformer; Throttle: integer = 0; InitialThrottle: boolean = False);
- (*
- Example of usage:
- Move the position of a button using the Bounce/Elastic animation.
- AnimatedTween(EaseOutElastic, 2000,
- procedure(Value: Double; var Continue: boolean)
- begin
- // Move from Left=20 to Left=100
- Button1.Left := 20 + Trunc(Value * 80);
- end, 40);
- The animation will take 2 seconds.
- Each frame/step will take a minimum of 40 mS.
- The animation will perform up to 50 frames or steps (50 = 2000/40) with
- a maximum frame rate of 25 fps (25 = 1000/40)
- *)
- // -----------------------------------------------------------------------------
- //
- // Easing functions
- //
- // -----------------------------------------------------------------------------
- type
- TEaseLinear = class
- public
- class function Ease(Value: Double): Double; static;
- end;
- TEaseSine = class
- public
- class function EaseIn(Value: Double): Double; static;
- class function EaseOut(Value: Double): Double; static;
- class function EaseInOut(Value: Double): Double; static;
- end;
- TEaseCubic = class
- public
- class function EaseIn(Value: Double): Double; static;
- class function EaseOut(Value: Double): Double; static;
- class function EaseInOut(Value: Double): Double; static;
- end;
- TEaseQuadratic = class
- public
- class function EaseIn(Value: Double): Double; static;
- class function EaseOut(Value: Double): Double; static;
- class function EaseInOut(Value: Double): Double; static;
- end;
- TEaseQuartic = class
- public
- class function EaseIn(Value: Double): Double; static;
- class function EaseOut(Value: Double): Double; static;
- class function EaseInOut(Value: Double): Double; static;
- end;
- TEaseQuintic = class
- public
- class function EaseIn(Value: Double): Double; static;
- class function EaseOut(Value: Double): Double; static;
- class function EaseInOut(Value: Double): Double; static;
- end;
- TEaseCircular = class
- public
- class function EaseIn(Value: Double): Double; static;
- class function EaseOut(Value: Double): Double; static;
- class function EaseInOut(Value: Double): Double; static;
- end;
- TEaseElastic = class
- public
- class function EaseIn(Value: Double): Double; static;
- class function EaseOut(Value: Double): Double; static;
- class function EaseInOut(Value: Double): Double; static;
- end;
- TEaseExponential = class
- public
- class function EaseIn(Value: Double): Double; static;
- class function EaseOut(Value: Double): Double; static;
- class function EaseInOut(Value: Double): Double; static;
- end;
- TEaseBack = class
- public
- class function EaseIn(Value: Double): Double; static;
- class function EaseOut(Value: Double): Double; static;
- class function EaseOut2(Value: Double): Double; static;
- class function EaseInOut(Value: Double): Double; static;
- end;
- TEaseBounce = class
- public
- class function EaseIn(Value: Double): Double; static;
- class function EaseOut(Value: Double): Double; static;
- class function EaseInOut(Value: Double): Double; static;
- end;
- // -----------------------------------------------------------------------------
- // -----------------------------------------------------------------------------
- // -----------------------------------------------------------------------------
- implementation
- uses
- Math,
- Windows,
- System.Diagnostics;
- // -----------------------------------------------------------------------------
- class function TEaseLinear.Ease(Value: Double): Double;
- begin
- Result := Value;
- end;
- // Modeled after quarter-cycle of sine wave
- class function TEaseSine.EaseIn(Value: Double): Double;
- begin
- Result := Sin((Value - 1) * Pi/2) + 1;
- end;
- // Modeled after quarter-cycle of sine wave (different phase)
- class function TEaseSine.EaseOut(Value: Double): Double;
- begin
- Result := Sin(Value * Pi/2);
- end;
- // Modeled after half sine wave
- class function TEaseSine.EaseInOut(Value: Double): Double;
- begin
- Result := 0.5 * (1 - Cos(Value * Pi));
- end;
- // Modeled after the parabola
- // y = x^2
- class function TEaseQuadratic.EaseIn(Value: Double): Double;
- begin
- Result := Value * Value;
- end;
- // Modeled after the parabola
- // y = -x^2 + 2x
- class function TEaseQuadratic.EaseOut(Value: Double): Double;
- begin
- Result := -(Value * (Value - 2));
- end;
- // Modeled after the piecewise quadratic
- // y = (1/2)((2x)^2) ; [0, 0.5)
- // y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
- class function TEaseQuadratic.EaseInOut(Value: Double): Double;
- begin
- if (Value < 0.5) then
- Result := 2 * Value * Value
- else
- Result := (-2 * Value * Value) + (4 * Value) - 1;
- end;
- // Modeled after the cubic
- // y = x^3
- class function TEaseCubic.EaseIn(Value: Double): Double;
- begin
- Result := Value * Value * Value;
- end;
- // Modeled after the cubic
- // y = (x - 1)^3 + 1
- class function TEaseCubic.EaseOut(Value: Double): Double;
- begin
- Value := Value - 1;
- Result := Value * Value * Value + 1;
- end;
- // Modeled after the piecewise cubic
- // y = (1/2)((2x)^3) ; [0, 0.5)
- // y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
- class function TEaseCubic.EaseInOut(Value: Double): Double;
- begin
- if (Value < 0.5) then
- Result := 4 * Value * Value * Value
- else
- begin
- Value := (2 * Value) - 2;
- Result := 0.5 * Value * Value * Value + 1;
- end;
- end;
- // Modeled after the quartic
- // y = x^4
- class function TEaseQuartic.EaseIn(Value: Double): Double;
- begin
- Result := Value * Value * Value * Value;
- end;
- // Modeled after the quartic
- // y = 1 - (x - 1)^4
- class function TEaseQuartic.EaseOut(Value: Double): Double;
- begin
- Value := Value - 1;
- Result := 1 - (Value * Value * Value * Value);
- end;
- // Modeled after the piecewise quartic
- // y = (1/2)((2x)^4) ; [0, 0.5)
- // y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
- class function TEaseQuartic.EaseInOut(Value: Double): Double;
- begin
- if (Value < 0.5) then
- Result := 8 * Value * Value * Value * Value
- else
- begin
- Value := Value - 1;
- Result := -8 * Value * Value * Value * Value + 1;
- end;
- end;
- // Modeled after the quintic
- // y = x^5
- class function TEaseQuintic.EaseIn(Value: Double): Double;
- begin
- Result := Value * Value * Value * Value * Value;
- end;
- // Modeled after the quintic
- // y = (x - 1)^5 + 1
- class function TEaseQuintic.EaseOut(Value: Double): Double;
- begin
- Value := Value - 1;
- Result := Value * Value * Value * Value * Value + 1;
- end;
- // Modeled after the piecewise quintic
- // y = (1/2)((2x)^5) ; [0, 0.5)
- // y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
- class function TEaseQuintic.EaseInOut(Value: Double): Double;
- begin
- if (Value < 0.5) then
- Result := 16 * Value * Value * Value * Value * Value
- else
- begin
- Value := 2 * Value - 2;
- Result := 0.5 * Value * Value * Value * Value * Value + 1;
- end;
- end;
- // Modeled after shifted quadrant IV of unit circle
- class function TEaseCircular.EaseIn(Value: Double): Double;
- begin
- Result := 1 - Sqrt(1 - Value * Value);
- end;
- // Modeled after shifted quadrant II of unit circle
- class function TEaseCircular.EaseOut(Value: Double): Double;
- begin
- Result := Sqrt((2 - Value) * Value);
- end;
- // Modeled after the piecewise circular function
- // y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5)
- // y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
- class function TEaseCircular.EaseInOut(Value: Double): Double;
- begin
- if (Value < 0.5) then
- Result := 0.5 * (1 - Sqrt(1 - 4 * Value * Value))
- else
- Result := 0.5 * (Sqrt(-(2 * Value - 3) * (2 * Value - 1)) + 1);
- end;
- // Modeled after the damped sine wave
- // y = sin(13pi/2*x)*pow(2, 10 * (x - 1))
- class function TEaseElastic.EaseIn(Value: Double): Double;
- begin
- if (Value = 0) then
- Exit(0);
- if (Value = 1) then
- Exit(1);
- Result := Sin(13 * Pi/2 * Value) * Math.Power(2, 10 * (Value-1));
- end;
- // Modeled after the damped sine wave
- // y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1
- class function TEaseElastic.EaseOut(Value: Double): Double;
- begin
- if (Value = 0) then
- Exit(0);
- if (Value = 1) then
- Exit(1);
- Result := Sin(-13 * Pi/2 * (Value + 1)) * Math.Power(2, -10 * Value) + 1;
- end;
- // Modeled after the piecewise exponentially-damped sine wave:
- // y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
- // y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
- class function TEaseElastic.EaseInOut(Value: Double): Double;
- begin
- if (Value = 0) then
- Exit(0);
- if (Value = 1) then
- Exit(1);
- if (Value < 0.5) then
- Result := 0.5 * Sin(13 * Pi * Value) * Math.Power(2, 10 * (2 * Value - 1))
- else
- Result := 0.5 * (Sin(-13 * Pi/2 * ((2 * Value - 1) + 1)) * Math.Power(2, -10 * (2 * Value - 1)) + 2);
- end;
- // Modeled after the exponential function
- // y = 2^(10(x - 1))
- class function TEaseExponential.EaseIn(Value: Double): Double;
- begin
- if (Value = 0) then
- Result := 0
- else
- Result := Math.Power(2, 10 * (Value-1));
- end;
- // Modeled after the exponential function
- // y = -2^(-10x) + 1
- class function TEaseExponential.EaseOut(Value: Double): Double;
- begin
- if (Value = 1) then
- Result := 1
- else
- Result := Math.Power(2, -10 * Value);
- end;
- // 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]
- class function TEaseExponential.EaseInOut(Value: Double): Double;
- begin
- if (Value = 0) then
- Result := 0
- else
- if (Value = 1) then
- Result := 1
- else
- if (Value < 0.5) then
- Result := 0.5 * Math.Power(2, 20 * Value - 10)
- else
- Result := -0.5 * Math.Power(2, -20 * Value + 10) + 1;
- end;
- // Modeled after the overshooting cubic
- // y = x^3-x*sin(x*pi)
- class function TEaseBack.EaseIn(Value: Double): Double;
- const
- s = 1.70158;
- begin
- Result := Value * Value * ((s + 1) * Value - s);
- end;
- // Modeled after the overshooting cubic
- // y = (1-x)^2*((x+1)*(1-x)+x)+1
- class function TEaseBack.EaseOut(Value: Double): Double;
- const
- s = 1.70158;
- begin
- Value := Value - 1;
- Result := Value * Value * ((s + 1) * Value + s) + 1;
- end;
- // 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]
- class function TEaseBack.EaseInOut(Value: Double): Double;
- const
- s = 1.70158 * 1.525;
- begin
- Value := Value * 2;
- if (Value < 1) then
- Result := 0.5 * (Value * Value * ((s + 1) * Value - s))
- else
- begin
- Value := Value - 2;
- Result := 0.5 * (Value * Value * ((s + 1) * Value + s) + 2);
- end;
- end;
- // Modeled after the overshooting cubic
- // y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
- // Overshoots a bit more than EaseOutBack
- class function TEaseBack.EaseOut2(Value: Double): Double;
- begin
- Value := 1 - Value;
- Result := 1 - (Value * Value * Value - Value * Sin(Value * Pi));
- end;
- class function TEaseBounce.EaseIn(Value: Double): Double;
- begin
- Result := 1 - EaseOut(1 - Value);
- end;
- class function TEaseBounce.EaseOut(Value: Double): Double;
- begin
- if (Value < 4/11) then
- Result := (121 * Value * Value) / 16
- else
- if (Value < 8/11) then
- Result := (363/40 * Value * Value) - (99/10 * Value) + 17/5
- else
- if (Value < 9/10) then
- Result := (4356/361 * Value * Value) - (35442/1805 * Value) + 16061/1805
- else
- Result := (54/5 * Value * Value) - (513/25 * Value) + 268/25;
- end;
- class function TEaseBounce.EaseInOut(Value: Double): Double;
- begin
- if(Value < 0.5) then
- Result := 0.5 * EaseIn(Value * 2)
- else
- Result := 0.5 * EaseOut(Value * 2 - 1) + 0.5;
- end;
- // -----------------------------------------------------------------------------
- procedure AnimatedTween(EaseFunc: TEaseFunc; Duration: integer; Performer: TEasePerformer; Throttle: integer; InitialThrottle: boolean);
- var
- Stopwatch: TStopwatch;
- Elapsed: int64;
- Value: Double;
- RemainingThrottle: int64;
- Continue: boolean;
- begin
- (*
- ** Performs time controlled tweening using an easing function.
- *)
- Stopwatch := TStopwatch.StartNew;
- Elapsed := 0;
- Value := 0;
- Continue := True;
- while (Continue) and (Elapsed <= Duration) do
- begin
- // Throttle
- if ((Elapsed <> 0) or (InitialThrottle)) and (Throttle <> 0) and (Elapsed < Duration) then
- begin
- // Make sure we don't wait too long
- RemainingThrottle := Duration - Stopwatch.ElapsedMilliseconds;
- if (RemainingThrottle > 0) then
- Sleep(Min(RemainingThrottle, Throttle));
- // Calculate time elapsed during throttle
- Elapsed := Stopwatch.ElapsedMilliseconds;
- end;
- if (Elapsed < Duration) then
- // Calculate tween value...
- Value := EaseFunc(Elapsed / Duration)
- else
- Value := 1;
- // ...and Ease
- Performer(Value, Continue);
- // Calculate time elapsed during Ease
- Elapsed := Stopwatch.ElapsedMilliseconds;
- end;
- // If we exited the loop prematurely because we ran out of time then
- // give the performer a final go so we can guarantee that we will
- // reach the goal.
- if (Continue) and (Value < 1) then
- Performer(1, Continue);
- end;
- // -----------------------------------------------------------------------------
- end.
|