TimingFunction.cpp 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. /*
  2. * Copyright (c) 2023, Ali Mohammad Pur <mpfard@serenityos.org>
  3. * Copyright (c) 2023, Matthew Olsson <mattco@serenityos.org>
  4. *
  5. * SPDX-License-Identifier: BSD-2-Clause
  6. */
  7. #include <AK/BinarySearch.h>
  8. #include <LibWeb/Animations/TimingFunction.h>
  9. #include <math.h>
  10. namespace Web::Animations {
  11. // https://www.w3.org/TR/css-easing-1/#linear-easing-function
  12. double LinearTimingFunction::operator()(double input_progress, bool) const
  13. {
  14. return input_progress;
  15. }
  16. static double cubic_bezier_at(double x1, double x2, double t)
  17. {
  18. auto a = 1.0 - 3.0 * x2 + 3.0 * x1;
  19. auto b = 3.0 * x2 - 6.0 * x1;
  20. auto c = 3.0 * x1;
  21. auto t2 = t * t;
  22. auto t3 = t2 * t;
  23. return (a * t3) + (b * t2) + (c * t);
  24. }
  25. // https://www.w3.org/TR/css-easing-1/#cubic-bezier-algo
  26. double CubicBezierTimingFunction::operator()(double input_progress, bool) const
  27. {
  28. // For input progress values outside the range [0, 1], the curve is extended infinitely using tangent of the curve
  29. // at the closest endpoint as follows:
  30. // - For input progress values less than zero,
  31. if (input_progress < 0.0) {
  32. // 1. If the x value of P1 is greater than zero, use a straight line that passes through P1 and P0 as the
  33. // tangent.
  34. if (x1 > 0.0)
  35. return y1 / x1 * input_progress;
  36. // 2. Otherwise, if the x value of P2 is greater than zero, use a straight line that passes through P2 and P0 as
  37. // the tangent.
  38. if (x2 > 0.0)
  39. return y2 / x2 * input_progress;
  40. // 3. Otherwise, let the output progress value be zero for all input progress values in the range [-∞, 0).
  41. return 0.0;
  42. }
  43. // - For input progress values greater than one,
  44. if (input_progress > 1.0) {
  45. // 1. If the x value of P2 is less than one, use a straight line that passes through P2 and P3 as the tangent.
  46. if (x2 < 1.0)
  47. return (1.0 - y2) / (1.0 - x2) * (input_progress - 1.0) + 1.0;
  48. // 2. Otherwise, if the x value of P1 is less than one, use a straight line that passes through P1 and P3 as the
  49. // tangent.
  50. if (x1 < 1.0)
  51. return (1.0 - y1) / (1.0 - x1) * (input_progress - 1.0) + 1.0;
  52. // 3. Otherwise, let the output progress value be one for all input progress values in the range (1, ∞].
  53. return 1.0;
  54. }
  55. // Note: The spec does not specify the precise algorithm for calculating values in the range [0, 1]:
  56. // "The evaluation of this curve is covered in many sources such as [FUND-COMP-GRAPHICS]."
  57. auto x = input_progress;
  58. auto solve = [&](auto t) {
  59. auto x = cubic_bezier_at(x1, x2, t);
  60. auto y = cubic_bezier_at(y1, y2, t);
  61. return CachedSample { x, y, t };
  62. };
  63. if (m_cached_x_samples.is_empty())
  64. m_cached_x_samples.append(solve(0.));
  65. size_t nearby_index = 0;
  66. if (auto found = binary_search(m_cached_x_samples, x, &nearby_index, [](auto x, auto& sample) {
  67. if (x > sample.x)
  68. return 1;
  69. if (x < sample.x)
  70. return -1;
  71. return 0;
  72. }))
  73. return found->y;
  74. if (nearby_index == m_cached_x_samples.size() || nearby_index + 1 == m_cached_x_samples.size()) {
  75. // Produce more samples until we have enough.
  76. auto last_t = m_cached_x_samples.is_empty() ? 0 : m_cached_x_samples.last().t;
  77. auto last_x = m_cached_x_samples.is_empty() ? 0 : m_cached_x_samples.last().x;
  78. while (last_x <= x) {
  79. last_t += 1. / 60.;
  80. auto solution = solve(last_t);
  81. m_cached_x_samples.append(solution);
  82. last_x = solution.x;
  83. }
  84. if (auto found = binary_search(m_cached_x_samples, x, &nearby_index, [](auto x, auto& sample) {
  85. if (x > sample.x)
  86. return 1;
  87. if (x < sample.x)
  88. return -1;
  89. return 0;
  90. }))
  91. return found->y;
  92. }
  93. // We have two samples on either side of the x value we want, so we can linearly interpolate between them.
  94. auto& sample1 = m_cached_x_samples[nearby_index];
  95. auto& sample2 = m_cached_x_samples[nearby_index + 1];
  96. auto factor = (x - sample1.x) / (sample2.x - sample1.x);
  97. return clamp(sample1.y + factor * (sample2.y - sample1.y), 0, 1);
  98. }
  99. // https://www.w3.org/TR/css-easing-1/#step-easing-algo
  100. double StepsTimingFunction::operator()(double input_progress, bool before_flag) const
  101. {
  102. // 1. Calculate the current step as floor(input progress value × steps).
  103. auto current_step = floor(input_progress * number_of_steps);
  104. // 2. If the step position property is one of:
  105. // - jump-start,
  106. // - jump-both,
  107. // increment current step by one.
  108. if (jump_at_start)
  109. current_step += 1;
  110. // 3. If both of the following conditions are true:
  111. // - the before flag is set, and
  112. // - input progress value × steps mod 1 equals zero (that is, if input progress value × steps is integral), then
  113. // decrement current step by one.
  114. auto step_progress = input_progress * number_of_steps;
  115. if (before_flag && trunc(step_progress) == step_progress)
  116. current_step -= 1;
  117. // 4. If input progress value ≥ 0 and current step < 0, let current step be zero.
  118. if (input_progress >= 0.0 && current_step < 0.0)
  119. current_step = 0.0;
  120. // 5. Calculate jumps based on the step position as follows:
  121. // jump-start or jump-end -> steps
  122. // jump-none -> steps - 1
  123. // jump-both -> steps + 1
  124. double jumps;
  125. if (jump_at_start ^ jump_at_end)
  126. jumps = number_of_steps;
  127. else if (jump_at_start && jump_at_end)
  128. jumps = number_of_steps + 1;
  129. else
  130. jumps = number_of_steps - 1;
  131. // 6. If input progress value ≤ 1 and current step > jumps, let current step be jumps.
  132. if (input_progress <= 1.0 && current_step > jumps)
  133. current_step = jumps;
  134. // 7. The output progress value is current step / jumps.
  135. return current_step / jumps;
  136. }
  137. double TimingFunction::operator()(double input_progress, bool before_flag) const
  138. {
  139. return function.visit([&](auto const& f) { return f(input_progress, before_flag); });
  140. }
  141. }