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