amEasing.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. unit amEasing;
  2. (*
  3. * Copyright © 2006 Anders Melander
  4. *
  5. * This Source Code Form is subject to the terms of the Mozilla Public
  6. * License, v. 2.0. If a copy of the MPL was not distributed with this
  7. * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  8. *)
  9. // -----------------------------------------------------------------------------
  10. //
  11. // Easing / Tweening animation
  12. //
  13. // -----------------------------------------------------------------------------
  14. // See:
  15. // - Robert Penner's Easing Functions
  16. // http://robertpenner.com/easing/
  17. //
  18. // - Motion, Tweening, and Easing
  19. // http://robertpenner.com/easing/penner_chapter7_tweening.pdf
  20. //
  21. // - Easing Functions Cheat Sheet
  22. // http://easings.net
  23. // -----------------------------------------------------------------------------
  24. // WEAKPACKAGEUNIT so we can include the unit in a design time package.
  25. {$WEAKPACKAGEUNIT ON}
  26. interface
  27. type
  28. // Ease function prototype.
  29. // Value: [0..1]
  30. // Result: [0..1]
  31. TEaseFunc = function(Value: Double): Double;
  32. // Ease/Tween performer prototype.
  33. // Value: [0..1]
  34. TEasePerformer = reference to procedure(Value: Double; var Continue: boolean);
  35. // Tween/Easing engine
  36. procedure AnimatedTween(EaseFunc: TEaseFunc; Duration: integer; Performer: TEasePerformer; Throttle: integer = 0; InitialThrottle: boolean = False);
  37. (*
  38. Example of usage:
  39. Move the position of a button using the Bounce/Elastic animation.
  40. AnimatedTween(EaseOutElastic, 2000,
  41. procedure(Value: Double; var Continue: boolean)
  42. begin
  43. // Move from Left=20 to Left=100
  44. Button1.Left := 20 + Trunc(Value * 80);
  45. end, 40);
  46. The animation will take 2 seconds.
  47. Each frame/step will take a minimum of 40 mS.
  48. The animation will perform up to 50 frames or steps (50 = 2000/40) with
  49. a maximum frame rate of 25 fps (25 = 1000/40)
  50. *)
  51. // -----------------------------------------------------------------------------
  52. //
  53. // Easing functions
  54. //
  55. // -----------------------------------------------------------------------------
  56. type
  57. TEaseLinear = class
  58. public
  59. class function Ease(Value: Double): Double; static;
  60. end;
  61. TEaseSine = class
  62. public
  63. class function EaseIn(Value: Double): Double; static;
  64. class function EaseOut(Value: Double): Double; static;
  65. class function EaseInOut(Value: Double): Double; static;
  66. end;
  67. TEaseCubic = class
  68. public
  69. class function EaseIn(Value: Double): Double; static;
  70. class function EaseOut(Value: Double): Double; static;
  71. class function EaseInOut(Value: Double): Double; static;
  72. end;
  73. TEaseQuadratic = class
  74. public
  75. class function EaseIn(Value: Double): Double; static;
  76. class function EaseOut(Value: Double): Double; static;
  77. class function EaseInOut(Value: Double): Double; static;
  78. end;
  79. TEaseQuartic = class
  80. public
  81. class function EaseIn(Value: Double): Double; static;
  82. class function EaseOut(Value: Double): Double; static;
  83. class function EaseInOut(Value: Double): Double; static;
  84. end;
  85. TEaseQuintic = class
  86. public
  87. class function EaseIn(Value: Double): Double; static;
  88. class function EaseOut(Value: Double): Double; static;
  89. class function EaseInOut(Value: Double): Double; static;
  90. end;
  91. TEaseCircular = class
  92. public
  93. class function EaseIn(Value: Double): Double; static;
  94. class function EaseOut(Value: Double): Double; static;
  95. class function EaseInOut(Value: Double): Double; static;
  96. end;
  97. TEaseElastic = class
  98. public
  99. class function EaseIn(Value: Double): Double; static;
  100. class function EaseOut(Value: Double): Double; static;
  101. class function EaseInOut(Value: Double): Double; static;
  102. end;
  103. TEaseExponential = class
  104. public
  105. class function EaseIn(Value: Double): Double; static;
  106. class function EaseOut(Value: Double): Double; static;
  107. class function EaseInOut(Value: Double): Double; static;
  108. end;
  109. TEaseBack = class
  110. public
  111. class function EaseIn(Value: Double): Double; static;
  112. class function EaseOut(Value: Double): Double; static;
  113. class function EaseOut2(Value: Double): Double; static;
  114. class function EaseInOut(Value: Double): Double; static;
  115. end;
  116. TEaseBounce = class
  117. public
  118. class function EaseIn(Value: Double): Double; static;
  119. class function EaseOut(Value: Double): Double; static;
  120. class function EaseInOut(Value: Double): Double; static;
  121. end;
  122. // -----------------------------------------------------------------------------
  123. // -----------------------------------------------------------------------------
  124. // -----------------------------------------------------------------------------
  125. implementation
  126. uses
  127. Math,
  128. Windows,
  129. System.Diagnostics;
  130. // -----------------------------------------------------------------------------
  131. class function TEaseLinear.Ease(Value: Double): Double;
  132. begin
  133. Result := Value;
  134. end;
  135. // Modeled after quarter-cycle of sine wave
  136. class function TEaseSine.EaseIn(Value: Double): Double;
  137. begin
  138. Result := Sin((Value - 1) * Pi/2) + 1;
  139. end;
  140. // Modeled after quarter-cycle of sine wave (different phase)
  141. class function TEaseSine.EaseOut(Value: Double): Double;
  142. begin
  143. Result := Sin(Value * Pi/2);
  144. end;
  145. // Modeled after half sine wave
  146. class function TEaseSine.EaseInOut(Value: Double): Double;
  147. begin
  148. Result := 0.5 * (1 - Cos(Value * Pi));
  149. end;
  150. // Modeled after the parabola
  151. // y = x^2
  152. class function TEaseQuadratic.EaseIn(Value: Double): Double;
  153. begin
  154. Result := Value * Value;
  155. end;
  156. // Modeled after the parabola
  157. // y = -x^2 + 2x
  158. class function TEaseQuadratic.EaseOut(Value: Double): Double;
  159. begin
  160. Result := -(Value * (Value - 2));
  161. end;
  162. // Modeled after the piecewise quadratic
  163. // y = (1/2)((2x)^2) ; [0, 0.5)
  164. // y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1]
  165. class function TEaseQuadratic.EaseInOut(Value: Double): Double;
  166. begin
  167. if (Value < 0.5) then
  168. Result := 2 * Value * Value
  169. else
  170. Result := (-2 * Value * Value) + (4 * Value) - 1;
  171. end;
  172. // Modeled after the cubic
  173. // y = x^3
  174. class function TEaseCubic.EaseIn(Value: Double): Double;
  175. begin
  176. Result := Value * Value * Value;
  177. end;
  178. // Modeled after the cubic
  179. // y = (x - 1)^3 + 1
  180. class function TEaseCubic.EaseOut(Value: Double): Double;
  181. begin
  182. Value := Value - 1;
  183. Result := Value * Value * Value + 1;
  184. end;
  185. // Modeled after the piecewise cubic
  186. // y = (1/2)((2x)^3) ; [0, 0.5)
  187. // y = (1/2)((2x-2)^3 + 2) ; [0.5, 1]
  188. class function TEaseCubic.EaseInOut(Value: Double): Double;
  189. begin
  190. if (Value < 0.5) then
  191. Result := 4 * Value * Value * Value
  192. else
  193. begin
  194. Value := (2 * Value) - 2;
  195. Result := 0.5 * Value * Value * Value + 1;
  196. end;
  197. end;
  198. // Modeled after the quartic
  199. // y = x^4
  200. class function TEaseQuartic.EaseIn(Value: Double): Double;
  201. begin
  202. Result := Value * Value * Value * Value;
  203. end;
  204. // Modeled after the quartic
  205. // y = 1 - (x - 1)^4
  206. class function TEaseQuartic.EaseOut(Value: Double): Double;
  207. begin
  208. Value := Value - 1;
  209. Result := 1 - (Value * Value * Value * Value);
  210. end;
  211. // Modeled after the piecewise quartic
  212. // y = (1/2)((2x)^4) ; [0, 0.5)
  213. // y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1]
  214. class function TEaseQuartic.EaseInOut(Value: Double): Double;
  215. begin
  216. if (Value < 0.5) then
  217. Result := 8 * Value * Value * Value * Value
  218. else
  219. begin
  220. Value := Value - 1;
  221. Result := -8 * Value * Value * Value * Value + 1;
  222. end;
  223. end;
  224. // Modeled after the quintic
  225. // y = x^5
  226. class function TEaseQuintic.EaseIn(Value: Double): Double;
  227. begin
  228. Result := Value * Value * Value * Value * Value;
  229. end;
  230. // Modeled after the quintic
  231. // y = (x - 1)^5 + 1
  232. class function TEaseQuintic.EaseOut(Value: Double): Double;
  233. begin
  234. Value := Value - 1;
  235. Result := Value * Value * Value * Value * Value + 1;
  236. end;
  237. // Modeled after the piecewise quintic
  238. // y = (1/2)((2x)^5) ; [0, 0.5)
  239. // y = (1/2)((2x-2)^5 + 2) ; [0.5, 1]
  240. class function TEaseQuintic.EaseInOut(Value: Double): Double;
  241. begin
  242. if (Value < 0.5) then
  243. Result := 16 * Value * Value * Value * Value * Value
  244. else
  245. begin
  246. Value := 2 * Value - 2;
  247. Result := 0.5 * Value * Value * Value * Value * Value + 1;
  248. end;
  249. end;
  250. // Modeled after shifted quadrant IV of unit circle
  251. class function TEaseCircular.EaseIn(Value: Double): Double;
  252. begin
  253. Result := 1 - Sqrt(1 - Value * Value);
  254. end;
  255. // Modeled after shifted quadrant II of unit circle
  256. class function TEaseCircular.EaseOut(Value: Double): Double;
  257. begin
  258. Result := Sqrt((2 - Value) * Value);
  259. end;
  260. // Modeled after the piecewise circular function
  261. // y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5)
  262. // y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1]
  263. class function TEaseCircular.EaseInOut(Value: Double): Double;
  264. begin
  265. if (Value < 0.5) then
  266. Result := 0.5 * (1 - Sqrt(1 - 4 * Value * Value))
  267. else
  268. Result := 0.5 * (Sqrt(-(2 * Value - 3) * (2 * Value - 1)) + 1);
  269. end;
  270. // Modeled after the damped sine wave
  271. // y = sin(13pi/2*x)*pow(2, 10 * (x - 1))
  272. class function TEaseElastic.EaseIn(Value: Double): Double;
  273. begin
  274. if (Value = 0) then
  275. Exit(0);
  276. if (Value = 1) then
  277. Exit(1);
  278. Result := Sin(13 * Pi/2 * Value) * Math.Power(2, 10 * (Value-1));
  279. end;
  280. // Modeled after the damped sine wave
  281. // y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1
  282. class function TEaseElastic.EaseOut(Value: Double): Double;
  283. begin
  284. if (Value = 0) then
  285. Exit(0);
  286. if (Value = 1) then
  287. Exit(1);
  288. Result := Sin(-13 * Pi/2 * (Value + 1)) * Math.Power(2, -10 * Value) + 1;
  289. end;
  290. // Modeled after the piecewise exponentially-damped sine wave:
  291. // y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5)
  292. // y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1]
  293. class function TEaseElastic.EaseInOut(Value: Double): Double;
  294. begin
  295. if (Value = 0) then
  296. Exit(0);
  297. if (Value = 1) then
  298. Exit(1);
  299. if (Value < 0.5) then
  300. Result := 0.5 * Sin(13 * Pi * Value) * Math.Power(2, 10 * (2 * Value - 1))
  301. else
  302. Result := 0.5 * (Sin(-13 * Pi/2 * ((2 * Value - 1) + 1)) * Math.Power(2, -10 * (2 * Value - 1)) + 2);
  303. end;
  304. // Modeled after the exponential function
  305. // y = 2^(10(x - 1))
  306. class function TEaseExponential.EaseIn(Value: Double): Double;
  307. begin
  308. if (Value = 0) then
  309. Result := 0
  310. else
  311. Result := Math.Power(2, 10 * (Value-1));
  312. end;
  313. // Modeled after the exponential function
  314. // y = -2^(-10x) + 1
  315. class function TEaseExponential.EaseOut(Value: Double): Double;
  316. begin
  317. if (Value = 1) then
  318. Result := 1
  319. else
  320. Result := Math.Power(2, -10 * Value);
  321. end;
  322. // Modeled after the piecewise exponential
  323. // y = (1/2)2^(10(2x - 1)) ; [0,0.5)
  324. // y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1]
  325. class function TEaseExponential.EaseInOut(Value: Double): Double;
  326. begin
  327. if (Value = 0) then
  328. Result := 0
  329. else
  330. if (Value = 1) then
  331. Result := 1
  332. else
  333. if (Value < 0.5) then
  334. Result := 0.5 * Math.Power(2, 20 * Value - 10)
  335. else
  336. Result := -0.5 * Math.Power(2, -20 * Value + 10) + 1;
  337. end;
  338. // Modeled after the overshooting cubic
  339. // y = x^3-x*sin(x*pi)
  340. class function TEaseBack.EaseIn(Value: Double): Double;
  341. const
  342. s = 1.70158;
  343. begin
  344. Result := Value * Value * ((s + 1) * Value - s);
  345. end;
  346. // Modeled after the overshooting cubic
  347. // y = (1-x)^2*((x+1)*(1-x)+x)+1
  348. class function TEaseBack.EaseOut(Value: Double): Double;
  349. const
  350. s = 1.70158;
  351. begin
  352. Value := Value - 1;
  353. Result := Value * Value * ((s + 1) * Value + s) + 1;
  354. end;
  355. // Modeled after the piecewise overshooting cubic function:
  356. // y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5)
  357. // y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1]
  358. class function TEaseBack.EaseInOut(Value: Double): Double;
  359. const
  360. s = 1.70158 * 1.525;
  361. begin
  362. Value := Value * 2;
  363. if (Value < 1) then
  364. Result := 0.5 * (Value * Value * ((s + 1) * Value - s))
  365. else
  366. begin
  367. Value := Value - 2;
  368. Result := 0.5 * (Value * Value * ((s + 1) * Value + s) + 2);
  369. end;
  370. end;
  371. // Modeled after the overshooting cubic
  372. // y = 1-((1-x)^3-(1-x)*sin((1-x)*pi))
  373. // Overshoots a bit more than EaseOutBack
  374. class function TEaseBack.EaseOut2(Value: Double): Double;
  375. begin
  376. Value := 1 - Value;
  377. Result := 1 - (Value * Value * Value - Value * Sin(Value * Pi));
  378. end;
  379. class function TEaseBounce.EaseIn(Value: Double): Double;
  380. begin
  381. Result := 1 - EaseOut(1 - Value);
  382. end;
  383. class function TEaseBounce.EaseOut(Value: Double): Double;
  384. begin
  385. if (Value < 4/11) then
  386. Result := (121 * Value * Value) / 16
  387. else
  388. if (Value < 8/11) then
  389. Result := (363/40 * Value * Value) - (99/10 * Value) + 17/5
  390. else
  391. if (Value < 9/10) then
  392. Result := (4356/361 * Value * Value) - (35442/1805 * Value) + 16061/1805
  393. else
  394. Result := (54/5 * Value * Value) - (513/25 * Value) + 268/25;
  395. end;
  396. class function TEaseBounce.EaseInOut(Value: Double): Double;
  397. begin
  398. if(Value < 0.5) then
  399. Result := 0.5 * EaseIn(Value * 2)
  400. else
  401. Result := 0.5 * EaseOut(Value * 2 - 1) + 0.5;
  402. end;
  403. // -----------------------------------------------------------------------------
  404. procedure AnimatedTween(EaseFunc: TEaseFunc; Duration: integer; Performer: TEasePerformer; Throttle: integer; InitialThrottle: boolean);
  405. var
  406. Stopwatch: TStopwatch;
  407. Elapsed: int64;
  408. Value: Double;
  409. RemainingThrottle: int64;
  410. Continue: boolean;
  411. begin
  412. (*
  413. ** Performs time controlled tweening using an easing function.
  414. *)
  415. Stopwatch := TStopwatch.StartNew;
  416. Elapsed := 0;
  417. Value := 0;
  418. Continue := True;
  419. while (Continue) and (Elapsed <= Duration) do
  420. begin
  421. // Throttle
  422. if ((Elapsed <> 0) or (InitialThrottle)) and (Throttle <> 0) and (Elapsed < Duration) then
  423. begin
  424. // Make sure we don't wait too long
  425. RemainingThrottle := Duration - Stopwatch.ElapsedMilliseconds;
  426. if (RemainingThrottle > 0) then
  427. Sleep(Min(RemainingThrottle, Throttle));
  428. // Calculate time elapsed during throttle
  429. Elapsed := Stopwatch.ElapsedMilliseconds;
  430. end;
  431. if (Elapsed < Duration) then
  432. // Calculate tween value...
  433. Value := EaseFunc(Elapsed / Duration)
  434. else
  435. Value := 1;
  436. // ...and Ease
  437. Performer(Value, Continue);
  438. // Calculate time elapsed during Ease
  439. Elapsed := Stopwatch.ElapsedMilliseconds;
  440. end;
  441. // If we exited the loop prematurely because we ran out of time then
  442. // give the performer a final go so we can guarantee that we will
  443. // reach the goal.
  444. if (Continue) and (Value < 1) then
  445. Performer(1, Continue);
  446. end;
  447. // -----------------------------------------------------------------------------
  448. end.