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 5 #include "ui/events/gesture_detection/velocity_tracker.h" 6 7 #include <cmath> 8 9 #include "base/logging.h" 10 #include "ui/events/gesture_detection/motion_event.h" 11 12 using base::TimeDelta; 13 using base::TimeTicks; 14 15 namespace ui { 16 17 // Implements a particular velocity tracker algorithm. 18 class VelocityTrackerStrategy { 19 public: 20 virtual ~VelocityTrackerStrategy() {} 21 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; 28 29 protected: 30 VelocityTrackerStrategy() {} 31 }; 32 33 namespace { 34 35 COMPILE_ASSERT(MotionEvent::MAX_POINTER_ID < 32, max_pointer_id_too_large); 36 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; 42 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; 48 49 struct Position { 50 float x, y; 51 }; 52 53 struct Estimator { 54 enum { MAX_DEGREE = 4 }; 55 56 // Estimator time base. 57 TimeTicks time; 58 59 // Polynomial coefficients describing motion in X and Y. 60 float xcoeff[MAX_DEGREE + 1], ycoeff[MAX_DEGREE + 1]; 61 62 // Polynomial degree (number of coefficients), or zero if no information is 63 // available. 64 uint32_t degree; 65 66 // Confidence (coefficient of determination), between 0 (no fit) 67 // and 1 (perfect fit). 68 float confidence; 69 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; 77 } 78 } 79 }; 80 81 float VectorDot(const float* a, const float* b, uint32_t m) { 82 float r = 0; 83 while (m--) { 84 r += *(a++) * *(b++); 85 } 86 return r; 87 } 88 89 float VectorNorm(const float* a, uint32_t m) { 90 float r = 0; 91 while (m--) { 92 float t = *(a++); 93 r += t * t; 94 } 95 return sqrtf(r); 96 } 97 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, 104 105 // Weight by time delta. Data points clustered together are weighted less. 106 WEIGHTING_DELTA, 107 108 // Weight such that points within a certain horizon are weighed more than 109 // those outside of that horizon. 110 WEIGHTING_CENTRAL, 111 112 // Weight such that points older than a certain amount are weighed less. 113 WEIGHTING_RECENT, 114 }; 115 116 // Number of samples to keep. 117 enum { HISTORY_SIZE = 20 }; 118 119 // Degree must be no greater than Estimator::MAX_DEGREE. 120 LeastSquaresVelocityTrackerStrategy(uint32_t degree, 121 Weighting weighting = WEIGHTING_NONE); 122 virtual ~LeastSquaresVelocityTrackerStrategy(); 123 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; 131 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 }; 137 138 struct Movement { 139 TimeTicks event_time; 140 BitSet32 id_bits; 141 Position positions[VelocityTracker::MAX_POINTERS]; 142 143 inline const Position& GetPosition(uint32_t id) const { 144 return positions[id_bits.get_index_of_bit(id)]; 145 } 146 }; 147 148 float ChooseWeight(uint32_t index) const; 149 150 const uint32_t degree_; 151 const Weighting weighting_; 152 uint32_t index_; 153 Movement movements_[HISTORY_SIZE]; 154 }; 155 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(); 162 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; 170 171 private: 172 // Current state estimate for a particular pointer. 173 struct State { 174 TimeTicks update_time; 175 uint32_t degree; 176 177 float xpos, xvel, xaccel; 178 float ypos, yvel, yaccel; 179 }; 180 181 const uint32_t degree_; 182 BitSet32 pointer_id_bits_; 183 State mPointerState[MotionEvent::MAX_POINTER_ID + 1]; 184 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; 194 }; 195 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); 217 } 218 NOTREACHED() << "Unrecognized velocity tracker strategy: " << strategy; 219 return CreateStrategy(VelocityTracker::STRATEGY_DEFAULT); 220 } 221 222 } // namespace 223 224 // --- VelocityTracker --- 225 226 VelocityTracker::VelocityTracker() 227 : current_pointer_id_bits_(0), 228 active_pointer_id_(-1), 229 strategy_(CreateStrategy(STRATEGY_DEFAULT)) {} 230 231 VelocityTracker::VelocityTracker(Strategy strategy) 232 : current_pointer_id_bits_(0), 233 active_pointer_id_(-1), 234 strategy_(CreateStrategy(strategy)) {} 235 236 VelocityTracker::~VelocityTracker() {} 237 238 void VelocityTracker::Clear() { 239 current_pointer_id_bits_.clear(); 240 active_pointer_id_ = -1; 241 strategy_->Clear(); 242 } 243 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; 247 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; 252 } 253 254 strategy_->ClearPointers(id_bits); 255 } 256 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(); 262 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(); 269 } 270 last_event_time_ = event_time; 271 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(); 275 276 strategy_->AddMovement(event_time, id_bits, positions); 277 } 278 279 void VelocityTracker::AddMovement(const MotionEvent& event) { 280 int32_t actionMasked = event.GetAction(); 281 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; 296 } 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; 320 } 321 322 size_t pointer_count = event.GetPointerCount(); 323 if (pointer_count > MAX_POINTERS) { 324 pointer_count = MAX_POINTERS; 325 } 326 327 BitSet32 id_bits; 328 for (size_t i = 0; i < pointer_count; i++) { 329 id_bits.mark_bit(event.GetPointerId(i)); 330 } 331 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)); 335 } 336 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); 344 } 345 AddMovement(event.GetHistoricalEventTime(h), id_bits, positions); 346 } 347 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); 352 } 353 AddMovement(event.GetEventTime(), id_bits, positions); 354 } 355 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; 364 } 365 *out_vx = 0; 366 *out_vy = 0; 367 return false; 368 } 369 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; 376 } 377 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]; 384 } 385 } 386 387 bool VelocityTracker::GetEstimator(uint32_t id, 388 Estimator* out_estimator) const { 389 return strategy_->GetEstimator(id, out_estimator); 390 } 391 392 // --- LeastSquaresVelocityTrackerStrategy --- 393 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(); 400 } 401 402 LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {} 403 404 void LeastSquaresVelocityTrackerStrategy::Clear() { 405 index_ = 0; 406 movements_[0].id_bits.clear(); 407 } 408 409 /** 410 * Solves a linear least squares problem to obtain a N degree polynomial that 411 * fits the specified input data as nearly as possible. 412 * 413 * Returns true if a solution is found, false otherwise. 414 * 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. 417 * 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. 422 * 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. 432 * 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. 436 * 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. 439 * 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. 443 * 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]./ 447 * 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. 452 * 453 * Finally we solve the system of linear equations given by 454 * R1 B = (Qtranspose W Y) to find B. 455 * 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. 458 * 459 * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares 460 * http://en.wikipedia.org/wiki/Gram-Schmidt 461 */ 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 475 }; 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 482 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]; 489 } 490 } 491 492 // Apply the Gram-Schmidt process to A to obtain its QR decomposition. 493 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]; 501 } 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]; 506 } 507 } 508 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; 513 } 514 515 float invNorm = 1.0f / norm; 516 for (uint32_t h = 0; h < m; h++) { 517 q[j][h] *= invNorm; 518 } 519 for (uint32_t i = 0; i < n; i++) { 520 r[j][i] = i < j ? 0 : VectorDot(&q[j][0], &a[i][0], m); 521 } 522 } 523 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]; 529 } 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]; 534 } 535 out_b[i] /= r[i][i]; 536 } 537 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]; 545 } 546 ymean /= m; 547 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]; 556 } 557 sserr += w[h] * w[h] * err * err; 558 float var = y[h] - ymean; 559 sstot += w[h] * w[h] * var * var; 560 } 561 *out_det = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1; 562 return true; 563 } 564 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; 568 } 569 570 bool LeastSquaresVelocityTrackerStrategy::GetEstimator( 571 uint32_t id, 572 Estimator* out_estimator) const { 573 out_estimator->Clear(); 574 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; 588 589 TimeDelta age = newest_movement.event_time - movement.event_time; 590 if (age > horizon) 591 break; 592 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); 600 601 if (m == 0) 602 return false; // no data 603 604 // Calculate a least squares polynomial fit. 605 uint32_t degree = degree_; 606 if (degree > m - 1) 607 degree = m - 1; 608 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; 618 } 619 } 620 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; 629 } 630 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; 640 } 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; 649 650 return 1.0f; 651 } 652 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; 671 672 return 0.5f; 673 } 674 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; 685 } 686 if (age_millis < 100) { 687 return 0.5f + (100 - age_millis) * 0.01f; 688 } 689 return 0.5f; 690 } 691 692 case WEIGHTING_NONE: 693 default: 694 return 1.0f; 695 } 696 } 697 698 // --- IntegratingVelocityTrackerStrategy --- 699 700 IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy( 701 uint32_t degree) 702 : degree_(degree) { 703 DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::MAX_DEGREE)); 704 } 705 706 IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {} 707 708 void IntegratingVelocityTrackerStrategy::Clear() { pointer_id_bits_.clear(); } 709 710 void IntegratingVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) { 711 pointer_id_bits_.value &= ~id_bits.value; 712 } 713 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); 727 } 728 729 pointer_id_bits_ = id_bits; 730 } 731 732 bool IntegratingVelocityTrackerStrategy::GetEstimator( 733 uint32_t id, 734 Estimator* out_estimator) const { 735 out_estimator->Clear(); 736 737 if (pointer_id_bits_.has_bit(id)) { 738 const State& state = mPointerState[id]; 739 PopulateEstimator(state, out_estimator); 740 return true; 741 } 742 743 return false; 744 } 745 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; 758 } 759 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 767 768 if (event_time <= state.update_time + MIN_TIME_DELTA) 769 return; 770 771 float dt = static_cast<float>((event_time - state.update_time).InSecondsF()); 772 state.update_time = event_time; 773 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; 795 } 796 state.xvel += (state.xaccel * dt) * alpha; 797 state.yvel += (state.yaccel * dt) * alpha; 798 } 799 } 800 state.xpos = xpos; 801 state.ypos = ypos; 802 } 803 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; 816 } 817 818 } // namespace ui 819