velocity_tracker_unittest.cc 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263
  1. // Copyright 2014 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include <stddef.h>
  5. #include <memory>
  6. #include "base/notreached.h"
  7. #include "base/time/time.h"
  8. #include "testing/gtest/include/gtest/gtest.h"
  9. #include "ui/events/gesture_detection/velocity_tracker_state.h"
  10. #include "ui/events/test/motion_event_test_utils.h"
  11. #include "ui/gfx/geometry/point_f.h"
  12. #include "ui/gfx/geometry/vector2d_f.h"
  13. using base::TimeTicks;
  14. using ui::test::MockMotionEvent;
  15. namespace ui {
  16. namespace {
  17. const base::TimeDelta kTenMillis = base::Milliseconds(10);
  18. const base::TimeDelta kOneSecond = base::Seconds(1);
  19. const float kEpsilson = .01f;
  20. const char* GetStrategyName(VelocityTracker::Strategy strategy) {
  21. switch (strategy) {
  22. case VelocityTracker::LSQ1: return "LSQ1";
  23. case VelocityTracker::LSQ2: return "LSQ2";
  24. case VelocityTracker::LSQ2_RESTRICTED: return "LSQ2_RESTRICTED";
  25. case VelocityTracker::LSQ3: return "LSQ3";
  26. case VelocityTracker::WLSQ2_DELTA: return "WLSQ2_DELTA";
  27. case VelocityTracker::WLSQ2_CENTRAL: return "WLSQ2_CENTRAL";
  28. case VelocityTracker::WLSQ2_RECENT: return "WLSQ2_RECENT";
  29. case VelocityTracker::INT1: return "INT1";
  30. case VelocityTracker::INT2: return "INT2";
  31. }
  32. NOTREACHED() << "Invalid strategy";
  33. return "";
  34. }
  35. } // namespace
  36. class VelocityTrackerTest : public testing::Test {
  37. public:
  38. VelocityTrackerTest() {}
  39. ~VelocityTrackerTest() override {}
  40. protected:
  41. static MockMotionEvent Sample(MotionEvent::Action action,
  42. const gfx::PointF& p0,
  43. TimeTicks t0,
  44. const gfx::Vector2dF& v,
  45. base::TimeDelta dt) {
  46. const gfx::PointF p = p0 + ScaleVector2d(v, dt.InSecondsF());
  47. return MockMotionEvent(action, t0 + dt, p.x(), p.y());
  48. }
  49. static void ApplyMovementSequence(VelocityTrackerState* state,
  50. const gfx::PointF& p0,
  51. const gfx::Vector2dF& v,
  52. TimeTicks t0,
  53. base::TimeDelta t,
  54. size_t samples) {
  55. EXPECT_TRUE(samples);
  56. if (!samples)
  57. return;
  58. const base::TimeDelta dt = t / samples;
  59. state->AddMovement(Sample(MotionEvent::Action::DOWN, p0, t0, v, dt * 0));
  60. ApplyMovement(state, p0, v, t0, t, samples);
  61. state->AddMovement(Sample(MotionEvent::Action::UP, p0, t0, v, t));
  62. }
  63. static void ApplyMovement(VelocityTrackerState* state,
  64. const gfx::PointF& p0,
  65. const gfx::Vector2dF& v,
  66. TimeTicks t0,
  67. base::TimeDelta t,
  68. size_t samples) {
  69. EXPECT_TRUE(samples);
  70. if (!samples)
  71. return;
  72. const base::TimeDelta dt = t / samples;
  73. for (size_t i = 0; i < samples; ++i)
  74. state->AddMovement(Sample(MotionEvent::Action::MOVE, p0, t0, v, dt * i));
  75. }
  76. };
  77. TEST_F(VelocityTrackerTest, Basic) {
  78. const gfx::PointF p0(0, 0);
  79. const gfx::Vector2dF v(0, 500);
  80. const size_t samples = 60;
  81. for (int i = 0; i <= VelocityTracker::STRATEGY_MAX; ++i) {
  82. VelocityTracker::Strategy strategy =
  83. static_cast<VelocityTracker::Strategy>(i);
  84. SCOPED_TRACE(GetStrategyName(strategy));
  85. VelocityTrackerState state(strategy);
  86. // Default state should report zero velocity.
  87. EXPECT_EQ(0, state.GetXVelocity(0));
  88. EXPECT_EQ(0, state.GetYVelocity(0));
  89. // Sample a constant velocity sequence.
  90. ApplyMovementSequence(&state, p0, v, TimeTicks::Now(), kOneSecond, samples);
  91. // The computed velocity should match that of the input.
  92. state.ComputeCurrentVelocity(1000, 20000);
  93. EXPECT_NEAR(v.x(), state.GetXVelocity(0), kEpsilson * v.x());
  94. EXPECT_NEAR(v.y(), state.GetYVelocity(0), kEpsilson * v.y());
  95. // A pointer ID of -1 should report the velocity of the active pointer.
  96. EXPECT_NEAR(v.x(), state.GetXVelocity(-1), kEpsilson * v.x());
  97. EXPECT_NEAR(v.y(), state.GetYVelocity(-1), kEpsilson * v.y());
  98. // Invalid pointer ID's should report zero velocity.
  99. EXPECT_EQ(0, state.GetXVelocity(1));
  100. EXPECT_EQ(0, state.GetYVelocity(1));
  101. EXPECT_EQ(0, state.GetXVelocity(7));
  102. EXPECT_EQ(0, state.GetYVelocity(7));
  103. }
  104. }
  105. TEST_F(VelocityTrackerTest, MaxVelocity) {
  106. const gfx::PointF p0(0, 0);
  107. const gfx::Vector2dF v(-50000, 50000);
  108. const size_t samples = 3;
  109. const base::TimeDelta dt = kTenMillis * 2;
  110. VelocityTrackerState state(VelocityTracker::Strategy::LSQ2);
  111. ApplyMovementSequence(&state, p0, v, TimeTicks::Now(), dt, samples);
  112. // The computed velocity should be restricted to the provided maximum.
  113. state.ComputeCurrentVelocity(1000, 100);
  114. EXPECT_NEAR(-100, state.GetXVelocity(0), kEpsilson);
  115. EXPECT_NEAR(100, state.GetYVelocity(0), kEpsilson);
  116. state.ComputeCurrentVelocity(1000, 1000);
  117. EXPECT_NEAR(-1000, state.GetXVelocity(0), kEpsilson);
  118. EXPECT_NEAR(1000, state.GetYVelocity(0), kEpsilson);
  119. }
  120. TEST_F(VelocityTrackerTest, VaryingVelocity) {
  121. const gfx::PointF p0(0, 0);
  122. const gfx::Vector2dF vFast(0, 500);
  123. const gfx::Vector2dF vSlow = ScaleVector2d(vFast, 0.5f);
  124. const size_t samples = 12;
  125. for (int i = 0; i <= VelocityTracker::STRATEGY_MAX; ++i) {
  126. VelocityTracker::Strategy strategy =
  127. static_cast<VelocityTracker::Strategy>(i);
  128. SCOPED_TRACE(GetStrategyName(strategy));
  129. VelocityTrackerState state(strategy);
  130. base::TimeTicks t0 = base::TimeTicks::Now();
  131. base::TimeDelta dt = kTenMillis * 10;
  132. state.AddMovement(
  133. Sample(MotionEvent::Action::DOWN, p0, t0, vFast, base::TimeDelta()));
  134. // Apply some fast movement and compute the velocity.
  135. gfx::PointF pCurr = p0;
  136. base::TimeTicks tCurr = t0;
  137. ApplyMovement(&state, pCurr, vFast, tCurr, dt, samples);
  138. state.ComputeCurrentVelocity(1000, 20000);
  139. float vOldY = state.GetYVelocity(0);
  140. // Apply some slow movement.
  141. pCurr += ScaleVector2d(vFast, dt.InSecondsF());
  142. tCurr += dt;
  143. ApplyMovement(&state, pCurr, vSlow, tCurr, dt, samples);
  144. // The computed velocity should have decreased.
  145. state.ComputeCurrentVelocity(1000, 20000);
  146. float vCurrentY = state.GetYVelocity(0);
  147. EXPECT_GT(vFast.y(), vCurrentY);
  148. EXPECT_GT(vOldY, vCurrentY);
  149. vOldY = vCurrentY;
  150. // Apply some additional fast movement.
  151. pCurr += ScaleVector2d(vSlow, dt.InSecondsF());
  152. tCurr += dt;
  153. ApplyMovement(&state, pCurr, vFast, tCurr, dt, samples);
  154. // The computed velocity should have increased.
  155. state.ComputeCurrentVelocity(1000, 20000);
  156. vCurrentY = state.GetYVelocity(0);
  157. EXPECT_LT(vSlow.y(), vCurrentY);
  158. EXPECT_LT(vOldY, vCurrentY);
  159. }
  160. }
  161. TEST_F(VelocityTrackerTest, DelayedActionUp) {
  162. const gfx::PointF p0(0, 0);
  163. const gfx::Vector2dF v(-50000, 50000);
  164. const size_t samples = 10;
  165. const base::TimeTicks t0 = base::TimeTicks::Now();
  166. const base::TimeDelta dt = kTenMillis * 2;
  167. VelocityTrackerState state(VelocityTracker::Strategy::LSQ2);
  168. state.AddMovement(
  169. Sample(MotionEvent::Action::DOWN, p0, t0, v, base::TimeDelta()));
  170. // Apply the movement and verify a (non-zero) velocity.
  171. ApplyMovement(&state, p0, v, t0, dt, samples);
  172. state.ComputeCurrentVelocity(1000, 1000);
  173. EXPECT_NEAR(-1000, state.GetXVelocity(0), kEpsilson);
  174. EXPECT_NEAR(1000, state.GetYVelocity(0), kEpsilson);
  175. // Apply the delayed Action::UP.
  176. const gfx::PointF p1 = p0 + ScaleVector2d(v, dt.InSecondsF());
  177. const base::TimeTicks t1 = t0 + dt + kTenMillis * 10;
  178. state.AddMovement(
  179. Sample(MotionEvent::Action::UP, p1, t1, v, base::TimeDelta()));
  180. // The tracked velocity should have been reset.
  181. state.ComputeCurrentVelocity(1000, 1000);
  182. EXPECT_EQ(0.f, state.GetXVelocity(0));
  183. EXPECT_EQ(0.f, state.GetYVelocity(0));
  184. }
  185. // Tests that a rapid deceleration won't result in a velocity going in the
  186. // opposite direction to the pointers primary movement, with the LSQ_RESTRICTED
  187. // strategy. See crbug.com/417855.
  188. TEST_F(VelocityTrackerTest, NoDirectionReversal) {
  189. VelocityTrackerState state_unrestricted(VelocityTracker::LSQ2);
  190. VelocityTrackerState state_restricted(VelocityTracker::LSQ2_RESTRICTED);
  191. const base::TimeTicks t0 = base::TimeTicks::Now();
  192. const base::TimeDelta dt = base::Milliseconds(1);
  193. const size_t samples = 60;
  194. gfx::PointF p(0, 0);
  195. MockMotionEvent m1(MotionEvent::Action::DOWN, t0, p.x(), p.y());
  196. state_unrestricted.AddMovement(m1);
  197. state_restricted.AddMovement(m1);
  198. for (size_t i = 0; i < samples; ++i) {
  199. if (i < 50)
  200. p.set_y(p.y() + 10);
  201. MockMotionEvent mi(MotionEvent::Action::MOVE, t0 + dt * i, p.x(), p.y());
  202. state_unrestricted.AddMovement(mi);
  203. state_restricted.AddMovement(mi);
  204. }
  205. // The computed velocity be zero, as we stopped at the end of the gesture. In
  206. // particular, it should not be negative, as all movement was in the positive
  207. // direction.
  208. state_restricted.ComputeCurrentVelocity(1000, 20000);
  209. EXPECT_EQ(0, state_restricted.GetXVelocity(0));
  210. EXPECT_EQ(0, state_restricted.GetYVelocity(0));
  211. // This does not hold for the unrestricted LSQ2 strategy.
  212. state_unrestricted.ComputeCurrentVelocity(1000, 20000);
  213. EXPECT_EQ(0, state_unrestricted.GetXVelocity(0));
  214. // Y velocity is negative, despite the fact that the finger only moved in the
  215. // positive y direction.
  216. EXPECT_GT(0, state_unrestricted.GetYVelocity(0));
  217. }
  218. } // namespace ui