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"
9 #include "base/logging.h"
10 #include "ui/events/gesture_detection/motion_event.h"
12 using base::TimeDelta
;
13 using base::TimeTicks
;
17 // Implements a particular velocity tracker algorithm.
18 class VelocityTrackerStrategy
{
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
,
26 const Position
* positions
) = 0;
27 virtual bool GetEstimator(uint32_t id
, Estimator
* out_estimator
) const = 0;
30 VelocityTrackerStrategy() {}
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;
54 enum { MAX_DEGREE
= 4 };
56 // Estimator time base.
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
66 // Confidence (coefficient of determination), between 0 (no fit)
67 // and 1 (perfect fit).
74 for (size_t i
= 0; i
<= MAX_DEGREE
; i
++) {
81 float VectorDot(const float* a
, const float* b
, uint32_t m
) {
89 float VectorNorm(const float* a
, uint32_t m
) {
98 // Velocity tracker algorithm based on least-squares linear regression.
99 class LeastSquaresVelocityTrackerStrategy
: public VelocityTrackerStrategy
{
102 // No weights applied. All data points are equally reliable.
105 // Weight by time delta. Data points clustered together are weighted less.
108 // Weight such that points within a certain horizon are weighed more than
109 // those outside of that horizon.
112 // Weight such that points older than a certain amount are weighed less.
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
,
128 const Position
* positions
) OVERRIDE
;
129 virtual bool GetEstimator(uint32_t id
,
130 Estimator
* out_estimator
) const OVERRIDE
;
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 };
139 TimeTicks event_time
;
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_
;
153 Movement movements_
[HISTORY_SIZE
];
156 // Velocity tracker algorithm that uses an IIR filter.
157 class IntegratingVelocityTrackerStrategy
: public VelocityTrackerStrategy
{
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
,
167 const Position
* positions
) OVERRIDE
;
168 virtual bool GetEstimator(uint32_t id
,
169 Estimator
* out_estimator
) const OVERRIDE
;
172 // Current state estimate for a particular pointer.
174 TimeTicks update_time
;
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
,
189 void UpdateState(State
& state
,
190 const TimeTicks
& event_time
,
193 void PopulateEstimator(const State
& state
, Estimator
* out_estimator
) const;
196 VelocityTrackerStrategy
* CreateStrategy(VelocityTracker::Strategy 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
);
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;
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()
254 strategy_
->ClearPointers(id_bits
);
257 void VelocityTracker::AddMovement(const TimeTicks
& event_time
,
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
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.
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.
293 downIdBits
.mark_bit(event
.GetPointerId(event
.GetActionIndex()));
294 ClearPointers(downIdBits
);
297 case MotionEvent::ACTION_MOVE
:
298 // case MotionEvent::ACTION_HOVER_MOVE:
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
))
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.
322 size_t pointer_count
= event
.GetPointerCount();
323 if (pointer_count
> MAX_POINTERS
) {
324 pointer_count
= MAX_POINTERS
;
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
,
358 float* out_vy
) const {
360 if (GetEstimator(id
, &estimator
) && estimator
.degree
>= 1) {
361 *out_vx
= estimator
.xcoeff
[1];
362 *out_vy
= estimator
.ycoeff
[1];
370 void LeastSquaresVelocityTrackerStrategy::AddMovement(
371 const TimeTicks
& event_time
,
373 const Position
* positions
) {
374 if (++index_
== HISTORY_SIZE
) {
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(
397 : degree_(degree
), weighting_(weighting
) {
398 DCHECK_LT(degree_
, static_cast<uint32_t>(Estimator::MAX_DEGREE
));
402 LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {}
404 void LeastSquaresVelocityTrackerStrategy::Clear() {
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
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 /
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
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
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
,
469 // MSVC does not support variable-length arrays (used by the original Android
470 // implementation of this function).
471 #if defined(COMPILER_MSVC)
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
));
479 const uint32_t M_ARRAY_LENGTH
= m
;
480 const uint32_t N_ARRAY_LENGTH
= n
;
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
++) {
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
++) {
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
515 float invNorm
= 1.0f
/ norm
;
516 for (uint32_t h
= 0; h
< m
; h
++) {
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
++) {
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
];
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.
543 for (uint32_t h
= 0; h
< m
; h
++) {
550 for (uint32_t h
= 0; h
< m
; h
++) {
551 float err
= y
[h
] - out_b
[0];
553 for (uint32_t i
= 1; i
< n
; i
++) {
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;
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(
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
];
581 uint32_t index
= index_
;
582 const base::TimeDelta horizon
= base::TimeDelta::FromMilliseconds(HORIZON_MS
);
583 const Movement
& newest_movement
= movements_
[index_
];
585 const Movement
& movement
= movements_
[index
];
586 if (!movement
.id_bits
.has_bit(id
))
589 TimeDelta age
= newest_movement
.event_time
- movement
.event_time
;
593 const Position
& position
= movement
.GetPosition(id
);
596 w
[m
] = ChooseWeight(index
);
597 time
[m
] = -age
.InSecondsF();
598 index
= (index
== 0 ? HISTORY_SIZE
: index
) - 1;
599 } while (++m
< HISTORY_SIZE
);
602 return false; // no data
604 // Calculate a least squares polynomial fit.
605 uint32_t degree
= degree_
;
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
;
621 // No velocity data available for this pointer, but we do have its current
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;
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.
638 if (index
== index_
) {
641 uint32_t next_index
= (index
+ 1) % HISTORY_SIZE
;
643 static_cast<float>((movements_
[next_index
].event_time
-
644 movements_
[index
].event_time
).InMillisecondsF());
645 if (delta_millis
< 0)
647 if (delta_millis
< 10)
648 return 0.5f
+ delta_millis
* 0.05;
653 case WEIGHTING_CENTRAL
: {
654 // Weight points based on their age, weighing very recent and very old
661 static_cast<float>((movements_
[index_
].event_time
-
662 movements_
[index
].event_time
).InMillisecondsF());
666 return 0.5f
+ age_millis
* 0.05;
670 return 0.5f
+ (60 - age_millis
) * 0.05;
675 case WEIGHTING_RECENT
: {
676 // Weight points based on their age, weighing older points less.
681 static_cast<float>((movements_
[index_
].event_time
-
682 movements_
[index
].event_time
).InMillisecondsF());
683 if (age_millis
< 50) {
686 if (age_millis
< 100) {
687 return 0.5f
+ (100 - age_millis
) * 0.01f
;
698 // --- IntegratingVelocityTrackerStrategy ---
700 IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(
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
,
717 const Position
* positions
) {
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
);
726 InitState(state
, event_time
, position
.x
, position
.y
);
729 pointer_id_bits_
= id_bits
;
732 bool IntegratingVelocityTrackerStrategy::GetEstimator(
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
);
746 void IntegratingVelocityTrackerStrategy::InitState(State
& state
,
747 const TimeTicks
& event_time
,
750 state
.update_time
= event_time
;
760 void IntegratingVelocityTrackerStrategy::UpdateState(
762 const TimeTicks
& event_time
,
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
)
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) {
781 float alpha
= dt
/ (FILTER_TIME_CONSTANT
+ dt
);
783 state
.xvel
+= (xvel
- state
.xvel
) * alpha
;
784 state
.yvel
+= (yvel
- state
.yvel
) * alpha
;
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
;
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
;
804 void IntegratingVelocityTrackerStrategy::PopulateEstimator(
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;