Roll src/third_party/WebKit d9c6159:8139f33 (svn 201974:201975)
[chromium-blink-merge.git] / ui / events / gesture_detection / velocity_tracker.cc
blob64583fdc25a31ad0e94e9cdc84beada1aa2fc43a
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.
5 #include "ui/events/gesture_detection/velocity_tracker.h"
7 #include <cmath>
9 #include "base/logging.h"
10 #include "ui/events/gesture_detection/motion_event.h"
12 using base::TimeDelta;
13 using base::TimeTicks;
15 namespace ui {
17 // Implements a particular velocity tracker algorithm.
18 class VelocityTrackerStrategy {
19 public:
20 virtual ~VelocityTrackerStrategy() {}
22 virtual void Clear() = 0;
23 virtual void ClearPointers(BitSet32 id_bits) = 0;
24 virtual void AddMovement(const base::TimeTicks& event_time,
25 BitSet32 id_bits,
26 const Position* positions) = 0;
27 virtual bool GetEstimator(uint32_t id, Estimator* out_estimator) const = 0;
29 protected:
30 VelocityTrackerStrategy() {}
33 namespace {
35 static_assert(MotionEvent::MAX_POINTER_ID < 32, "max pointer id too large");
37 // Threshold between ACTION_MOVE events for determining that a pointer has
38 // stopped moving. Some input devices do not send ACTION_MOVE events in the case
39 // where a pointer has stopped. We need to detect this case so that we can
40 // accurately predict the velocity after the pointer starts moving again.
41 const int kAssumePointerMoveStoppedTimeMs = 40;
43 // Threshold between ACTION_MOVE and ACTION_{UP|POINTER_UP} events for
44 // determining that a pointer has stopped moving. This is a larger threshold
45 // than |kAssumePointerMoveStoppedTimeMs|, as some devices may delay synthesis
46 // of ACTION_{UP|POINTER_UP} to reduce risk of noisy release.
47 const int kAssumePointerUpStoppedTimeMs = 80;
49 struct Position {
50 float x, y;
53 struct Estimator {
54 static const uint8_t kMaxDegree = 4;
56 // Estimator time base.
57 TimeTicks time;
59 // Polynomial coefficients describing motion in X and Y.
60 float xcoeff[kMaxDegree + 1], ycoeff[kMaxDegree + 1];
62 // Polynomial degree (number of coefficients), or zero if no information is
63 // available.
64 uint32_t degree;
66 // Confidence (coefficient of determination), between 0 (no fit)
67 // and 1 (perfect fit).
68 float confidence;
70 inline void Clear() {
71 time = TimeTicks();
72 degree = 0;
73 confidence = 0;
74 for (size_t i = 0; i <= kMaxDegree; i++) {
75 xcoeff[i] = 0;
76 ycoeff[i] = 0;
81 float VectorDot(const float* a, const float* b, uint32_t m) {
82 float r = 0;
83 while (m--) {
84 r += *(a++) * *(b++);
86 return r;
89 float VectorNorm(const float* a, uint32_t m) {
90 float r = 0;
91 while (m--) {
92 float t = *(a++);
93 r += t * t;
95 return sqrtf(r);
98 // Velocity tracker algorithm based on least-squares linear regression.
99 class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy {
100 public:
101 enum Weighting {
102 // No weights applied. All data points are equally reliable.
103 WEIGHTING_NONE,
105 // Weight by time delta. Data points clustered together are weighted less.
106 WEIGHTING_DELTA,
108 // Weight such that points within a certain horizon are weighed more than
109 // those outside of that horizon.
110 WEIGHTING_CENTRAL,
112 // Weight such that points older than a certain amount are weighed less.
113 WEIGHTING_RECENT,
116 enum Restriction {
117 // There's no restriction on the output of the velocity tracker.
118 RESTRICTION_NONE,
120 // If the velocity determined by the tracker is in a sufficiently different
121 // direction from the primary motion of the finger for the events being
122 // considered for velocity calculation, return a velocity of 0.
123 RESTRICTION_ALIGNED_DIRECTIONS
126 // Number of samples to keep.
127 static const uint8_t kHistorySize = 20;
129 // Degree must be no greater than Estimator::kMaxDegree.
130 LeastSquaresVelocityTrackerStrategy(
131 uint32_t degree,
132 Weighting weighting,
133 Restriction restriction);
134 ~LeastSquaresVelocityTrackerStrategy() override;
136 void Clear() override;
137 void ClearPointers(BitSet32 id_bits) override;
138 void AddMovement(const TimeTicks& event_time,
139 BitSet32 id_bits,
140 const Position* positions) override;
141 bool GetEstimator(uint32_t id, Estimator* out_estimator) const override;
143 private:
144 // Sample horizon.
145 // We don't use too much history by default since we want to react to quick
146 // changes in direction.
147 static const uint8_t kHorizonMS = 100;
149 struct Movement {
150 TimeTicks event_time;
151 BitSet32 id_bits;
152 Position positions[VelocityTracker::MAX_POINTERS];
154 inline const Position& GetPosition(uint32_t id) const {
155 return positions[id_bits.get_index_of_bit(id)];
159 float ChooseWeight(uint32_t index) const;
161 const uint32_t degree_;
162 const Weighting weighting_;
163 const Restriction restriction_;
164 uint32_t index_;
165 Movement movements_[kHistorySize];
168 // Velocity tracker algorithm that uses an IIR filter.
169 class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy {
170 public:
171 // Degree must be 1 or 2.
172 explicit IntegratingVelocityTrackerStrategy(uint32_t degree);
173 ~IntegratingVelocityTrackerStrategy() override;
175 void Clear() override;
176 void ClearPointers(BitSet32 id_bits) override;
177 void AddMovement(const TimeTicks& event_time,
178 BitSet32 id_bits,
179 const Position* positions) override;
180 bool GetEstimator(uint32_t id, Estimator* out_estimator) const override;
182 private:
183 // Current state estimate for a particular pointer.
184 struct State {
185 TimeTicks update_time;
186 uint32_t degree;
188 float xpos, xvel, xaccel;
189 float ypos, yvel, yaccel;
192 const uint32_t degree_;
193 BitSet32 pointer_id_bits_;
194 State mPointerState[MotionEvent::MAX_POINTER_ID + 1];
196 void InitState(State& state,
197 const TimeTicks& event_time,
198 float xpos,
199 float ypos) const;
200 void UpdateState(State& state,
201 const TimeTicks& event_time,
202 float xpos,
203 float ypos) const;
204 void PopulateEstimator(const State& state, Estimator* out_estimator) const;
207 VelocityTrackerStrategy* CreateStrategy(VelocityTracker::Strategy strategy) {
208 LeastSquaresVelocityTrackerStrategy::Weighting none =
209 LeastSquaresVelocityTrackerStrategy::WEIGHTING_NONE;
210 LeastSquaresVelocityTrackerStrategy::Restriction no_restriction =
211 LeastSquaresVelocityTrackerStrategy::RESTRICTION_NONE;
212 switch (strategy) {
213 case VelocityTracker::LSQ1:
214 return new LeastSquaresVelocityTrackerStrategy(1, none, no_restriction);
215 case VelocityTracker::LSQ2:
216 return new LeastSquaresVelocityTrackerStrategy(2, none, no_restriction);
217 case VelocityTracker::LSQ2_RESTRICTED:
218 return new LeastSquaresVelocityTrackerStrategy(
219 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_NONE,
220 LeastSquaresVelocityTrackerStrategy::RESTRICTION_ALIGNED_DIRECTIONS);
221 case VelocityTracker::LSQ3:
222 return new LeastSquaresVelocityTrackerStrategy(3, none, no_restriction);
223 case VelocityTracker::WLSQ2_DELTA:
224 return new LeastSquaresVelocityTrackerStrategy(
225 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA,
226 no_restriction);
227 case VelocityTracker::WLSQ2_CENTRAL:
228 return new LeastSquaresVelocityTrackerStrategy(
229 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL,
230 no_restriction);
231 case VelocityTracker::WLSQ2_RECENT:
232 return new LeastSquaresVelocityTrackerStrategy(
233 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT,
234 no_restriction);
235 case VelocityTracker::INT1:
236 return new IntegratingVelocityTrackerStrategy(1);
237 case VelocityTracker::INT2:
238 return new IntegratingVelocityTrackerStrategy(2);
240 NOTREACHED() << "Unrecognized velocity tracker strategy: " << strategy;
241 // Quadratic regression is a safe default.
242 return CreateStrategy(VelocityTracker::STRATEGY_DEFAULT);
245 } // namespace
247 // --- VelocityTracker ---
249 VelocityTracker::VelocityTracker(Strategy strategy)
250 : current_pointer_id_bits_(0),
251 active_pointer_id_(-1),
252 strategy_(CreateStrategy(strategy)) {}
254 VelocityTracker::~VelocityTracker() {}
256 void VelocityTracker::Clear() {
257 current_pointer_id_bits_.clear();
258 active_pointer_id_ = -1;
259 strategy_->Clear();
262 void VelocityTracker::ClearPointers(BitSet32 id_bits) {
263 BitSet32 remaining_id_bits(current_pointer_id_bits_.value & ~id_bits.value);
264 current_pointer_id_bits_ = remaining_id_bits;
266 if (active_pointer_id_ >= 0 && id_bits.has_bit(active_pointer_id_)) {
267 active_pointer_id_ = !remaining_id_bits.is_empty()
268 ? remaining_id_bits.first_marked_bit()
269 : -1;
272 strategy_->ClearPointers(id_bits);
275 void VelocityTracker::AddMovement(const TimeTicks& event_time,
276 BitSet32 id_bits,
277 const Position* positions) {
278 while (id_bits.count() > MAX_POINTERS)
279 id_bits.clear_last_marked_bit();
281 if ((current_pointer_id_bits_.value & id_bits.value) &&
282 (event_time - last_event_time_) >=
283 base::TimeDelta::FromMilliseconds(kAssumePointerMoveStoppedTimeMs)) {
284 // We have not received any movements for too long. Assume that all pointers
285 // have stopped.
286 strategy_->Clear();
288 last_event_time_ = event_time;
290 current_pointer_id_bits_ = id_bits;
291 if (active_pointer_id_ < 0 || !id_bits.has_bit(active_pointer_id_))
292 active_pointer_id_ = id_bits.is_empty() ? -1 : id_bits.first_marked_bit();
294 strategy_->AddMovement(event_time, id_bits, positions);
297 void VelocityTracker::AddMovement(const MotionEvent& event) {
298 int32_t actionMasked = event.GetAction();
300 switch (actionMasked) {
301 case MotionEvent::ACTION_DOWN:
302 // case MotionEvent::HOVER_ENTER:
303 // Clear all pointers on down before adding the new movement.
304 Clear();
305 break;
306 case MotionEvent::ACTION_POINTER_DOWN: {
307 // Start a new movement trace for a pointer that just went down.
308 // We do this on down instead of on up because the client may want to
309 // query the final velocity for a pointer that just went up.
310 BitSet32 downIdBits;
311 downIdBits.mark_bit(event.GetPointerId(event.GetActionIndex()));
312 ClearPointers(downIdBits);
313 break;
315 case MotionEvent::ACTION_MOVE:
316 // case MotionEvent::ACTION_HOVER_MOVE:
317 break;
318 case MotionEvent::ACTION_UP:
319 case MotionEvent::ACTION_POINTER_UP:
320 // Note that ACTION_UP and ACTION_POINTER_UP always report the last known
321 // position of the pointers that went up. ACTION_POINTER_UP does include
322 // the new position of pointers that remained down but we will also
323 // receive an ACTION_MOVE with this information if any of them actually
324 // moved. Since we don't know how many pointers will be going up at once
325 // it makes sense to just wait for the following ACTION_MOVE before adding
326 // the movement. However, if the up event itself is delayed because of
327 // (difficult albeit possible) prolonged stationary screen contact, assume
328 // that motion has stopped.
329 if ((event.GetEventTime() - last_event_time_) >=
330 base::TimeDelta::FromMilliseconds(kAssumePointerUpStoppedTimeMs))
331 strategy_->Clear();
332 return;
333 default:
334 // Ignore all other actions because they do not convey any new information
335 // about pointer movement. We also want to preserve the last known
336 // velocity of the pointers.
337 return;
340 size_t pointer_count = event.GetPointerCount();
341 if (pointer_count > MAX_POINTERS) {
342 pointer_count = MAX_POINTERS;
345 BitSet32 id_bits;
346 for (size_t i = 0; i < pointer_count; i++) {
347 id_bits.mark_bit(event.GetPointerId(i));
350 uint32_t pointer_index[MAX_POINTERS];
351 for (size_t i = 0; i < pointer_count; i++) {
352 pointer_index[i] = id_bits.get_index_of_bit(event.GetPointerId(i));
355 Position positions[MAX_POINTERS];
356 size_t historySize = event.GetHistorySize();
357 for (size_t h = 0; h < historySize; h++) {
358 for (size_t i = 0; i < pointer_count; i++) {
359 uint32_t index = pointer_index[i];
360 positions[index].x = event.GetHistoricalX(i, h);
361 positions[index].y = event.GetHistoricalY(i, h);
363 AddMovement(event.GetHistoricalEventTime(h), id_bits, positions);
366 for (size_t i = 0; i < pointer_count; i++) {
367 uint32_t index = pointer_index[i];
368 positions[index].x = event.GetX(i);
369 positions[index].y = event.GetY(i);
371 AddMovement(event.GetEventTime(), id_bits, positions);
374 bool VelocityTracker::GetVelocity(uint32_t id,
375 float* out_vx,
376 float* out_vy) const {
377 Estimator estimator;
378 if (GetEstimator(id, &estimator) && estimator.degree >= 1) {
379 *out_vx = estimator.xcoeff[1];
380 *out_vy = estimator.ycoeff[1];
381 return true;
383 *out_vx = 0;
384 *out_vy = 0;
385 return false;
388 void LeastSquaresVelocityTrackerStrategy::AddMovement(
389 const TimeTicks& event_time,
390 BitSet32 id_bits,
391 const Position* positions) {
392 if (++index_ == kHistorySize) {
393 index_ = 0;
396 Movement& movement = movements_[index_];
397 movement.event_time = event_time;
398 movement.id_bits = id_bits;
399 uint32_t count = id_bits.count();
400 for (uint32_t i = 0; i < count; i++) {
401 movement.positions[i] = positions[i];
405 bool VelocityTracker::GetEstimator(uint32_t id,
406 Estimator* out_estimator) const {
407 return strategy_->GetEstimator(id, out_estimator);
410 // --- LeastSquaresVelocityTrackerStrategy ---
412 LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(
413 uint32_t degree,
414 Weighting weighting,
415 Restriction restriction)
416 : degree_(degree),
417 weighting_(weighting),
418 restriction_(restriction) {
419 DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::kMaxDegree));
420 Clear();
423 LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {}
425 void LeastSquaresVelocityTrackerStrategy::Clear() {
426 index_ = 0;
427 movements_[0].id_bits.clear();
431 * Solves a linear least squares problem to obtain a N degree polynomial that
432 * fits the specified input data as nearly as possible.
434 * Returns true if a solution is found, false otherwise.
436 * The input consists of two vectors of data points X and Y with indices 0..m-1
437 * along with a weight vector W of the same size.
439 * The output is a vector B with indices 0..n that describes a polynomial
440 * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1]
441 * X[i] * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is
442 * minimized.
444 * Accordingly, the weight vector W should be initialized by the caller with the
445 * reciprocal square root of the variance of the error in each input data point.
446 * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 /
447 * stddev(Y[i]).
448 * The weights express the relative importance of each data point. If the
449 * weights are* all 1, then the data points are considered to be of equal
450 * importance when fitting the polynomial. It is a good idea to choose weights
451 * that diminish the importance of data points that may have higher than usual
452 * error margins.
454 * Errors among data points are assumed to be independent. W is represented
455 * here as a vector although in the literature it is typically taken to be a
456 * diagonal matrix.
458 * That is to say, the function that generated the input data can be
459 * approximated by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
461 * The coefficient of determination (R^2) is also returned to describe the
462 * goodness of fit of the model for the given data. It is a value between 0
463 * and 1, where 1 indicates perfect correspondence.
465 * This function first expands the X vector to a m by n matrix A such that
466 * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then
467 * multiplies it by w[i]./
469 * Then it calculates the QR decomposition of A yielding an m by m orthonormal
470 * matrix Q and an m by n upper triangular matrix R. Because R is upper
471 * triangular (lower part is all zeroes), we can simplify the decomposition into
472 * an m by n matrix Q1 and a n by n matrix R1 such that A = Q1 R1.
474 * Finally we solve the system of linear equations given by
475 * R1 B = (Qtranspose W Y) to find B.
477 * For efficiency, we lay out A and Q column-wise in memory because we
478 * frequently operate on the column vectors. Conversely, we lay out R row-wise.
480 * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
481 * http://en.wikipedia.org/wiki/Gram-Schmidt
483 static bool SolveLeastSquares(const float* x,
484 const float* y,
485 const float* w,
486 uint32_t m,
487 uint32_t n,
488 float* out_b,
489 float* out_det) {
490 // MSVC does not support variable-length arrays (used by the original Android
491 // implementation of this function).
492 #if defined(COMPILER_MSVC)
493 const uint32_t M_ARRAY_LENGTH =
494 LeastSquaresVelocityTrackerStrategy::kHistorySize;
495 const uint32_t N_ARRAY_LENGTH = Estimator::kMaxDegree;
496 DCHECK_LE(m, M_ARRAY_LENGTH);
497 DCHECK_LE(n, N_ARRAY_LENGTH);
498 #else
499 const uint32_t M_ARRAY_LENGTH = m;
500 const uint32_t N_ARRAY_LENGTH = n;
501 #endif
503 // Expand the X vector to a matrix A, pre-multiplied by the weights.
504 float a[N_ARRAY_LENGTH][M_ARRAY_LENGTH]; // column-major order
505 for (uint32_t h = 0; h < m; h++) {
506 a[0][h] = w[h];
507 for (uint32_t i = 1; i < n; i++) {
508 a[i][h] = a[i - 1][h] * x[h];
512 // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
514 // Orthonormal basis, column-major order.
515 float q[N_ARRAY_LENGTH][M_ARRAY_LENGTH];
516 // Upper triangular matrix, row-major order.
517 float r[N_ARRAY_LENGTH][N_ARRAY_LENGTH];
518 for (uint32_t j = 0; j < n; j++) {
519 for (uint32_t h = 0; h < m; h++) {
520 q[j][h] = a[j][h];
522 for (uint32_t i = 0; i < j; i++) {
523 float dot = VectorDot(&q[j][0], &q[i][0], m);
524 for (uint32_t h = 0; h < m; h++) {
525 q[j][h] -= dot * q[i][h];
529 float norm = VectorNorm(&q[j][0], m);
530 if (norm < 0.000001f) {
531 // vectors are linearly dependent or zero so no solution
532 return false;
535 float invNorm = 1.0f / norm;
536 for (uint32_t h = 0; h < m; h++) {
537 q[j][h] *= invNorm;
539 for (uint32_t i = 0; i < n; i++) {
540 r[j][i] = i < j ? 0 : VectorDot(&q[j][0], &a[i][0], m);
544 // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
545 // We just work from bottom-right to top-left calculating B's coefficients.
546 float wy[M_ARRAY_LENGTH];
547 for (uint32_t h = 0; h < m; h++) {
548 wy[h] = y[h] * w[h];
550 for (uint32_t i = n; i-- != 0;) {
551 out_b[i] = VectorDot(&q[i][0], wy, m);
552 for (uint32_t j = n - 1; j > i; j--) {
553 out_b[i] -= r[i][j] * out_b[j];
555 out_b[i] /= r[i][i];
558 // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
559 // SSerr is the residual sum of squares (variance of the error),
560 // and SStot is the total sum of squares (variance of the data) where each
561 // has been weighted.
562 float ymean = 0;
563 for (uint32_t h = 0; h < m; h++) {
564 ymean += y[h];
566 ymean /= m;
568 float sserr = 0;
569 float sstot = 0;
570 for (uint32_t h = 0; h < m; h++) {
571 float err = y[h] - out_b[0];
572 float term = 1;
573 for (uint32_t i = 1; i < n; i++) {
574 term *= x[h];
575 err -= term * out_b[i];
577 sserr += w[h] * w[h] * err * err;
578 float var = y[h] - ymean;
579 sstot += w[h] * w[h] * var * var;
581 *out_det = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
582 return true;
585 void LeastSquaresVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) {
586 BitSet32 remaining_id_bits(movements_[index_].id_bits.value & ~id_bits.value);
587 movements_[index_].id_bits = remaining_id_bits;
590 bool LeastSquaresVelocityTrackerStrategy::GetEstimator(
591 uint32_t id,
592 Estimator* out_estimator) const {
593 out_estimator->Clear();
595 // Iterate over movement samples in reverse time order and collect samples.
596 float x[kHistorySize];
597 float y[kHistorySize];
598 float w[kHistorySize];
599 float time[kHistorySize];
600 uint32_t m = 0;
601 uint32_t index = index_;
602 const base::TimeDelta horizon = base::TimeDelta::FromMilliseconds(kHorizonMS);
603 const Movement& newest_movement = movements_[index_];
604 const Movement* first_movement = nullptr;
606 do {
607 const Movement& movement = movements_[index];
608 if (!movement.id_bits.has_bit(id))
609 break;
611 first_movement = &movement;
612 TimeDelta age = newest_movement.event_time - movement.event_time;
613 if (age > horizon)
614 break;
616 const Position& position = movement.GetPosition(id);
617 x[m] = position.x;
618 y[m] = position.y;
619 w[m] = ChooseWeight(index);
620 time[m] = -static_cast<float>(age.InSecondsF());
621 index = (index == 0 ? kHistorySize : index) - 1;
622 } while (++m < kHistorySize);
624 if (m == 0)
625 return false; // no data
627 // Calculate a least squares polynomial fit.
628 uint32_t degree = degree_;
629 if (degree > m - 1)
630 degree = m - 1;
632 if (degree >= 1) {
633 float xdet, ydet;
634 uint32_t n = degree + 1;
635 if (SolveLeastSquares(time, x, w, m, n, out_estimator->xcoeff, &xdet) &&
636 SolveLeastSquares(time, y, w, m, n, out_estimator->ycoeff, &ydet)) {
637 if (restriction_ == RESTRICTION_ALIGNED_DIRECTIONS) {
638 DCHECK(first_movement);
639 float dx = newest_movement.GetPosition(id).x -
640 first_movement->GetPosition(id).x;
641 float dy = newest_movement.GetPosition(id).y -
642 first_movement->GetPosition(id).y;
644 // If the velocity is in a sufficiently different direction from the
645 // primary movement, ignore it.
646 if (out_estimator->xcoeff[1] * dx + out_estimator->ycoeff[1] * dy < 0)
647 return false;
650 out_estimator->time = newest_movement.event_time;
651 out_estimator->degree = degree;
652 out_estimator->confidence = xdet * ydet;
653 return true;
657 // No velocity data available for this pointer, but we do have its current
658 // position.
659 out_estimator->xcoeff[0] = x[0];
660 out_estimator->ycoeff[0] = y[0];
661 out_estimator->time = newest_movement.event_time;
662 out_estimator->degree = 0;
663 out_estimator->confidence = 1;
664 return true;
667 float LeastSquaresVelocityTrackerStrategy::ChooseWeight(uint32_t index) const {
668 switch (weighting_) {
669 case WEIGHTING_DELTA: {
670 // Weight points based on how much time elapsed between them and the next
671 // point so that points that "cover" a shorter time span are weighed less.
672 // delta 0ms: 0.5
673 // delta 10ms: 1.0
674 if (index == index_) {
675 return 1.0f;
677 uint32_t next_index = (index + 1) % kHistorySize;
678 float delta_millis =
679 static_cast<float>((movements_[next_index].event_time -
680 movements_[index].event_time).InMillisecondsF());
681 if (delta_millis < 0)
682 return 0.5f;
683 if (delta_millis < 10)
684 return 0.5f + delta_millis * 0.05f;
686 return 1.0f;
689 case WEIGHTING_CENTRAL: {
690 // Weight points based on their age, weighing very recent and very old
691 // points less.
692 // age 0ms: 0.5
693 // age 10ms: 1.0
694 // age 50ms: 1.0
695 // age 60ms: 0.5
696 float age_millis =
697 static_cast<float>((movements_[index_].event_time -
698 movements_[index].event_time).InMillisecondsF());
699 if (age_millis < 0)
700 return 0.5f;
701 if (age_millis < 10)
702 return 0.5f + age_millis * 0.05f;
703 if (age_millis < 50)
704 return 1.0f;
705 if (age_millis < 60)
706 return 0.5f + (60 - age_millis) * 0.05f;
708 return 0.5f;
711 case WEIGHTING_RECENT: {
712 // Weight points based on their age, weighing older points less.
713 // age 0ms: 1.0
714 // age 50ms: 1.0
715 // age 100ms: 0.5
716 float age_millis =
717 static_cast<float>((movements_[index_].event_time -
718 movements_[index].event_time).InMillisecondsF());
719 if (age_millis < 50) {
720 return 1.0f;
722 if (age_millis < 100) {
723 return 0.5f + (100 - age_millis) * 0.01f;
725 return 0.5f;
728 case WEIGHTING_NONE:
729 default:
730 return 1.0f;
734 // --- IntegratingVelocityTrackerStrategy ---
736 IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(
737 uint32_t degree)
738 : degree_(degree) {
739 DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::kMaxDegree));
742 IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {}
744 void IntegratingVelocityTrackerStrategy::Clear() { pointer_id_bits_.clear(); }
746 void IntegratingVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) {
747 pointer_id_bits_.value &= ~id_bits.value;
750 void IntegratingVelocityTrackerStrategy::AddMovement(
751 const TimeTicks& event_time,
752 BitSet32 id_bits,
753 const Position* positions) {
754 uint32_t index = 0;
755 for (BitSet32 iter_id_bits(id_bits); !iter_id_bits.is_empty();) {
756 uint32_t id = iter_id_bits.clear_first_marked_bit();
757 State& state = mPointerState[id];
758 const Position& position = positions[index++];
759 if (pointer_id_bits_.has_bit(id))
760 UpdateState(state, event_time, position.x, position.y);
761 else
762 InitState(state, event_time, position.x, position.y);
765 pointer_id_bits_ = id_bits;
768 bool IntegratingVelocityTrackerStrategy::GetEstimator(
769 uint32_t id,
770 Estimator* out_estimator) const {
771 out_estimator->Clear();
773 if (pointer_id_bits_.has_bit(id)) {
774 const State& state = mPointerState[id];
775 PopulateEstimator(state, out_estimator);
776 return true;
779 return false;
782 void IntegratingVelocityTrackerStrategy::InitState(State& state,
783 const TimeTicks& event_time,
784 float xpos,
785 float ypos) const {
786 state.update_time = event_time;
787 state.degree = 0;
788 state.xpos = xpos;
789 state.xvel = 0;
790 state.xaccel = 0;
791 state.ypos = ypos;
792 state.yvel = 0;
793 state.yaccel = 0;
796 void IntegratingVelocityTrackerStrategy::UpdateState(
797 State& state,
798 const TimeTicks& event_time,
799 float xpos,
800 float ypos) const {
801 const base::TimeDelta MIN_TIME_DELTA = TimeDelta::FromMicroseconds(2);
802 const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds
804 if (event_time <= state.update_time + MIN_TIME_DELTA)
805 return;
807 float dt = static_cast<float>((event_time - state.update_time).InSecondsF());
808 state.update_time = event_time;
810 float xvel = (xpos - state.xpos) / dt;
811 float yvel = (ypos - state.ypos) / dt;
812 if (state.degree == 0) {
813 state.xvel = xvel;
814 state.yvel = yvel;
815 state.degree = 1;
816 } else {
817 float alpha = dt / (FILTER_TIME_CONSTANT + dt);
818 if (degree_ == 1) {
819 state.xvel += (xvel - state.xvel) * alpha;
820 state.yvel += (yvel - state.yvel) * alpha;
821 } else {
822 float xaccel = (xvel - state.xvel) / dt;
823 float yaccel = (yvel - state.yvel) / dt;
824 if (state.degree == 1) {
825 state.xaccel = xaccel;
826 state.yaccel = yaccel;
827 state.degree = 2;
828 } else {
829 state.xaccel += (xaccel - state.xaccel) * alpha;
830 state.yaccel += (yaccel - state.yaccel) * alpha;
832 state.xvel += (state.xaccel * dt) * alpha;
833 state.yvel += (state.yaccel * dt) * alpha;
836 state.xpos = xpos;
837 state.ypos = ypos;
840 void IntegratingVelocityTrackerStrategy::PopulateEstimator(
841 const State& state,
842 Estimator* out_estimator) const {
843 out_estimator->time = state.update_time;
844 out_estimator->confidence = 1.0f;
845 out_estimator->degree = state.degree;
846 out_estimator->xcoeff[0] = state.xpos;
847 out_estimator->xcoeff[1] = state.xvel;
848 out_estimator->xcoeff[2] = state.xaccel / 2;
849 out_estimator->ycoeff[0] = state.ypos;
850 out_estimator->ycoeff[1] = state.yvel;
851 out_estimator->ycoeff[2] = state.yaccel / 2;
854 } // namespace ui