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 for determining that a pointer has stopped moving. 38 // Some input devices do not send ACTION_MOVE events in the case where a pointer 39 // has stopped. We need to detect this case so that we can accurately predict 40 // the velocity after the pointer starts moving again. 41 const int kAssumePointerStoppedTimeMs = 40; 42 43 struct Position { 44 float x, y; 45 }; 46 47 struct Estimator { 48 enum { MAX_DEGREE = 4 }; 49 50 // Estimator time base. 51 TimeTicks time; 52 53 // Polynomial coefficients describing motion in X and Y. 54 float xcoeff[MAX_DEGREE + 1], ycoeff[MAX_DEGREE + 1]; 55 56 // Polynomial degree (number of coefficients), or zero if no information is 57 // available. 58 uint32_t degree; 59 60 // Confidence (coefficient of determination), between 0 (no fit) 61 // and 1 (perfect fit). 62 float confidence; 63 64 inline void Clear() { 65 time = TimeTicks(); 66 degree = 0; 67 confidence = 0; 68 for (size_t i = 0; i <= MAX_DEGREE; i++) { 69 xcoeff[i] = 0; 70 ycoeff[i] = 0; 71 } 72 } 73 }; 74 75 float VectorDot(const float* a, const float* b, uint32_t m) { 76 float r = 0; 77 while (m--) { 78 r += *(a++) * *(b++); 79 } 80 return r; 81 } 82 83 float VectorNorm(const float* a, uint32_t m) { 84 float r = 0; 85 while (m--) { 86 float t = *(a++); 87 r += t * t; 88 } 89 return sqrtf(r); 90 } 91 92 // Velocity tracker algorithm based on least-squares linear regression. 93 class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy { 94 public: 95 enum Weighting { 96 // No weights applied. All data points are equally reliable. 97 WEIGHTING_NONE, 98 99 // Weight by time delta. Data points clustered together are weighted less. 100 WEIGHTING_DELTA, 101 102 // Weight such that points within a certain horizon are weighed more than 103 // those outside of that horizon. 104 WEIGHTING_CENTRAL, 105 106 // Weight such that points older than a certain amount are weighed less. 107 WEIGHTING_RECENT, 108 }; 109 110 // Number of samples to keep. 111 enum { HISTORY_SIZE = 20 }; 112 113 // Degree must be no greater than Estimator::MAX_DEGREE. 114 LeastSquaresVelocityTrackerStrategy(uint32_t degree, 115 Weighting weighting = WEIGHTING_NONE); 116 virtual ~LeastSquaresVelocityTrackerStrategy(); 117 118 virtual void Clear() OVERRIDE; 119 virtual void ClearPointers(BitSet32 id_bits) OVERRIDE; 120 virtual void AddMovement(const TimeTicks& event_time, 121 BitSet32 id_bits, 122 const Position* positions) OVERRIDE; 123 virtual bool GetEstimator(uint32_t id, 124 Estimator* out_estimator) const OVERRIDE; 125 126 private: 127 // Sample horizon. 128 // We don't use too much history by default since we want to react to quick 129 // changes in direction. 130 enum { HORIZON_MS = 100 }; 131 132 struct Movement { 133 TimeTicks event_time; 134 BitSet32 id_bits; 135 Position positions[VelocityTracker::MAX_POINTERS]; 136 137 inline const Position& GetPosition(uint32_t id) const { 138 return positions[id_bits.get_index_of_bit(id)]; 139 } 140 }; 141 142 float ChooseWeight(uint32_t index) const; 143 144 const uint32_t degree_; 145 const Weighting weighting_; 146 uint32_t index_; 147 Movement movements_[HISTORY_SIZE]; 148 }; 149 150 // Velocity tracker algorithm that uses an IIR filter. 151 class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy { 152 public: 153 // Degree must be 1 or 2. 154 explicit IntegratingVelocityTrackerStrategy(uint32_t degree); 155 virtual ~IntegratingVelocityTrackerStrategy(); 156 157 virtual void Clear() OVERRIDE; 158 virtual void ClearPointers(BitSet32 id_bits) OVERRIDE; 159 virtual void AddMovement(const TimeTicks& event_time, 160 BitSet32 id_bits, 161 const Position* positions) OVERRIDE; 162 virtual bool GetEstimator(uint32_t id, 163 Estimator* out_estimator) const OVERRIDE; 164 165 private: 166 // Current state estimate for a particular pointer. 167 struct State { 168 TimeTicks update_time; 169 uint32_t degree; 170 171 float xpos, xvel, xaccel; 172 float ypos, yvel, yaccel; 173 }; 174 175 const uint32_t degree_; 176 BitSet32 pointer_id_bits_; 177 State mPointerState[MotionEvent::MAX_POINTER_ID + 1]; 178 179 void InitState(State& state, 180 const TimeTicks& event_time, 181 float xpos, 182 float ypos) const; 183 void UpdateState(State& state, 184 const TimeTicks& event_time, 185 float xpos, 186 float ypos) const; 187 void PopulateEstimator(const State& state, Estimator* out_estimator) const; 188 }; 189 190 VelocityTrackerStrategy* CreateStrategy(VelocityTracker::Strategy strategy) { 191 switch (strategy) { 192 case VelocityTracker::LSQ1: 193 return new LeastSquaresVelocityTrackerStrategy(1); 194 case VelocityTracker::LSQ2: 195 return new LeastSquaresVelocityTrackerStrategy(2); 196 case VelocityTracker::LSQ3: 197 return new LeastSquaresVelocityTrackerStrategy(3); 198 case VelocityTracker::WLSQ2_DELTA: 199 return new LeastSquaresVelocityTrackerStrategy( 200 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA); 201 case VelocityTracker::WLSQ2_CENTRAL: 202 return new LeastSquaresVelocityTrackerStrategy( 203 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL); 204 case VelocityTracker::WLSQ2_RECENT: 205 return new LeastSquaresVelocityTrackerStrategy( 206 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT); 207 case VelocityTracker::INT1: 208 return new IntegratingVelocityTrackerStrategy(1); 209 case VelocityTracker::INT2: 210 return new IntegratingVelocityTrackerStrategy(2); 211 } 212 NOTREACHED() << "Unrecognized velocity tracker strategy: " << strategy; 213 return CreateStrategy(VelocityTracker::STRATEGY_DEFAULT); 214 } 215 216 } // namespace 217 218 // --- VelocityTracker --- 219 220 VelocityTracker::VelocityTracker() 221 : current_pointer_id_bits_(0), 222 active_pointer_id_(-1), 223 strategy_(CreateStrategy(STRATEGY_DEFAULT)) {} 224 225 VelocityTracker::VelocityTracker(Strategy strategy) 226 : current_pointer_id_bits_(0), 227 active_pointer_id_(-1), 228 strategy_(CreateStrategy(strategy)) {} 229 230 VelocityTracker::~VelocityTracker() {} 231 232 void VelocityTracker::Clear() { 233 current_pointer_id_bits_.clear(); 234 active_pointer_id_ = -1; 235 strategy_->Clear(); 236 } 237 238 void VelocityTracker::ClearPointers(BitSet32 id_bits) { 239 BitSet32 remaining_id_bits(current_pointer_id_bits_.value & ~id_bits.value); 240 current_pointer_id_bits_ = remaining_id_bits; 241 242 if (active_pointer_id_ >= 0 && id_bits.has_bit(active_pointer_id_)) { 243 active_pointer_id_ = !remaining_id_bits.is_empty() 244 ? remaining_id_bits.first_marked_bit() 245 : -1; 246 } 247 248 strategy_->ClearPointers(id_bits); 249 } 250 251 void VelocityTracker::AddMovement(const TimeTicks& event_time, 252 BitSet32 id_bits, 253 const Position* positions) { 254 while (id_bits.count() > MAX_POINTERS) 255 id_bits.clear_last_marked_bit(); 256 257 if ((current_pointer_id_bits_.value & id_bits.value) && 258 event_time >= (last_event_time_ + base::TimeDelta::FromMilliseconds( 259 kAssumePointerStoppedTimeMs))) { 260 // We have not received any movements for too long. Assume that all 261 // pointers 262 // have stopped. 263 strategy_->Clear(); 264 } 265 last_event_time_ = event_time; 266 267 current_pointer_id_bits_ = id_bits; 268 if (active_pointer_id_ < 0 || !id_bits.has_bit(active_pointer_id_)) 269 active_pointer_id_ = id_bits.is_empty() ? -1 : id_bits.first_marked_bit(); 270 271 strategy_->AddMovement(event_time, id_bits, positions); 272 } 273 274 void VelocityTracker::AddMovement(const MotionEvent& event) { 275 int32_t actionMasked = event.GetAction(); 276 277 switch (actionMasked) { 278 case MotionEvent::ACTION_DOWN: 279 // case MotionEvent::HOVER_ENTER: 280 // Clear all pointers on down before adding the new movement. 281 Clear(); 282 break; 283 case MotionEvent::ACTION_POINTER_DOWN: { 284 // Start a new movement trace for a pointer that just went down. 285 // We do this on down instead of on up because the client may want to 286 // query the final velocity for a pointer that just went up. 287 BitSet32 downIdBits; 288 downIdBits.mark_bit(event.GetPointerId(event.GetActionIndex())); 289 ClearPointers(downIdBits); 290 break; 291 } 292 case MotionEvent::ACTION_MOVE: 293 // case MotionEvent::ACTION_HOVER_MOVE: 294 break; 295 default: 296 // Ignore all other actions because they do not convey any new information 297 // about pointer movement. We also want to preserve the last known 298 // velocity of the pointers. 299 // Note that ACTION_UP and ACTION_POINTER_UP always report the last known 300 // position of the pointers that went up. ACTION_POINTER_UP does include 301 // the new position of pointers that remained down but we will also 302 // receive an ACTION_MOVE with this information if any of them actually 303 // moved. Since we don't know how many pointers will be going up at once 304 // it makes sense to just wait for the following ACTION_MOVE before adding 305 // the movement. 306 return; 307 } 308 309 size_t pointer_count = event.GetPointerCount(); 310 if (pointer_count > MAX_POINTERS) { 311 pointer_count = MAX_POINTERS; 312 } 313 314 BitSet32 id_bits; 315 for (size_t i = 0; i < pointer_count; i++) { 316 id_bits.mark_bit(event.GetPointerId(i)); 317 } 318 319 uint32_t pointer_index[MAX_POINTERS]; 320 for (size_t i = 0; i < pointer_count; i++) { 321 pointer_index[i] = id_bits.get_index_of_bit(event.GetPointerId(i)); 322 } 323 324 Position positions[MAX_POINTERS]; 325 size_t historySize = event.GetHistorySize(); 326 for (size_t h = 0; h < historySize; h++) { 327 for (size_t i = 0; i < pointer_count; i++) { 328 uint32_t index = pointer_index[i]; 329 positions[index].x = event.GetHistoricalX(i, h); 330 positions[index].y = event.GetHistoricalY(i, h); 331 } 332 AddMovement(event.GetHistoricalEventTime(h), id_bits, positions); 333 } 334 335 for (size_t i = 0; i < pointer_count; i++) { 336 uint32_t index = pointer_index[i]; 337 positions[index].x = event.GetX(i); 338 positions[index].y = event.GetY(i); 339 } 340 AddMovement(event.GetEventTime(), id_bits, positions); 341 } 342 343 bool VelocityTracker::GetVelocity(uint32_t id, 344 float* out_vx, 345 float* out_vy) const { 346 Estimator estimator; 347 if (GetEstimator(id, &estimator) && estimator.degree >= 1) { 348 *out_vx = estimator.xcoeff[1]; 349 *out_vy = estimator.ycoeff[1]; 350 return true; 351 } 352 *out_vx = 0; 353 *out_vy = 0; 354 return false; 355 } 356 357 void LeastSquaresVelocityTrackerStrategy::AddMovement( 358 const TimeTicks& event_time, 359 BitSet32 id_bits, 360 const Position* positions) { 361 if (++index_ == HISTORY_SIZE) { 362 index_ = 0; 363 } 364 365 Movement& movement = movements_[index_]; 366 movement.event_time = event_time; 367 movement.id_bits = id_bits; 368 uint32_t count = id_bits.count(); 369 for (uint32_t i = 0; i < count; i++) { 370 movement.positions[i] = positions[i]; 371 } 372 } 373 374 bool VelocityTracker::GetEstimator(uint32_t id, 375 Estimator* out_estimator) const { 376 return strategy_->GetEstimator(id, out_estimator); 377 } 378 379 // --- LeastSquaresVelocityTrackerStrategy --- 380 381 LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy( 382 uint32_t degree, 383 Weighting weighting) 384 : degree_(degree), weighting_(weighting) { 385 DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::MAX_DEGREE)); 386 Clear(); 387 } 388 389 LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {} 390 391 void LeastSquaresVelocityTrackerStrategy::Clear() { 392 index_ = 0; 393 movements_[0].id_bits.clear(); 394 } 395 396 /** 397 * Solves a linear least squares problem to obtain a N degree polynomial that 398 * fits the specified input data as nearly as possible. 399 * 400 * Returns true if a solution is found, false otherwise. 401 * 402 * The input consists of two vectors of data points X and Y with indices 0..m-1 403 * along with a weight vector W of the same size. 404 * 405 * The output is a vector B with indices 0..n that describes a polynomial 406 * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] 407 * X[i] * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is 408 * minimized. 409 * 410 * Accordingly, the weight vector W should be initialized by the caller with the 411 * reciprocal square root of the variance of the error in each input data point. 412 * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / 413 * stddev(Y[i]). 414 * The weights express the relative importance of each data point. If the 415 * weights are* all 1, then the data points are considered to be of equal 416 * importance when fitting the polynomial. It is a good idea to choose weights 417 * that diminish the importance of data points that may have higher than usual 418 * error margins. 419 * 420 * Errors among data points are assumed to be independent. W is represented 421 * here as a vector although in the literature it is typically taken to be a 422 * diagonal matrix. 423 * 424 * That is to say, the function that generated the input data can be 425 * approximated by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n. 426 * 427 * The coefficient of determination (R^2) is also returned to describe the 428 * goodness of fit of the model for the given data. It is a value between 0 429 * and 1, where 1 indicates perfect correspondence. 430 * 431 * This function first expands the X vector to a m by n matrix A such that 432 * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then 433 * multiplies it by w[i]./ 434 * 435 * Then it calculates the QR decomposition of A yielding an m by m orthonormal 436 * matrix Q and an m by n upper triangular matrix R. Because R is upper 437 * triangular (lower part is all zeroes), we can simplify the decomposition into 438 * an m by n matrix Q1 and a n by n matrix R1 such that A = Q1 R1. 439 * 440 * Finally we solve the system of linear equations given by 441 * R1 B = (Qtranspose W Y) to find B. 442 * 443 * For efficiency, we lay out A and Q column-wise in memory because we 444 * frequently operate on the column vectors. Conversely, we lay out R row-wise. 445 * 446 * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares 447 * http://en.wikipedia.org/wiki/Gram-Schmidt 448 */ 449 static bool SolveLeastSquares(const float* x, 450 const float* y, 451 const float* w, 452 uint32_t m, 453 uint32_t n, 454 float* out_b, 455 float* out_det) { 456 // MSVC does not support variable-length arrays (used by the original Android 457 // implementation of this function). 458 #if defined(COMPILER_MSVC) 459 enum { 460 M_ARRAY_LENGTH = LeastSquaresVelocityTrackerStrategy::HISTORY_SIZE, 461 N_ARRAY_LENGTH = Estimator::MAX_DEGREE 462 }; 463 DCHECK_LE(m, static_cast<uint32_t>(M_ARRAY_LENGTH)); 464 DCHECK_LE(n, static_cast<uint32_t>(N_ARRAY_LENGTH)); 465 #else 466 const uint32_t M_ARRAY_LENGTH = m; 467 const uint32_t N_ARRAY_LENGTH = n; 468 #endif 469 470 // Expand the X vector to a matrix A, pre-multiplied by the weights. 471 float a[N_ARRAY_LENGTH][M_ARRAY_LENGTH]; // column-major order 472 for (uint32_t h = 0; h < m; h++) { 473 a[0][h] = w[h]; 474 for (uint32_t i = 1; i < n; i++) { 475 a[i][h] = a[i - 1][h] * x[h]; 476 } 477 } 478 479 // Apply the Gram-Schmidt process to A to obtain its QR decomposition. 480 481 // Orthonormal basis, column-major order. 482 float q[N_ARRAY_LENGTH][M_ARRAY_LENGTH]; 483 // Upper triangular matrix, row-major order. 484 float r[N_ARRAY_LENGTH][N_ARRAY_LENGTH]; 485 for (uint32_t j = 0; j < n; j++) { 486 for (uint32_t h = 0; h < m; h++) { 487 q[j][h] = a[j][h]; 488 } 489 for (uint32_t i = 0; i < j; i++) { 490 float dot = VectorDot(&q[j][0], &q[i][0], m); 491 for (uint32_t h = 0; h < m; h++) { 492 q[j][h] -= dot * q[i][h]; 493 } 494 } 495 496 float norm = VectorNorm(&q[j][0], m); 497 if (norm < 0.000001f) { 498 // vectors are linearly dependent or zero so no solution 499 return false; 500 } 501 502 float invNorm = 1.0f / norm; 503 for (uint32_t h = 0; h < m; h++) { 504 q[j][h] *= invNorm; 505 } 506 for (uint32_t i = 0; i < n; i++) { 507 r[j][i] = i < j ? 0 : VectorDot(&q[j][0], &a[i][0], m); 508 } 509 } 510 511 // Solve R B = Qt W Y to find B. This is easy because R is upper triangular. 512 // We just work from bottom-right to top-left calculating B's coefficients. 513 float wy[M_ARRAY_LENGTH]; 514 for (uint32_t h = 0; h < m; h++) { 515 wy[h] = y[h] * w[h]; 516 } 517 for (uint32_t i = n; i-- != 0;) { 518 out_b[i] = VectorDot(&q[i][0], wy, m); 519 for (uint32_t j = n - 1; j > i; j--) { 520 out_b[i] -= r[i][j] * out_b[j]; 521 } 522 out_b[i] /= r[i][i]; 523 } 524 525 // Calculate the coefficient of determination as 1 - (SSerr / SStot) where 526 // SSerr is the residual sum of squares (variance of the error), 527 // and SStot is the total sum of squares (variance of the data) where each 528 // has been weighted. 529 float ymean = 0; 530 for (uint32_t h = 0; h < m; h++) { 531 ymean += y[h]; 532 } 533 ymean /= m; 534 535 float sserr = 0; 536 float sstot = 0; 537 for (uint32_t h = 0; h < m; h++) { 538 float err = y[h] - out_b[0]; 539 float term = 1; 540 for (uint32_t i = 1; i < n; i++) { 541 term *= x[h]; 542 err -= term * out_b[i]; 543 } 544 sserr += w[h] * w[h] * err * err; 545 float var = y[h] - ymean; 546 sstot += w[h] * w[h] * var * var; 547 } 548 *out_det = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1; 549 return true; 550 } 551 552 void LeastSquaresVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) { 553 BitSet32 remaining_id_bits(movements_[index_].id_bits.value & ~id_bits.value); 554 movements_[index_].id_bits = remaining_id_bits; 555 } 556 557 bool LeastSquaresVelocityTrackerStrategy::GetEstimator( 558 uint32_t id, 559 Estimator* out_estimator) const { 560 out_estimator->Clear(); 561 562 // Iterate over movement samples in reverse time order and collect samples. 563 float x[HISTORY_SIZE]; 564 float y[HISTORY_SIZE]; 565 float w[HISTORY_SIZE]; 566 float time[HISTORY_SIZE]; 567 uint32_t m = 0; 568 uint32_t index = index_; 569 const base::TimeDelta horizon = base::TimeDelta::FromMilliseconds(HORIZON_MS); 570 const Movement& newest_movement = movements_[index_]; 571 do { 572 const Movement& movement = movements_[index]; 573 if (!movement.id_bits.has_bit(id)) 574 break; 575 576 TimeDelta age = newest_movement.event_time - movement.event_time; 577 if (age > horizon) 578 break; 579 580 const Position& position = movement.GetPosition(id); 581 x[m] = position.x; 582 y[m] = position.y; 583 w[m] = ChooseWeight(index); 584 time[m] = -age.InSecondsF(); 585 index = (index == 0 ? HISTORY_SIZE : index) - 1; 586 } while (++m < HISTORY_SIZE); 587 588 if (m == 0) 589 return false; // no data 590 591 // Calculate a least squares polynomial fit. 592 uint32_t degree = degree_; 593 if (degree > m - 1) 594 degree = m - 1; 595 596 if (degree >= 1) { 597 float xdet, ydet; 598 uint32_t n = degree + 1; 599 if (SolveLeastSquares(time, x, w, m, n, out_estimator->xcoeff, &xdet) && 600 SolveLeastSquares(time, y, w, m, n, out_estimator->ycoeff, &ydet)) { 601 out_estimator->time = newest_movement.event_time; 602 out_estimator->degree = degree; 603 out_estimator->confidence = xdet * ydet; 604 return true; 605 } 606 } 607 608 // No velocity data available for this pointer, but we do have its current 609 // position. 610 out_estimator->xcoeff[0] = x[0]; 611 out_estimator->ycoeff[0] = y[0]; 612 out_estimator->time = newest_movement.event_time; 613 out_estimator->degree = 0; 614 out_estimator->confidence = 1; 615 return true; 616 } 617 618 float LeastSquaresVelocityTrackerStrategy::ChooseWeight(uint32_t index) const { 619 switch (weighting_) { 620 case WEIGHTING_DELTA: { 621 // Weight points based on how much time elapsed between them and the next 622 // point so that points that "cover" a shorter time span are weighed less. 623 // delta 0ms: 0.5 624 // delta 10ms: 1.0 625 if (index == index_) { 626 return 1.0f; 627 } 628 uint32_t next_index = (index + 1) % HISTORY_SIZE; 629 float delta_millis = 630 static_cast<float>((movements_[next_index].event_time - 631 movements_[index].event_time).InMillisecondsF()); 632 if (delta_millis < 0) 633 return 0.5f; 634 if (delta_millis < 10) 635 return 0.5f + delta_millis * 0.05; 636 637 return 1.0f; 638 } 639 640 case WEIGHTING_CENTRAL: { 641 // Weight points based on their age, weighing very recent and very old 642 // points less. 643 // age 0ms: 0.5 644 // age 10ms: 1.0 645 // age 50ms: 1.0 646 // age 60ms: 0.5 647 float age_millis = 648 static_cast<float>((movements_[index_].event_time - 649 movements_[index].event_time).InMillisecondsF()); 650 if (age_millis < 0) 651 return 0.5f; 652 if (age_millis < 10) 653 return 0.5f + age_millis * 0.05; 654 if (age_millis < 50) 655 return 1.0f; 656 if (age_millis < 60) 657 return 0.5f + (60 - age_millis) * 0.05; 658 659 return 0.5f; 660 } 661 662 case WEIGHTING_RECENT: { 663 // Weight points based on their age, weighing older points less. 664 // age 0ms: 1.0 665 // age 50ms: 1.0 666 // age 100ms: 0.5 667 float age_millis = 668 static_cast<float>((movements_[index_].event_time - 669 movements_[index].event_time).InMillisecondsF()); 670 if (age_millis < 50) { 671 return 1.0f; 672 } 673 if (age_millis < 100) { 674 return 0.5f + (100 - age_millis) * 0.01f; 675 } 676 return 0.5f; 677 } 678 679 case WEIGHTING_NONE: 680 default: 681 return 1.0f; 682 } 683 } 684 685 // --- IntegratingVelocityTrackerStrategy --- 686 687 IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy( 688 uint32_t degree) 689 : degree_(degree) { 690 DCHECK_LT(degree_, static_cast<uint32_t>(Estimator::MAX_DEGREE)); 691 } 692 693 IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {} 694 695 void IntegratingVelocityTrackerStrategy::Clear() { pointer_id_bits_.clear(); } 696 697 void IntegratingVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) { 698 pointer_id_bits_.value &= ~id_bits.value; 699 } 700 701 void IntegratingVelocityTrackerStrategy::AddMovement( 702 const TimeTicks& event_time, 703 BitSet32 id_bits, 704 const Position* positions) { 705 uint32_t index = 0; 706 for (BitSet32 iter_id_bits(id_bits); !iter_id_bits.is_empty();) { 707 uint32_t id = iter_id_bits.clear_first_marked_bit(); 708 State& state = mPointerState[id]; 709 const Position& position = positions[index++]; 710 if (pointer_id_bits_.has_bit(id)) 711 UpdateState(state, event_time, position.x, position.y); 712 else 713 InitState(state, event_time, position.x, position.y); 714 } 715 716 pointer_id_bits_ = id_bits; 717 } 718 719 bool IntegratingVelocityTrackerStrategy::GetEstimator( 720 uint32_t id, 721 Estimator* out_estimator) const { 722 out_estimator->Clear(); 723 724 if (pointer_id_bits_.has_bit(id)) { 725 const State& state = mPointerState[id]; 726 PopulateEstimator(state, out_estimator); 727 return true; 728 } 729 730 return false; 731 } 732 733 void IntegratingVelocityTrackerStrategy::InitState(State& state, 734 const TimeTicks& event_time, 735 float xpos, 736 float ypos) const { 737 state.update_time = event_time; 738 state.degree = 0; 739 state.xpos = xpos; 740 state.xvel = 0; 741 state.xaccel = 0; 742 state.ypos = ypos; 743 state.yvel = 0; 744 state.yaccel = 0; 745 } 746 747 void IntegratingVelocityTrackerStrategy::UpdateState( 748 State& state, 749 const TimeTicks& event_time, 750 float xpos, 751 float ypos) const { 752 const base::TimeDelta MIN_TIME_DELTA = TimeDelta::FromMicroseconds(2); 753 const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds 754 755 if (event_time <= state.update_time + MIN_TIME_DELTA) 756 return; 757 758 float dt = static_cast<float>((event_time - state.update_time).InSecondsF()); 759 state.update_time = event_time; 760 761 float xvel = (xpos - state.xpos) / dt; 762 float yvel = (ypos - state.ypos) / dt; 763 if (state.degree == 0) { 764 state.xvel = xvel; 765 state.yvel = yvel; 766 state.degree = 1; 767 } else { 768 float alpha = dt / (FILTER_TIME_CONSTANT + dt); 769 if (degree_ == 1) { 770 state.xvel += (xvel - state.xvel) * alpha; 771 state.yvel += (yvel - state.yvel) * alpha; 772 } else { 773 float xaccel = (xvel - state.xvel) / dt; 774 float yaccel = (yvel - state.yvel) / dt; 775 if (state.degree == 1) { 776 state.xaccel = xaccel; 777 state.yaccel = yaccel; 778 state.degree = 2; 779 } else { 780 state.xaccel += (xaccel - state.xaccel) * alpha; 781 state.yaccel += (yaccel - state.yaccel) * alpha; 782 } 783 state.xvel += (state.xaccel * dt) * alpha; 784 state.yvel += (state.yaccel * dt) * alpha; 785 } 786 } 787 state.xpos = xpos; 788 state.ypos = ypos; 789 } 790 791 void IntegratingVelocityTrackerStrategy::PopulateEstimator( 792 const State& state, 793 Estimator* out_estimator) const { 794 out_estimator->time = state.update_time; 795 out_estimator->confidence = 1.0f; 796 out_estimator->degree = state.degree; 797 out_estimator->xcoeff[0] = state.xpos; 798 out_estimator->xcoeff[1] = state.xvel; 799 out_estimator->xcoeff[2] = state.xaccel / 2; 800 out_estimator->ycoeff[0] = state.ypos; 801 out_estimator->ycoeff[1] = state.yvel; 802 out_estimator->ycoeff[2] = state.yaccel / 2; 803 } 804 805 } // namespace ui 806