Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / ui / events / gesture_detection / velocity_tracker.cc
blob16454e80c3eec6fa301554c8c560dc57e430710a
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 COMPILE_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 enum { MAX_DEGREE = 4 };
56 // Estimator time base.
57 TimeTicks time;
59 // Polynomial coefficients describing motion in X and Y.
60 float xcoeff[MAX_DEGREE + 1], ycoeff[MAX_DEGREE + 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 <= MAX_DEGREE; 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 // Number of samples to keep.
117 enum { HISTORY_SIZE = 20 };
119 // Degree must be no greater than Estimator::MAX_DEGREE.
120 LeastSquaresVelocityTrackerStrategy(uint32_t degree,
121 Weighting weighting = WEIGHTING_NONE);
122 virtual ~LeastSquaresVelocityTrackerStrategy();
124 virtual void Clear() OVERRIDE;
125 virtual void ClearPointers(BitSet32 id_bits) OVERRIDE;
126 virtual void AddMovement(const TimeTicks& event_time,
127 BitSet32 id_bits,
128 const Position* positions) OVERRIDE;
129 virtual bool GetEstimator(uint32_t id,
130 Estimator* out_estimator) const OVERRIDE;
132 private:
133 // Sample horizon.
134 // We don't use too much history by default since we want to react to quick
135 // changes in direction.
136 enum { HORIZON_MS = 100 };
138 struct Movement {
139 TimeTicks event_time;
140 BitSet32 id_bits;
141 Position positions[VelocityTracker::MAX_POINTERS];
143 inline const Position& GetPosition(uint32_t id) const {
144 return positions[id_bits.get_index_of_bit(id)];
148 float ChooseWeight(uint32_t index) const;
150 const uint32_t degree_;
151 const Weighting weighting_;
152 uint32_t index_;
153 Movement movements_[HISTORY_SIZE];
156 // Velocity tracker algorithm that uses an IIR filter.
157 class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy {
158 public:
159 // Degree must be 1 or 2.
160 explicit IntegratingVelocityTrackerStrategy(uint32_t degree);
161 virtual ~IntegratingVelocityTrackerStrategy();
163 virtual void Clear() OVERRIDE;
164 virtual void ClearPointers(BitSet32 id_bits) OVERRIDE;
165 virtual void AddMovement(const TimeTicks& event_time,
166 BitSet32 id_bits,
167 const Position* positions) OVERRIDE;
168 virtual bool GetEstimator(uint32_t id,
169 Estimator* out_estimator) const OVERRIDE;
171 private:
172 // Current state estimate for a particular pointer.
173 struct State {
174 TimeTicks update_time;
175 uint32_t degree;
177 float xpos, xvel, xaccel;
178 float ypos, yvel, yaccel;
181 const uint32_t degree_;
182 BitSet32 pointer_id_bits_;
183 State mPointerState[MotionEvent::MAX_POINTER_ID + 1];
185 void InitState(State& state,
186 const TimeTicks& event_time,
187 float xpos,
188 float ypos) const;
189 void UpdateState(State& state,
190 const TimeTicks& event_time,
191 float xpos,
192 float ypos) const;
193 void PopulateEstimator(const State& state, Estimator* out_estimator) const;
196 VelocityTrackerStrategy* CreateStrategy(VelocityTracker::Strategy strategy) {
197 switch (strategy) {
198 case VelocityTracker::LSQ1:
199 return new LeastSquaresVelocityTrackerStrategy(1);
200 case VelocityTracker::LSQ2:
201 return new LeastSquaresVelocityTrackerStrategy(2);
202 case VelocityTracker::LSQ3:
203 return new LeastSquaresVelocityTrackerStrategy(3);
204 case VelocityTracker::WLSQ2_DELTA:
205 return new LeastSquaresVelocityTrackerStrategy(
206 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA);
207 case VelocityTracker::WLSQ2_CENTRAL:
208 return new LeastSquaresVelocityTrackerStrategy(
209 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL);
210 case VelocityTracker::WLSQ2_RECENT:
211 return new LeastSquaresVelocityTrackerStrategy(
212 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT);
213 case VelocityTracker::INT1:
214 return new IntegratingVelocityTrackerStrategy(1);
215 case VelocityTracker::INT2:
216 return new IntegratingVelocityTrackerStrategy(2);
218 NOTREACHED() << "Unrecognized velocity tracker strategy: " << strategy;
219 return CreateStrategy(VelocityTracker::STRATEGY_DEFAULT);
222 } // namespace
224 // --- VelocityTracker ---
226 VelocityTracker::VelocityTracker()
227 : current_pointer_id_bits_(0),
228 active_pointer_id_(-1),
229 strategy_(CreateStrategy(STRATEGY_DEFAULT)) {}
231 VelocityTracker::VelocityTracker(Strategy strategy)
232 : current_pointer_id_bits_(0),
233 active_pointer_id_(-1),
234 strategy_(CreateStrategy(strategy)) {}
236 VelocityTracker::~VelocityTracker() {}
238 void VelocityTracker::Clear() {
239 current_pointer_id_bits_.clear();
240 active_pointer_id_ = -1;
241 strategy_->Clear();
244 void VelocityTracker::ClearPointers(BitSet32 id_bits) {
245 BitSet32 remaining_id_bits(current_pointer_id_bits_.value & ~id_bits.value);
246 current_pointer_id_bits_ = remaining_id_bits;
248 if (active_pointer_id_ >= 0 && id_bits.has_bit(active_pointer_id_)) {
249 active_pointer_id_ = !remaining_id_bits.is_empty()
250 ? remaining_id_bits.first_marked_bit()
251 : -1;
254 strategy_->ClearPointers(id_bits);
257 void VelocityTracker::AddMovement(const TimeTicks& event_time,
258 BitSet32 id_bits,
259 const Position* positions) {
260 while (id_bits.count() > MAX_POINTERS)
261 id_bits.clear_last_marked_bit();
263 if ((current_pointer_id_bits_.value & id_bits.value) &&
264 (event_time - last_event_time_) >=
265 base::TimeDelta::FromMilliseconds(kAssumePointerMoveStoppedTimeMs)) {
266 // We have not received any movements for too long. Assume that all pointers
267 // have stopped.
268 strategy_->Clear();
270 last_event_time_ = event_time;
272 current_pointer_id_bits_ = id_bits;
273 if (active_pointer_id_ < 0 || !id_bits.has_bit(active_pointer_id_))
274 active_pointer_id_ = id_bits.is_empty() ? -1 : id_bits.first_marked_bit();
276 strategy_->AddMovement(event_time, id_bits, positions);
279 void VelocityTracker::AddMovement(const MotionEvent& event) {
280 int32_t actionMasked = event.GetAction();
282 switch (actionMasked) {
283 case MotionEvent::ACTION_DOWN:
284 // case MotionEvent::HOVER_ENTER:
285 // Clear all pointers on down before adding the new movement.
286 Clear();
287 break;
288 case MotionEvent::ACTION_POINTER_DOWN: {
289 // Start a new movement trace for a pointer that just went down.
290 // We do this on down instead of on up because the client may want to
291 // query the final velocity for a pointer that just went up.
292 BitSet32 downIdBits;
293 downIdBits.mark_bit(event.GetPointerId(event.GetActionIndex()));
294 ClearPointers(downIdBits);
295 break;
297 case MotionEvent::ACTION_MOVE:
298 // case MotionEvent::ACTION_HOVER_MOVE:
299 break;
300 case MotionEvent::ACTION_UP:
301 case MotionEvent::ACTION_POINTER_UP:
302 // Note that ACTION_UP and ACTION_POINTER_UP always report the last known
303 // position of the pointers that went up. ACTION_POINTER_UP does include
304 // the new position of pointers that remained down but we will also
305 // receive an ACTION_MOVE with this information if any of them actually
306 // moved. Since we don't know how many pointers will be going up at once
307 // it makes sense to just wait for the following ACTION_MOVE before adding
308 // the movement. However, if the up event itself is delayed because of
309 // (difficult albeit possible) prolonged stationary screen contact, assume
310 // that motion has stopped.
311 if ((event.GetEventTime() - last_event_time_) >=
312 base::TimeDelta::FromMilliseconds(kAssumePointerUpStoppedTimeMs))
313 strategy_->Clear();
314 return;
315 default:
316 // Ignore all other actions because they do not convey any new information
317 // about pointer movement. We also want to preserve the last known
318 // velocity of the pointers.
319 return;
322 size_t pointer_count = event.GetPointerCount();
323 if (pointer_count > MAX_POINTERS) {
324 pointer_count = MAX_POINTERS;
327 BitSet32 id_bits;
328 for (size_t i = 0; i < pointer_count; i++) {
329 id_bits.mark_bit(event.GetPointerId(i));
332 uint32_t pointer_index[MAX_POINTERS];
333 for (size_t i = 0; i < pointer_count; i++) {
334 pointer_index[i] = id_bits.get_index_of_bit(event.GetPointerId(i));
337 Position positions[MAX_POINTERS];
338 size_t historySize = event.GetHistorySize();
339 for (size_t h = 0; h < historySize; h++) {
340 for (size_t i = 0; i < pointer_count; i++) {
341 uint32_t index = pointer_index[i];
342 positions[index].x = event.GetHistoricalX(i, h);
343 positions[index].y = event.GetHistoricalY(i, h);
345 AddMovement(event.GetHistoricalEventTime(h), id_bits, positions);
348 for (size_t i = 0; i < pointer_count; i++) {
349 uint32_t index = pointer_index[i];
350 positions[index].x = event.GetX(i);
351 positions[index].y = event.GetY(i);
353 AddMovement(event.GetEventTime(), id_bits, positions);
356 bool VelocityTracker::GetVelocity(uint32_t id,
357 float* out_vx,
358 float* out_vy) const {
359 Estimator estimator;
360 if (GetEstimator(id, &estimator) && estimator.degree >= 1) {
361 *out_vx = estimator.xcoeff[1];
362 *out_vy = estimator.ycoeff[1];
363 return true;
365 *out_vx = 0;
366 *out_vy = 0;
367 return false;
370 void LeastSquaresVelocityTrackerStrategy::AddMovement(
371 const TimeTicks& event_time,
372 BitSet32 id_bits,
373 const Position* positions) {
374 if (++index_ == HISTORY_SIZE) {
375 index_ = 0;
378 Movement& movement = movements_[index_];
379 movement.event_time = event_time;
380 movement.id_bits = id_bits;
381 uint32_t count = id_bits.count();
382 for (uint32_t i = 0; i < count; i++) {
383 movement.positions[i] = positions[i];
387 bool VelocityTracker::GetEstimator(uint32_t id,
388 Estimator* out_estimator) const {
389 return strategy_->GetEstimator(id, out_estimator);
392 // --- LeastSquaresVelocityTrackerStrategy ---
394 LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(
395 uint32_t degree,
396 Weighting weighting)
397 : degree_(degree), weighting_(weighting) {
398 DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::MAX_DEGREE));
399 Clear();
402 LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {}
404 void LeastSquaresVelocityTrackerStrategy::Clear() {
405 index_ = 0;
406 movements_[0].id_bits.clear();
410 * Solves a linear least squares problem to obtain a N degree polynomial that
411 * fits the specified input data as nearly as possible.
413 * Returns true if a solution is found, false otherwise.
415 * The input consists of two vectors of data points X and Y with indices 0..m-1
416 * along with a weight vector W of the same size.
418 * The output is a vector B with indices 0..n that describes a polynomial
419 * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1]
420 * X[i] * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is
421 * minimized.
423 * Accordingly, the weight vector W should be initialized by the caller with the
424 * reciprocal square root of the variance of the error in each input data point.
425 * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 /
426 * stddev(Y[i]).
427 * The weights express the relative importance of each data point. If the
428 * weights are* all 1, then the data points are considered to be of equal
429 * importance when fitting the polynomial. It is a good idea to choose weights
430 * that diminish the importance of data points that may have higher than usual
431 * error margins.
433 * Errors among data points are assumed to be independent. W is represented
434 * here as a vector although in the literature it is typically taken to be a
435 * diagonal matrix.
437 * That is to say, the function that generated the input data can be
438 * approximated by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
440 * The coefficient of determination (R^2) is also returned to describe the
441 * goodness of fit of the model for the given data. It is a value between 0
442 * and 1, where 1 indicates perfect correspondence.
444 * This function first expands the X vector to a m by n matrix A such that
445 * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then
446 * multiplies it by w[i]./
448 * Then it calculates the QR decomposition of A yielding an m by m orthonormal
449 * matrix Q and an m by n upper triangular matrix R. Because R is upper
450 * triangular (lower part is all zeroes), we can simplify the decomposition into
451 * an m by n matrix Q1 and a n by n matrix R1 such that A = Q1 R1.
453 * Finally we solve the system of linear equations given by
454 * R1 B = (Qtranspose W Y) to find B.
456 * For efficiency, we lay out A and Q column-wise in memory because we
457 * frequently operate on the column vectors. Conversely, we lay out R row-wise.
459 * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
460 * http://en.wikipedia.org/wiki/Gram-Schmidt
462 static bool SolveLeastSquares(const float* x,
463 const float* y,
464 const float* w,
465 uint32_t m,
466 uint32_t n,
467 float* out_b,
468 float* out_det) {
469 // MSVC does not support variable-length arrays (used by the original Android
470 // implementation of this function).
471 #if defined(COMPILER_MSVC)
472 enum {
473 M_ARRAY_LENGTH = LeastSquaresVelocityTrackerStrategy::HISTORY_SIZE,
474 N_ARRAY_LENGTH = Estimator::MAX_DEGREE
476 DCHECK_LE(m, static_cast<uint32_t>(M_ARRAY_LENGTH));
477 DCHECK_LE(n, static_cast<uint32_t>(N_ARRAY_LENGTH));
478 #else
479 const uint32_t M_ARRAY_LENGTH = m;
480 const uint32_t N_ARRAY_LENGTH = n;
481 #endif
483 // Expand the X vector to a matrix A, pre-multiplied by the weights.
484 float a[N_ARRAY_LENGTH][M_ARRAY_LENGTH]; // column-major order
485 for (uint32_t h = 0; h < m; h++) {
486 a[0][h] = w[h];
487 for (uint32_t i = 1; i < n; i++) {
488 a[i][h] = a[i - 1][h] * x[h];
492 // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
494 // Orthonormal basis, column-major order.
495 float q[N_ARRAY_LENGTH][M_ARRAY_LENGTH];
496 // Upper triangular matrix, row-major order.
497 float r[N_ARRAY_LENGTH][N_ARRAY_LENGTH];
498 for (uint32_t j = 0; j < n; j++) {
499 for (uint32_t h = 0; h < m; h++) {
500 q[j][h] = a[j][h];
502 for (uint32_t i = 0; i < j; i++) {
503 float dot = VectorDot(&q[j][0], &q[i][0], m);
504 for (uint32_t h = 0; h < m; h++) {
505 q[j][h] -= dot * q[i][h];
509 float norm = VectorNorm(&q[j][0], m);
510 if (norm < 0.000001f) {
511 // vectors are linearly dependent or zero so no solution
512 return false;
515 float invNorm = 1.0f / norm;
516 for (uint32_t h = 0; h < m; h++) {
517 q[j][h] *= invNorm;
519 for (uint32_t i = 0; i < n; i++) {
520 r[j][i] = i < j ? 0 : VectorDot(&q[j][0], &a[i][0], m);
524 // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
525 // We just work from bottom-right to top-left calculating B's coefficients.
526 float wy[M_ARRAY_LENGTH];
527 for (uint32_t h = 0; h < m; h++) {
528 wy[h] = y[h] * w[h];
530 for (uint32_t i = n; i-- != 0;) {
531 out_b[i] = VectorDot(&q[i][0], wy, m);
532 for (uint32_t j = n - 1; j > i; j--) {
533 out_b[i] -= r[i][j] * out_b[j];
535 out_b[i] /= r[i][i];
538 // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
539 // SSerr is the residual sum of squares (variance of the error),
540 // and SStot is the total sum of squares (variance of the data) where each
541 // has been weighted.
542 float ymean = 0;
543 for (uint32_t h = 0; h < m; h++) {
544 ymean += y[h];
546 ymean /= m;
548 float sserr = 0;
549 float sstot = 0;
550 for (uint32_t h = 0; h < m; h++) {
551 float err = y[h] - out_b[0];
552 float term = 1;
553 for (uint32_t i = 1; i < n; i++) {
554 term *= x[h];
555 err -= term * out_b[i];
557 sserr += w[h] * w[h] * err * err;
558 float var = y[h] - ymean;
559 sstot += w[h] * w[h] * var * var;
561 *out_det = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
562 return true;
565 void LeastSquaresVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) {
566 BitSet32 remaining_id_bits(movements_[index_].id_bits.value & ~id_bits.value);
567 movements_[index_].id_bits = remaining_id_bits;
570 bool LeastSquaresVelocityTrackerStrategy::GetEstimator(
571 uint32_t id,
572 Estimator* out_estimator) const {
573 out_estimator->Clear();
575 // Iterate over movement samples in reverse time order and collect samples.
576 float x[HISTORY_SIZE];
577 float y[HISTORY_SIZE];
578 float w[HISTORY_SIZE];
579 float time[HISTORY_SIZE];
580 uint32_t m = 0;
581 uint32_t index = index_;
582 const base::TimeDelta horizon = base::TimeDelta::FromMilliseconds(HORIZON_MS);
583 const Movement& newest_movement = movements_[index_];
584 do {
585 const Movement& movement = movements_[index];
586 if (!movement.id_bits.has_bit(id))
587 break;
589 TimeDelta age = newest_movement.event_time - movement.event_time;
590 if (age > horizon)
591 break;
593 const Position& position = movement.GetPosition(id);
594 x[m] = position.x;
595 y[m] = position.y;
596 w[m] = ChooseWeight(index);
597 time[m] = -age.InSecondsF();
598 index = (index == 0 ? HISTORY_SIZE : index) - 1;
599 } while (++m < HISTORY_SIZE);
601 if (m == 0)
602 return false; // no data
604 // Calculate a least squares polynomial fit.
605 uint32_t degree = degree_;
606 if (degree > m - 1)
607 degree = m - 1;
609 if (degree >= 1) {
610 float xdet, ydet;
611 uint32_t n = degree + 1;
612 if (SolveLeastSquares(time, x, w, m, n, out_estimator->xcoeff, &xdet) &&
613 SolveLeastSquares(time, y, w, m, n, out_estimator->ycoeff, &ydet)) {
614 out_estimator->time = newest_movement.event_time;
615 out_estimator->degree = degree;
616 out_estimator->confidence = xdet * ydet;
617 return true;
621 // No velocity data available for this pointer, but we do have its current
622 // position.
623 out_estimator->xcoeff[0] = x[0];
624 out_estimator->ycoeff[0] = y[0];
625 out_estimator->time = newest_movement.event_time;
626 out_estimator->degree = 0;
627 out_estimator->confidence = 1;
628 return true;
631 float LeastSquaresVelocityTrackerStrategy::ChooseWeight(uint32_t index) const {
632 switch (weighting_) {
633 case WEIGHTING_DELTA: {
634 // Weight points based on how much time elapsed between them and the next
635 // point so that points that "cover" a shorter time span are weighed less.
636 // delta 0ms: 0.5
637 // delta 10ms: 1.0
638 if (index == index_) {
639 return 1.0f;
641 uint32_t next_index = (index + 1) % HISTORY_SIZE;
642 float delta_millis =
643 static_cast<float>((movements_[next_index].event_time -
644 movements_[index].event_time).InMillisecondsF());
645 if (delta_millis < 0)
646 return 0.5f;
647 if (delta_millis < 10)
648 return 0.5f + delta_millis * 0.05;
650 return 1.0f;
653 case WEIGHTING_CENTRAL: {
654 // Weight points based on their age, weighing very recent and very old
655 // points less.
656 // age 0ms: 0.5
657 // age 10ms: 1.0
658 // age 50ms: 1.0
659 // age 60ms: 0.5
660 float age_millis =
661 static_cast<float>((movements_[index_].event_time -
662 movements_[index].event_time).InMillisecondsF());
663 if (age_millis < 0)
664 return 0.5f;
665 if (age_millis < 10)
666 return 0.5f + age_millis * 0.05;
667 if (age_millis < 50)
668 return 1.0f;
669 if (age_millis < 60)
670 return 0.5f + (60 - age_millis) * 0.05;
672 return 0.5f;
675 case WEIGHTING_RECENT: {
676 // Weight points based on their age, weighing older points less.
677 // age 0ms: 1.0
678 // age 50ms: 1.0
679 // age 100ms: 0.5
680 float age_millis =
681 static_cast<float>((movements_[index_].event_time -
682 movements_[index].event_time).InMillisecondsF());
683 if (age_millis < 50) {
684 return 1.0f;
686 if (age_millis < 100) {
687 return 0.5f + (100 - age_millis) * 0.01f;
689 return 0.5f;
692 case WEIGHTING_NONE:
693 default:
694 return 1.0f;
698 // --- IntegratingVelocityTrackerStrategy ---
700 IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(
701 uint32_t degree)
702 : degree_(degree) {
703 DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::MAX_DEGREE));
706 IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {}
708 void IntegratingVelocityTrackerStrategy::Clear() { pointer_id_bits_.clear(); }
710 void IntegratingVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) {
711 pointer_id_bits_.value &= ~id_bits.value;
714 void IntegratingVelocityTrackerStrategy::AddMovement(
715 const TimeTicks& event_time,
716 BitSet32 id_bits,
717 const Position* positions) {
718 uint32_t index = 0;
719 for (BitSet32 iter_id_bits(id_bits); !iter_id_bits.is_empty();) {
720 uint32_t id = iter_id_bits.clear_first_marked_bit();
721 State& state = mPointerState[id];
722 const Position& position = positions[index++];
723 if (pointer_id_bits_.has_bit(id))
724 UpdateState(state, event_time, position.x, position.y);
725 else
726 InitState(state, event_time, position.x, position.y);
729 pointer_id_bits_ = id_bits;
732 bool IntegratingVelocityTrackerStrategy::GetEstimator(
733 uint32_t id,
734 Estimator* out_estimator) const {
735 out_estimator->Clear();
737 if (pointer_id_bits_.has_bit(id)) {
738 const State& state = mPointerState[id];
739 PopulateEstimator(state, out_estimator);
740 return true;
743 return false;
746 void IntegratingVelocityTrackerStrategy::InitState(State& state,
747 const TimeTicks& event_time,
748 float xpos,
749 float ypos) const {
750 state.update_time = event_time;
751 state.degree = 0;
752 state.xpos = xpos;
753 state.xvel = 0;
754 state.xaccel = 0;
755 state.ypos = ypos;
756 state.yvel = 0;
757 state.yaccel = 0;
760 void IntegratingVelocityTrackerStrategy::UpdateState(
761 State& state,
762 const TimeTicks& event_time,
763 float xpos,
764 float ypos) const {
765 const base::TimeDelta MIN_TIME_DELTA = TimeDelta::FromMicroseconds(2);
766 const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds
768 if (event_time <= state.update_time + MIN_TIME_DELTA)
769 return;
771 float dt = static_cast<float>((event_time - state.update_time).InSecondsF());
772 state.update_time = event_time;
774 float xvel = (xpos - state.xpos) / dt;
775 float yvel = (ypos - state.ypos) / dt;
776 if (state.degree == 0) {
777 state.xvel = xvel;
778 state.yvel = yvel;
779 state.degree = 1;
780 } else {
781 float alpha = dt / (FILTER_TIME_CONSTANT + dt);
782 if (degree_ == 1) {
783 state.xvel += (xvel - state.xvel) * alpha;
784 state.yvel += (yvel - state.yvel) * alpha;
785 } else {
786 float xaccel = (xvel - state.xvel) / dt;
787 float yaccel = (yvel - state.yvel) / dt;
788 if (state.degree == 1) {
789 state.xaccel = xaccel;
790 state.yaccel = yaccel;
791 state.degree = 2;
792 } else {
793 state.xaccel += (xaccel - state.xaccel) * alpha;
794 state.yaccel += (yaccel - state.yaccel) * alpha;
796 state.xvel += (state.xaccel * dt) * alpha;
797 state.yvel += (state.yaccel * dt) * alpha;
800 state.xpos = xpos;
801 state.ypos = ypos;
804 void IntegratingVelocityTrackerStrategy::PopulateEstimator(
805 const State& state,
806 Estimator* out_estimator) const {
807 out_estimator->time = state.update_time;
808 out_estimator->confidence = 1.0f;
809 out_estimator->degree = state.degree;
810 out_estimator->xcoeff[0] = state.xpos;
811 out_estimator->xcoeff[1] = state.xvel;
812 out_estimator->xcoeff[2] = state.xaccel / 2;
813 out_estimator->ycoeff[0] = state.ypos;
814 out_estimator->ycoeff[1] = state.yvel;
815 out_estimator->ycoeff[2] = state.yaccel / 2;
818 } // namespace ui