Index: content/browser/renderer_host/input/gestures/velocity_tracker.cc |
diff --git a/content/browser/renderer_host/input/gestures/velocity_tracker.cc b/content/browser/renderer_host/input/gestures/velocity_tracker.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..bcd8e09b41a2c53672683241691fdb5dbcc52a72 |
--- /dev/null |
+++ b/content/browser/renderer_host/input/gestures/velocity_tracker.cc |
@@ -0,0 +1,822 @@ |
+// Copyright 2014 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "content/browser/renderer_host/input/gestures/velocity_tracker.h" |
+ |
+#include <math.h> |
+ |
+#include "base/logging.h" |
+#include "content/browser/renderer_host/input/gestures/motion_event.h" |
+ |
+using base::TimeDelta; |
+using base::TimeTicks; |
+ |
+namespace content { |
+namespace { |
+ |
+// Threshold for determining that a pointer has stopped moving. |
+// Some input devices do not send ACTION_MOVE events in the case where a pointer |
+// hasstopped. We need to detect this case so that we can accurately predict |
+// the velocity after the pointer starts moving again. |
+const TimeDelta ASSUME_POINTER_STOPPED_TIME = TimeDelta::FromMilliseconds(40); |
+ |
+// The default velocity tracker strategy. |
+// Although other strategies are available for testing and comparison purposes, |
+// this is the strategy that applications will actually use. Be very careful |
+// when adjusting the default strategy because it can dramatically affect |
+// (often in a bad way) the user experience. |
+const char DEFAULT_STRATEGY[] = "lsq2"; |
+ |
+static float VectorDot(const float* a, const float* b, uint32_t m) { |
+ float r = 0; |
+ while (m--) { |
+ r += *(a++) * *(b++); |
+ } |
+ return r; |
+} |
+ |
+static float VectorNorm(const float* a, uint32_t m) { |
+ float r = 0; |
+ while (m--) { |
+ float t = *(a++); |
+ r += t * t; |
+ } |
+ return sqrtf(r); |
+} |
+ |
+struct Estimator { |
+ enum { MAX_DEGREE = 4 }; |
+ |
+ // Estimator time base. |
+ TimeTicks time; |
+ |
+ // Polynomial coefficients describing motion in X and Y. |
+ float xCoeff[MAX_DEGREE + 1], yCoeff[MAX_DEGREE + 1]; |
+ |
+ // Polynomial degree (number of coefficients), or zero if no information is |
+ // available. |
+ uint32_t degree; |
+ |
+ // Confidence (coefficient of determination), between 0 (no fit) |
+ // and 1 (perfect fit). |
+ float confidence; |
+ |
+ inline void Clear() { |
+ time = TimeTicks(); |
+ degree = 0; |
+ confidence = 0; |
+ for (size_t i = 0; i <= MAX_DEGREE; i++) { |
+ xCoeff[i] = 0; |
+ yCoeff[i] = 0; |
+ } |
+ } |
+}; |
+ |
+// Velocity tracker algorithm based on least-squares linear regression. |
+class LeastSquaresVelocityTrackerStrategy : public VelocityTrackerStrategy { |
+ public: |
+ enum Weighting { |
+ // No weights applied. All data points are equally reliable. |
+ WEIGHTING_NONE, |
+ |
+ // Weight by time delta. Data points clustered together are weighted less. |
+ WEIGHTING_DELTA, |
+ |
+ // Weight such that points within a certain horizon are weighed more than |
+ // those outside of that horizon. |
+ WEIGHTING_CENTRAL, |
+ |
+ // Weight such that points older than a certain amount are weighed less. |
+ WEIGHTING_RECENT, |
+ }; |
+ |
+ // Degree must be no greater than Estimator::MAX_DEGREE. |
+ LeastSquaresVelocityTrackerStrategy(uint32_t degree, |
+ Weighting weighting = WEIGHTING_NONE); |
+ virtual ~LeastSquaresVelocityTrackerStrategy(); |
+ |
+ virtual void Clear() OVERRIDE; |
+ virtual void ClearPointers(BitSet32 id_bits) OVERRIDE; |
+ virtual void AddMovement(const TimeTicks& event_time, |
+ BitSet32 id_bits, |
+ const VelocityTracker::Position* positions) OVERRIDE; |
+ virtual bool GetEstimator(uint32_t id, |
+ Estimator* out_estimator) const OVERRIDE; |
+ |
+ private: |
+ // Sample horizon. |
+ // We don't use too much history by default since we want to react to quick |
+ // changes in direction. |
+ static const TimeDelta HORIZON; |
+ |
+ // Number of samples to keep. |
+ enum { HISTORY_SIZE = 20 }; |
+ |
+ struct Movement { |
+ TimeTicks event_time; |
+ BitSet32 id_bits; |
+ VelocityTracker::Position positions[VelocityTracker::MAX_POINTERS]; |
+ |
+ inline const VelocityTracker::Position& GetPosition(uint32_t id) const { |
+ return positions[id_bits.get_index_of_bit(id)]; |
+ } |
+ }; |
+ |
+ float ChooseWeight(uint32_t index) const; |
+ |
+ const uint32_t mDegree; |
+ const Weighting mWeighting; |
+ uint32_t index_; |
+ Movement movements_[HISTORY_SIZE]; |
+}; |
+ |
+// Velocity tracker algorithm that uses an IIR filter. |
+class IntegratingVelocityTrackerStrategy : public VelocityTrackerStrategy { |
+ public: |
+ // Degree must be 1 or 2. |
+ explicit IntegratingVelocityTrackerStrategy(uint32_t degree); |
+ virtual ~IntegratingVelocityTrackerStrategy(); |
+ |
+ virtual void Clear() OVERRIDE; |
+ virtual void ClearPointers(BitSet32 id_bits) OVERRIDE; |
+ virtual void AddMovement(const TimeTicks& event_time, |
+ BitSet32 id_bits, |
+ const VelocityTracker::Position* positions) OVERRIDE; |
+ virtual bool GetEstimator(uint32_t id, |
+ Estimator* out_estimator) const OVERRIDE; |
+ |
+ private: |
+ // Current state estimate for a particular pointer. |
+ struct State { |
+ TimeTicks updateTime; |
+ uint32_t degree; |
+ |
+ float xpos, xvel, xaccel; |
+ float ypos, yvel, yaccel; |
+ }; |
+ |
+ const uint32_t mDegree; |
+ BitSet32 pointer_id_bits_; |
+ State mPointerState[VelocityTracker::MAX_POINTER_ID + 1]; |
+ |
+ void InitState(State& state, |
+ const TimeTicks& event_time, |
+ float xpos, |
+ float ypos) const; |
+ void UpdateState(State& state, |
+ const TimeTicks& event_time, |
+ float xpos, |
+ float ypos) const; |
+ void PopulateEstimator(const State& state, Estimator* out_estimator) const; |
+}; |
+ |
+} // namespace |
+ |
+// --- VelocityTracker --- |
+ |
+VelocityTracker::VelocityTracker() |
+ : current_pointer_id_bits_(0), active_pointer_id_(-1) { |
+ if (!ConfigureStrategy(DEFAULT_STRATEGY)) |
+ NOTREACHED() << "Unrecognized velocity tracker strategy: " |
+ << DEFAULT_STRATEGY; |
+} |
+ |
+VelocityTracker::VelocityTracker(const char* strategy) |
+ : current_pointer_id_bits_(0), active_pointer_id_(-1) { |
+ if (!strategy) |
+ strategy = DEFAULT_STRATEGY; |
+ |
+ if (!ConfigureStrategy(strategy)) |
+ NOTREACHED() << "Unrecognized velocity tracker strategy: " << strategy; |
+} |
+ |
+VelocityTracker::~VelocityTracker() {} |
+ |
+void VelocityTracker::Clear() { |
+ current_pointer_id_bits_.clear(); |
+ active_pointer_id_ = -1; |
+ strategy_->Clear(); |
+} |
+ |
+void VelocityTracker::ClearPointers(BitSet32 id_bits) { |
+ BitSet32 remaining_id_bits(current_pointer_id_bits_.value & ~id_bits.value); |
+ current_pointer_id_bits_ = remaining_id_bits; |
+ |
+ if (active_pointer_id_ >= 0 && id_bits.has_bit(active_pointer_id_)) { |
+ active_pointer_id_ = |
+ !remaining_id_bits.is_empty() ? remaining_id_bits.first_marked_bit() |
+ : -1; |
+ } |
+ |
+ strategy_->ClearPointers(id_bits); |
+} |
+ |
+void VelocityTracker::AddMovement(const TimeTicks& event_time, |
+ BitSet32 id_bits, |
+ const Position* positions) { |
+ while (id_bits.count() > MAX_POINTERS) |
+ id_bits.clear_last_marked_bit(); |
+ |
+ if ((current_pointer_id_bits_.value & id_bits.value) && |
+ event_time >= last_event_time_ + ASSUME_POINTER_STOPPED_TIME) { |
+ // We have not received any movements for too long. Assume that all |
+ // pointers |
+ // have stopped. |
+ strategy_->Clear(); |
+ } |
+ last_event_time_ = event_time; |
+ |
+ current_pointer_id_bits_ = id_bits; |
+ if (active_pointer_id_ < 0 || !id_bits.has_bit(active_pointer_id_)) |
+ active_pointer_id_ = id_bits.is_empty() ? -1 : id_bits.first_marked_bit(); |
+ |
+ strategy_->AddMovement(event_time, id_bits, positions); |
+} |
+ |
+void VelocityTracker::AddMovement(const MotionEvent& event) { |
+ int32_t actionMasked = event.GetActionMasked(); |
+ |
+ switch (actionMasked) { |
+ case MotionEvent::ACTION_DOWN: |
+ // case MotionEvent::HOVER_ENTER: |
+ // Clear all pointers on down before adding the new movement. |
+ Clear(); |
+ break; |
+ case MotionEvent::ACTION_POINTER_DOWN: { |
+ // Start a new movement trace for a pointer that just went down. |
+ // We do this on down instead of on up because the client may want to |
+ // query the |
+ // final velocity for a pointer that just went up. |
+ BitSet32 downIdBits; |
+ downIdBits.mark_bit(event.GetPointerId(event.GetActionIndex())); |
+ ClearPointers(downIdBits); |
+ break; |
+ } |
+ case MotionEvent::ACTION_MOVE: |
+ // case AMOTION_EVENT_ACTION_HOVER_MOVE: |
+ break; |
+ default: |
+ // Ignore all other actions because they do not convey any new information |
+ // about |
+ // pointer movement. We also want to preserve the last known velocity of |
+ // the pointers. |
+ // Note that ACTION_UP and ACTION_POINTER_UP always report the last known |
+ // position |
+ // of the pointers that went up. ACTION_POINTER_UP does include the new |
+ // position of |
+ // pointers that remained down but we will also receive an ACTION_MOVE |
+ // with this |
+ // information if any of them actually moved. Since we don't know how |
+ // many pointers |
+ // will be going up at once it makes sense to just wait for the following |
+ // ACTION_MOVE |
+ // before adding the movement. |
+ return; |
+ } |
+ |
+ size_t pointerCount = event.GetPointerCount(); |
+ if (pointerCount > MAX_POINTERS) |
+ pointerCount = MAX_POINTERS; |
+ |
+ BitSet32 id_bits; |
+ for (size_t i = 0; i < pointerCount; i++) |
+ id_bits.mark_bit(event.GetPointerId(i)); |
+ |
+ uint32_t pointerIndex[MAX_POINTERS]; |
+ for (size_t i = 0; i < pointerCount; i++) |
+ pointerIndex[i] = id_bits.get_index_of_bit(event.GetPointerId(i)); |
+ |
+ TimeTicks event_time; |
+ Position positions[pointerCount]; |
+ |
+ size_t historySize = event.GetHistorySize(); |
+ for (size_t h = 0; h < historySize; h++) { |
+ event_time = event.GetHistoricalEventTime(h); |
+ for (size_t i = 0; i < pointerCount; i++) { |
+ uint32_t index = pointerIndex[i]; |
+ positions[index].x = event.GetHistoricalX(i, h); |
+ positions[index].y = event.GetHistoricalY(i, h); |
+ } |
+ AddMovement(event_time, id_bits, positions); |
+ } |
+ |
+ event_time = event.GetEventTime(); |
+ for (size_t i = 0; i < pointerCount; i++) { |
+ uint32_t index = pointerIndex[i]; |
+ positions[index].x = event.GetX(i); |
+ positions[index].y = event.GetY(i); |
+ } |
+ AddMovement(event_time, id_bits, positions); |
+} |
+ |
+bool VelocityTracker::GetVelocity(uint32_t id, |
+ float* outVx, |
+ float* outVy) const { |
+ Estimator estimator; |
+ if (GetEstimator(id, &estimator) && estimator.degree >= 1) { |
+ *outVx = estimator.xCoeff[1]; |
+ *outVy = estimator.yCoeff[1]; |
+ return true; |
+ } |
+ *outVx = 0; |
+ *outVy = 0; |
+ return false; |
+} |
+ |
+void LeastSquaresVelocityTrackerStrategy::AddMovement( |
+ const TimeTicks& event_time, |
+ BitSet32 id_bits, |
+ const VelocityTracker::Position* positions) { |
+ if (++index_ == HISTORY_SIZE) { |
+ index_ = 0; |
+ } |
+ |
+ Movement& movement = movements_[index_]; |
+ movement.event_time = event_time; |
+ movement.id_bits = id_bits; |
+ uint32_t count = id_bits.count(); |
+ for (uint32_t i = 0; i < count; i++) { |
+ movement.positions[i] = positions[i]; |
+ } |
+} |
+ |
+bool VelocityTracker::GetEstimator(uint32_t id, |
+ Estimator* out_estimator) const { |
+ return strategy_->GetEstimator(id, out_estimator); |
+} |
+ |
+ |
+bool VelocityTracker::ConfigureStrategy(const char* strategy) { |
+ strategy_.reset(CreateStrategy(strategy)); |
+ return !!strategy_; |
+} |
+ |
+VelocityTrackerStrategy* VelocityTracker::CreateStrategy(const char* strategy) { |
+ if (!strcmp("lsq1", strategy)) { |
+ // 1st order least squares. Quality: POOR. |
+ // Frequently underfits the touch data especially when the finger |
+ // accelerates |
+ // or changes direction. Often underestimates velocity. The direction |
+ // is overly influenced by historical touch points. |
+ return new LeastSquaresVelocityTrackerStrategy(1); |
+ } |
+ if (!strcmp("lsq2", strategy)) { |
+ // 2nd order least squares. Quality: VERY GOOD. |
+ // Pretty much ideal, but can be confused by certain kinds of touch data, |
+ // particularly if the panel has a tendency to generate delayed, |
+ // duplicate or jittery touch coordinates when the finger is released. |
+ return new LeastSquaresVelocityTrackerStrategy(2); |
+ } |
+ if (!strcmp("lsq3", strategy)) { |
+ // 3rd order least squares. Quality: UNUSABLE. |
+ // Frequently overfits the touch data yielding wildly divergent estimates |
+ // of the velocity when the finger is released. |
+ return new LeastSquaresVelocityTrackerStrategy(3); |
+ } |
+ if (!strcmp("wlsq2-delta", strategy)) { |
+ // 2nd order weighted least squares, delta weighting. Quality: EXPERIMENTAL |
+ return new LeastSquaresVelocityTrackerStrategy( |
+ 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_DELTA); |
+ } |
+ if (!strcmp("wlsq2-central", strategy)) { |
+ // 2nd order weighted least squares, central weighting. Quality: |
+ // EXPERIMENTAL |
+ return new LeastSquaresVelocityTrackerStrategy( |
+ 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_CENTRAL); |
+ } |
+ if (!strcmp("wlsq2-recent", strategy)) { |
+ // 2nd order weighted least squares, recent weighting. Quality: |
+ // EXPERIMENTAL |
+ return new LeastSquaresVelocityTrackerStrategy( |
+ 2, LeastSquaresVelocityTrackerStrategy::WEIGHTING_RECENT); |
+ } |
+ if (!strcmp("int1", strategy)) { |
+ // 1st order integrating filter. Quality: GOOD. |
+ // Not as good as 'lsq2' because it cannot estimate acceleration but it is |
+ // more tolerant of errors. Like 'lsq1', this strategy tends to |
+ // underestimate |
+ // the velocity of a fling but this strategy tends to respond to changes in |
+ // direction more quickly and accurately. |
+ return new IntegratingVelocityTrackerStrategy(1); |
+ } |
+ if (!strcmp("int2", strategy)) { |
+ // 2nd order integrating filter. Quality: EXPERIMENTAL. |
+ // For comparison purposes only. Unlike 'int1' this strategy can compensate |
+ // for acceleration but it typically overestimates the effect. |
+ return new IntegratingVelocityTrackerStrategy(2); |
+ } |
+ return NULL; |
+} |
+ |
+// --- LeastSquaresVelocityTrackerStrategy --- |
+ |
+const TimeDelta LeastSquaresVelocityTrackerStrategy::HORIZON = |
+ TimeDelta::FromMilliseconds(100); |
+ |
+LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy( |
+ uint32_t degree, |
+ Weighting weighting) |
+ : mDegree(degree), mWeighting(weighting) { |
+ Clear(); |
+} |
+ |
+LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {} |
+ |
+void LeastSquaresVelocityTrackerStrategy::Clear() { |
+ index_ = 0; |
+ movements_[0].id_bits.clear(); |
+} |
+ |
+/** |
+ * Solves a linear least squares problem to obtain a N degree polynomial that |
+ * fits the specified input data as nearly as possible. |
+ * |
+ * Returns true if a solution is found, false otherwise. |
+ * |
+ * The input consists of two vectors of data points X and Y with indices 0..m-1 |
+ * along with a weight vector W of the same size. |
+ * |
+ * The output is a vector B with indices 0..n that describes a polynomial |
+ * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] |
+ * X[i] * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is |
+ * minimized. |
+ * |
+ * Accordingly, the weight vector W should be initialized by the caller with the |
+ * reciprocal square root of the variance of the error in each input data point. |
+ * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / |
+ * stddev(Y[i]). |
+ * The weights express the relative importance of each data point. If the |
+ * weights are* all 1, then the data points are considered to be of equal |
+ * importance when fitting the polynomial. It is a good idea to choose weights |
+ * that diminish the importance of data points that may have higher than usual |
+ * error margins. |
+ * |
+ * Errors among data points are assumed to be independent. W is represented |
+ * here as a vector although in the literature it is typically taken to be a |
+ * diagonal matrix. |
+ * |
+ * That is to say, the function that generated the input data can be |
+ * approximated by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n. |
+ * |
+ * The coefficient of determination (R^2) is also returned to describe the |
+ * goodness of fit of the model for the given data. It is a value between 0 |
+ * and 1, where 1 indicates perfect correspondence. |
+ * |
+ * This function first expands the X vector to a m by n matrix A such that |
+ * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then |
+ * multiplies it by w[i]./ |
+ * |
+ * Then it calculates the QR decomposition of A yielding an m by m orthonormal |
+ * matrix Q and an m by n upper triangular matrix R. Because R is upper |
+ * triangular (lower part is all zeroes), we can simplify the decomposition into |
+ * an m by n matrix Q1 and a n by n matrix R1 such that A = Q1 R1. |
+ * |
+ * Finally we solve the system of linear equations given by |
+ * R1 B = (Qtranspose W Y) to find B. |
+ * |
+ * For efficiency, we lay out A and Q column-wise in memory because we |
+ * frequently operate on the column vectors. Conversely, we lay out R row-wise. |
+ * |
+ * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares |
+ * http://en.wikipedia.org/wiki/Gram-Schmidt |
+ */ |
+static bool SolveLeastSquares(const float* x, |
+ const float* y, |
+ const float* w, |
+ uint32_t m, |
+ uint32_t n, |
+ float* outB, |
+ float* outDet) { |
+ // Expand the X vector to a matrix A, pre-multiplied by the weights. |
+ float a[n][m]; // column-major order |
+ for (uint32_t h = 0; h < m; h++) { |
+ a[0][h] = w[h]; |
+ for (uint32_t i = 1; i < n; i++) { |
+ a[i][h] = a[i - 1][h] * x[h]; |
+ } |
+ } |
+ |
+ // Apply the Gram-Schmidt process to A to obtain its QR decomposition. |
+ float q[n][m]; // orthonormal basis, column-major order |
+ float r[n][n]; // upper triangular matrix, row-major order |
+ for (uint32_t j = 0; j < n; j++) { |
+ for (uint32_t h = 0; h < m; h++) { |
+ q[j][h] = a[j][h]; |
+ } |
+ for (uint32_t i = 0; i < j; i++) { |
+ float dot = VectorDot(&q[j][0], &q[i][0], m); |
+ for (uint32_t h = 0; h < m; h++) { |
+ q[j][h] -= dot * q[i][h]; |
+ } |
+ } |
+ |
+ float norm = VectorNorm(&q[j][0], m); |
+ if (norm < 0.000001f) { |
+ // vectors are linearly dependent or zero so no solution |
+ return false; |
+ } |
+ |
+ float invNorm = 1.0f / norm; |
+ for (uint32_t h = 0; h < m; h++) { |
+ q[j][h] *= invNorm; |
+ } |
+ for (uint32_t i = 0; i < n; i++) { |
+ r[j][i] = i < j ? 0 : VectorDot(&q[j][0], &a[i][0], m); |
+ } |
+ } |
+ |
+ // Solve R B = Qt W Y to find B. This is easy because R is upper triangular. |
+ // We just work from bottom-right to top-left calculating B's coefficients. |
+ float wy[m]; |
+ for (uint32_t h = 0; h < m; h++) { |
+ wy[h] = y[h] * w[h]; |
+ } |
+ for (uint32_t i = n; i-- != 0;) { |
+ outB[i] = VectorDot(&q[i][0], wy, m); |
+ for (uint32_t j = n - 1; j > i; j--) { |
+ outB[i] -= r[i][j] * outB[j]; |
+ } |
+ outB[i] /= r[i][i]; |
+ } |
+ |
+ // Calculate the coefficient of determination as 1 - (SSerr / SStot) where |
+ // SSerr is the residual sum of squares (variance of the error), |
+ // and SStot is the total sum of squares (variance of the data) where each |
+ // has been weighted. |
+ float ymean = 0; |
+ for (uint32_t h = 0; h < m; h++) { |
+ ymean += y[h]; |
+ } |
+ ymean /= m; |
+ |
+ float sserr = 0; |
+ float sstot = 0; |
+ for (uint32_t h = 0; h < m; h++) { |
+ float err = y[h] - outB[0]; |
+ float term = 1; |
+ for (uint32_t i = 1; i < n; i++) { |
+ term *= x[h]; |
+ err -= term * outB[i]; |
+ } |
+ sserr += w[h] * w[h] * err * err; |
+ float var = y[h] - ymean; |
+ sstot += w[h] * w[h] * var * var; |
+ } |
+ *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1; |
+ return true; |
+} |
+ |
+void LeastSquaresVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) { |
+ BitSet32 remaining_id_bits(movements_[index_].id_bits.value & ~id_bits.value); |
+ movements_[index_].id_bits = remaining_id_bits; |
+} |
+ |
+bool LeastSquaresVelocityTrackerStrategy::GetEstimator( |
+ uint32_t id, |
+ Estimator* out_estimator) const { |
+ out_estimator->Clear(); |
+ |
+ // Iterate over movement samples in reverse time order and collect samples. |
+ float x[HISTORY_SIZE]; |
+ float y[HISTORY_SIZE]; |
+ float w[HISTORY_SIZE]; |
+ float time[HISTORY_SIZE]; |
+ uint32_t m = 0; |
+ uint32_t index = index_; |
+ const Movement& newestMovement = movements_[index_]; |
+ do { |
+ const Movement& movement = movements_[index]; |
+ if (!movement.id_bits.has_bit(id)) |
+ break; |
+ |
+ TimeDelta age = newestMovement.event_time - movement.event_time; |
+ if (age > HORIZON) |
+ break; |
+ |
+ const VelocityTracker::Position& position = movement.GetPosition(id); |
+ x[m] = position.x; |
+ y[m] = position.y; |
+ w[m] = ChooseWeight(index); |
+ time[m] = -static_cast<float>(age.InSecondsF()); |
+ index = (index == 0 ? HISTORY_SIZE : index) - 1; |
+ } while (++m < HISTORY_SIZE); |
+ |
+ if (m == 0) |
+ return false; // no data |
+ |
+ // Calculate a least squares polynomial fit. |
+ uint32_t degree = mDegree; |
+ if (degree > m - 1) |
+ degree = m - 1; |
+ |
+ if (degree >= 1) { |
+ float xdet, ydet; |
+ uint32_t n = degree + 1; |
+ if (SolveLeastSquares(time, x, w, m, n, out_estimator->xCoeff, &xdet) && |
+ SolveLeastSquares(time, y, w, m, n, out_estimator->yCoeff, &ydet)) { |
+ out_estimator->time = newestMovement.event_time; |
+ out_estimator->degree = degree; |
+ out_estimator->confidence = xdet * ydet; |
+ return true; |
+ } |
+ } |
+ |
+ // No velocity data available for this pointer, but we do have its current |
+ // position. |
+ out_estimator->xCoeff[0] = x[0]; |
+ out_estimator->yCoeff[0] = y[0]; |
+ out_estimator->time = newestMovement.event_time; |
+ out_estimator->degree = 0; |
+ out_estimator->confidence = 1; |
+ return true; |
+} |
+ |
+float LeastSquaresVelocityTrackerStrategy::ChooseWeight(uint32_t index) const { |
+ switch (mWeighting) { |
+ case WEIGHTING_DELTA: { |
+ // Weight points based on how much time elapsed between them and the next |
+ // point so that points that "cover" a shorter time span are weighed less. |
+ // delta 0ms: 0.5 |
+ // delta 10ms: 1.0 |
+ if (index == index_) { |
+ return 1.0f; |
+ } |
+ uint32_t nextIndex = (index + 1) % HISTORY_SIZE; |
+ float delta_millis = static_cast<float>( |
+ (movements_[nextIndex].event_time - movements_[index].event_time) |
+ .InMillisecondsF()); |
+ if (delta_millis < 0) |
+ return 0.5f; |
+ if (delta_millis < 10) |
+ return 0.5f + delta_millis * 0.05; |
+ |
+ return 1.0f; |
+ } |
+ |
+ case WEIGHTING_CENTRAL: { |
+ // Weight points based on their age, weighing very recent and very old |
+ // points less. |
+ // age 0ms: 0.5 |
+ // age 10ms: 1.0 |
+ // age 50ms: 1.0 |
+ // age 60ms: 0.5 |
+ float age_millis = static_cast<float>( |
+ (movements_[index_].event_time - movements_[index].event_time) |
+ .InMillisecondsF()); |
+ if (age_millis < 0) |
+ return 0.5f; |
+ if (age_millis < 10) |
+ return 0.5f + age_millis * 0.05; |
+ if (age_millis < 50) |
+ return 1.0f; |
+ if (age_millis < 60) |
+ return 0.5f + (60 - age_millis) * 0.05; |
+ |
+ return 0.5f; |
+ } |
+ |
+ case WEIGHTING_RECENT: { |
+ // Weight points based on their age, weighing older points less. |
+ // age 0ms: 1.0 |
+ // age 50ms: 1.0 |
+ // age 100ms: 0.5 |
+ float age_millis = static_cast<float>( |
+ (movements_[index_].event_time - movements_[index].event_time) |
+ .InMillisecondsF()); |
+ if (age_millis < 50) { |
+ return 1.0f; |
+ } |
+ if (age_millis < 100) { |
+ return 0.5f + (100 - age_millis) * 0.01f; |
+ } |
+ return 0.5f; |
+ } |
+ |
+ case WEIGHTING_NONE: |
+ default: |
+ return 1.0f; |
+ } |
+} |
+ |
+// --- IntegratingVelocityTrackerStrategy --- |
+ |
+IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy( |
+ uint32_t degree) |
+ : mDegree(degree) {} |
+ |
+IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {} |
+ |
+void IntegratingVelocityTrackerStrategy::Clear() { pointer_id_bits_.clear(); } |
+ |
+void IntegratingVelocityTrackerStrategy::ClearPointers(BitSet32 id_bits) { |
+ pointer_id_bits_.value &= ~id_bits.value; |
+} |
+ |
+void IntegratingVelocityTrackerStrategy::AddMovement( |
+ const TimeTicks& event_time, |
+ BitSet32 id_bits, |
+ const VelocityTracker::Position* positions) { |
+ uint32_t index = 0; |
+ for (BitSet32 iter_id_bits(id_bits); !iter_id_bits.is_empty();) { |
+ uint32_t id = iter_id_bits.clear_first_marked_bit(); |
+ State& state = mPointerState[id]; |
+ const VelocityTracker::Position& position = positions[index++]; |
+ if (pointer_id_bits_.has_bit(id)) |
+ UpdateState(state, event_time, position.x, position.y); |
+ else |
+ InitState(state, event_time, position.x, position.y); |
+ } |
+ |
+ pointer_id_bits_ = id_bits; |
+} |
+ |
+bool IntegratingVelocityTrackerStrategy::GetEstimator( |
+ uint32_t id, |
+ Estimator* out_estimator) const { |
+ out_estimator->Clear(); |
+ |
+ if (pointer_id_bits_.has_bit(id)) { |
+ const State& state = mPointerState[id]; |
+ PopulateEstimator(state, out_estimator); |
+ return true; |
+ } |
+ |
+ return false; |
+} |
+ |
+void IntegratingVelocityTrackerStrategy::InitState( |
+ State& state, |
+ const TimeTicks& event_time, |
+ float xpos, |
+ float ypos) const { |
+ state.updateTime = event_time; |
+ state.degree = 0; |
+ |
+ state.xpos = xpos; |
+ state.xvel = 0; |
+ state.xaccel = 0; |
+ state.ypos = ypos; |
+ state.yvel = 0; |
+ state.yaccel = 0; |
+} |
+ |
+void IntegratingVelocityTrackerStrategy::UpdateState( |
+ State& state, |
+ const TimeTicks& event_time, |
+ float xpos, |
+ float ypos) const { |
+ const base::TimeDelta MIN_TIME_DELTA = TimeDelta::FromMicroseconds(2); |
+ const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds |
+ |
+ if (event_time <= state.updateTime + MIN_TIME_DELTA) |
+ return; |
+ |
+ float dt = static_cast<float>((event_time - state.updateTime).InSecondsF()); |
+ state.updateTime = event_time; |
+ |
+ float xvel = (xpos - state.xpos) / dt; |
+ float yvel = (ypos - state.ypos) / dt; |
+ if (state.degree == 0) { |
+ state.xvel = xvel; |
+ state.yvel = yvel; |
+ state.degree = 1; |
+ } else { |
+ float alpha = dt / (FILTER_TIME_CONSTANT + dt); |
+ if (mDegree == 1) { |
+ state.xvel += (xvel - state.xvel) * alpha; |
+ state.yvel += (yvel - state.yvel) * alpha; |
+ } else { |
+ float xaccel = (xvel - state.xvel) / dt; |
+ float yaccel = (yvel - state.yvel) / dt; |
+ if (state.degree == 1) { |
+ state.xaccel = xaccel; |
+ state.yaccel = yaccel; |
+ state.degree = 2; |
+ } else { |
+ state.xaccel += (xaccel - state.xaccel) * alpha; |
+ state.yaccel += (yaccel - state.yaccel) * alpha; |
+ } |
+ state.xvel += (state.xaccel * dt) * alpha; |
+ state.yvel += (state.yaccel * dt) * alpha; |
+ } |
+ } |
+ state.xpos = xpos; |
+ state.ypos = ypos; |
+} |
+ |
+void IntegratingVelocityTrackerStrategy::PopulateEstimator( |
+ const State& state, |
+ Estimator* out_estimator) const { |
+ out_estimator->time = state.updateTime; |
+ out_estimator->confidence = 1.0f; |
+ out_estimator->degree = state.degree; |
+ out_estimator->xCoeff[0] = state.xpos; |
+ out_estimator->xCoeff[1] = state.xvel; |
+ out_estimator->xCoeff[2] = state.xaccel / 2; |
+ out_estimator->yCoeff[0] = state.ypos; |
+ out_estimator->yCoeff[1] = state.yvel; |
+ out_estimator->yCoeff[2] = state.yaccel / 2; |
+} |
+ |
+} // namespace content |