blob: d74c5209284e5254bc5892f0f62651c22bc65b65 [file] [log] [blame]
Jeff Brown5912f952013-07-01 19:10:31 -07001/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "VelocityTracker"
18//#define LOG_NDEBUG 0
19
20// Log debug messages about velocity tracking.
21#define DEBUG_VELOCITY 0
22
23// Log debug messages about the progress of the algorithm itself.
24#define DEBUG_STRATEGY 0
25
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -070026#include <array>
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070027#include <inttypes.h>
Jeff Brown5912f952013-07-01 19:10:31 -070028#include <limits.h>
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070029#include <math.h>
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -070030#include <optional>
Jeff Brown5912f952013-07-01 19:10:31 -070031
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070032#include <android-base/stringprintf.h>
Jeff Brown5912f952013-07-01 19:10:31 -070033#include <cutils/properties.h>
34#include <input/VelocityTracker.h>
35#include <utils/BitSet.h>
Jeff Brown5912f952013-07-01 19:10:31 -070036#include <utils/Timers.h>
37
38namespace android {
39
40// Nanoseconds per milliseconds.
41static const nsecs_t NANOS_PER_MS = 1000000;
42
43// Threshold for determining that a pointer has stopped moving.
44// Some input devices do not send ACTION_MOVE events in the case where a pointer has
45// stopped. We need to detect this case so that we can accurately predict the
46// velocity after the pointer starts moving again.
47static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS;
48
49
50static float vectorDot(const float* a, const float* b, uint32_t m) {
51 float r = 0;
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070052 for (size_t i = 0; i < m; i++) {
Jeff Brown5912f952013-07-01 19:10:31 -070053 r += *(a++) * *(b++);
54 }
55 return r;
56}
57
58static float vectorNorm(const float* a, uint32_t m) {
59 float r = 0;
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070060 for (size_t i = 0; i < m; i++) {
Jeff Brown5912f952013-07-01 19:10:31 -070061 float t = *(a++);
62 r += t * t;
63 }
64 return sqrtf(r);
65}
66
67#if DEBUG_STRATEGY || DEBUG_VELOCITY
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070068static std::string vectorToString(const float* a, uint32_t m) {
69 std::string str;
70 str += "[";
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070071 for (size_t i = 0; i < m; i++) {
72 if (i) {
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070073 str += ",";
Jeff Brown5912f952013-07-01 19:10:31 -070074 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070075 str += android::base::StringPrintf(" %f", *(a++));
Jeff Brown5912f952013-07-01 19:10:31 -070076 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070077 str += " ]";
Jeff Brown5912f952013-07-01 19:10:31 -070078 return str;
79}
Siarhei Vishniakoud4b607e2017-06-13 12:21:59 +010080#endif
Jeff Brown5912f952013-07-01 19:10:31 -070081
Siarhei Vishniakoud4b607e2017-06-13 12:21:59 +010082#if DEBUG_STRATEGY
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070083static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) {
84 std::string str;
85 str = "[";
Jeff Brown5912f952013-07-01 19:10:31 -070086 for (size_t i = 0; i < m; i++) {
87 if (i) {
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070088 str += ",";
Jeff Brown5912f952013-07-01 19:10:31 -070089 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070090 str += " [";
Jeff Brown5912f952013-07-01 19:10:31 -070091 for (size_t j = 0; j < n; j++) {
92 if (j) {
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070093 str += ",";
Jeff Brown5912f952013-07-01 19:10:31 -070094 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070095 str += android::base::StringPrintf(" %f", a[rowMajor ? i * n + j : j * m + i]);
Jeff Brown5912f952013-07-01 19:10:31 -070096 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070097 str += " ]";
Jeff Brown5912f952013-07-01 19:10:31 -070098 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070099 str += " ]";
Jeff Brown5912f952013-07-01 19:10:31 -0700100 return str;
101}
102#endif
103
104
105// --- VelocityTracker ---
106
Chris Yef8591482020-04-17 11:49:17 -0700107VelocityTracker::VelocityTracker(const Strategy strategy)
108 : mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) {
Jeff Brown5912f952013-07-01 19:10:31 -0700109 // Configure the strategy.
110 if (!configureStrategy(strategy)) {
Chris Yef8591482020-04-17 11:49:17 -0700111 ALOGE("Unrecognized velocity tracker strategy %" PRId32 ".", strategy);
112 if (!configureStrategy(VelocityTracker::DEFAULT_STRATEGY)) {
113 LOG_ALWAYS_FATAL("Could not create the default velocity tracker strategy '%" PRId32
114 "'!",
115 strategy);
Jeff Brown5912f952013-07-01 19:10:31 -0700116 }
117 }
118}
119
120VelocityTracker::~VelocityTracker() {
Jeff Brown5912f952013-07-01 19:10:31 -0700121}
122
Chris Yef8591482020-04-17 11:49:17 -0700123bool VelocityTracker::configureStrategy(Strategy strategy) {
124 if (strategy == VelocityTracker::Strategy::DEFAULT) {
125 mStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY);
126 } else {
127 mStrategy = createStrategy(strategy);
128 }
Yi Kong5bed83b2018-07-17 12:53:47 -0700129 return mStrategy != nullptr;
Jeff Brown5912f952013-07-01 19:10:31 -0700130}
131
Chris Yef8591482020-04-17 11:49:17 -0700132std::unique_ptr<VelocityTrackerStrategy> VelocityTracker::createStrategy(
133 VelocityTracker::Strategy strategy) {
134 switch (strategy) {
135 case VelocityTracker::Strategy::IMPULSE:
136 return std::make_unique<ImpulseVelocityTrackerStrategy>();
137
138 case VelocityTracker::Strategy::LSQ1:
139 return std::make_unique<LeastSquaresVelocityTrackerStrategy>(1);
140
141 case VelocityTracker::Strategy::LSQ2:
142 return std::make_unique<LeastSquaresVelocityTrackerStrategy>(2);
143
144 case VelocityTracker::Strategy::LSQ3:
145 return std::make_unique<LeastSquaresVelocityTrackerStrategy>(3);
146
147 case VelocityTracker::Strategy::WLSQ2_DELTA:
148 return std::make_unique<
149 LeastSquaresVelocityTrackerStrategy>(2,
150 LeastSquaresVelocityTrackerStrategy::
151 WEIGHTING_DELTA);
152 case VelocityTracker::Strategy::WLSQ2_CENTRAL:
153 return std::make_unique<
154 LeastSquaresVelocityTrackerStrategy>(2,
155 LeastSquaresVelocityTrackerStrategy::
156 WEIGHTING_CENTRAL);
157 case VelocityTracker::Strategy::WLSQ2_RECENT:
158 return std::make_unique<
159 LeastSquaresVelocityTrackerStrategy>(2,
160 LeastSquaresVelocityTrackerStrategy::
161 WEIGHTING_RECENT);
162
163 case VelocityTracker::Strategy::INT1:
164 return std::make_unique<IntegratingVelocityTrackerStrategy>(1);
165
166 case VelocityTracker::Strategy::INT2:
167 return std::make_unique<IntegratingVelocityTrackerStrategy>(2);
168
169 case VelocityTracker::Strategy::LEGACY:
170 return std::make_unique<LegacyVelocityTrackerStrategy>();
171
172 default:
173 break;
Jeff Brown5912f952013-07-01 19:10:31 -0700174 }
Yi Kong5bed83b2018-07-17 12:53:47 -0700175 return nullptr;
Jeff Brown5912f952013-07-01 19:10:31 -0700176}
177
178void VelocityTracker::clear() {
179 mCurrentPointerIdBits.clear();
180 mActivePointerId = -1;
181
182 mStrategy->clear();
183}
184
185void VelocityTracker::clearPointers(BitSet32 idBits) {
186 BitSet32 remainingIdBits(mCurrentPointerIdBits.value & ~idBits.value);
187 mCurrentPointerIdBits = remainingIdBits;
188
189 if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) {
190 mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1;
191 }
192
193 mStrategy->clearPointers(idBits);
194}
195
Siarhei Vishniakouae0f9902020-09-14 19:23:31 -0500196void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits,
197 const std::vector<VelocityTracker::Position>& positions) {
198 LOG_ALWAYS_FATAL_IF(idBits.count() != positions.size(),
199 "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu",
200 idBits.count(), positions.size());
Jeff Brown5912f952013-07-01 19:10:31 -0700201 while (idBits.count() > MAX_POINTERS) {
202 idBits.clearLastMarkedBit();
203 }
204
205 if ((mCurrentPointerIdBits.value & idBits.value)
206 && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) {
207#if DEBUG_VELOCITY
208 ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.",
209 (eventTime - mLastEventTime) * 0.000001f);
210#endif
211 // We have not received any movements for too long. Assume that all pointers
212 // have stopped.
213 mStrategy->clear();
214 }
215 mLastEventTime = eventTime;
216
217 mCurrentPointerIdBits = idBits;
218 if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) {
219 mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit();
220 }
221
222 mStrategy->addMovement(eventTime, idBits, positions);
223
224#if DEBUG_VELOCITY
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -0700225 ALOGD("VelocityTracker: addMovement eventTime=%" PRId64 ", idBits=0x%08x, activePointerId=%d",
Jeff Brown5912f952013-07-01 19:10:31 -0700226 eventTime, idBits.value, mActivePointerId);
227 for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) {
228 uint32_t id = iterBits.firstMarkedBit();
229 uint32_t index = idBits.getIndexOfBit(id);
230 iterBits.clearBit(id);
231 Estimator estimator;
232 getEstimator(id, &estimator);
233 ALOGD(" %d: position (%0.3f, %0.3f), "
234 "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)",
235 id, positions[index].x, positions[index].y,
236 int(estimator.degree),
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700237 vectorToString(estimator.xCoeff, estimator.degree + 1).c_str(),
238 vectorToString(estimator.yCoeff, estimator.degree + 1).c_str(),
Jeff Brown5912f952013-07-01 19:10:31 -0700239 estimator.confidence);
240 }
241#endif
242}
243
244void VelocityTracker::addMovement(const MotionEvent* event) {
245 int32_t actionMasked = event->getActionMasked();
246
247 switch (actionMasked) {
248 case AMOTION_EVENT_ACTION_DOWN:
249 case AMOTION_EVENT_ACTION_HOVER_ENTER:
250 // Clear all pointers on down before adding the new movement.
251 clear();
252 break;
253 case AMOTION_EVENT_ACTION_POINTER_DOWN: {
254 // Start a new movement trace for a pointer that just went down.
255 // We do this on down instead of on up because the client may want to query the
256 // final velocity for a pointer that just went up.
257 BitSet32 downIdBits;
258 downIdBits.markBit(event->getPointerId(event->getActionIndex()));
259 clearPointers(downIdBits);
260 break;
261 }
262 case AMOTION_EVENT_ACTION_MOVE:
263 case AMOTION_EVENT_ACTION_HOVER_MOVE:
264 break;
265 default:
266 // Ignore all other actions because they do not convey any new information about
267 // pointer movement. We also want to preserve the last known velocity of the pointers.
268 // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
269 // of the pointers that went up. ACTION_POINTER_UP does include the new position of
270 // pointers that remained down but we will also receive an ACTION_MOVE with this
271 // information if any of them actually moved. Since we don't know how many pointers
272 // will be going up at once it makes sense to just wait for the following ACTION_MOVE
273 // before adding the movement.
274 return;
275 }
276
277 size_t pointerCount = event->getPointerCount();
278 if (pointerCount > MAX_POINTERS) {
279 pointerCount = MAX_POINTERS;
280 }
281
282 BitSet32 idBits;
283 for (size_t i = 0; i < pointerCount; i++) {
284 idBits.markBit(event->getPointerId(i));
285 }
286
287 uint32_t pointerIndex[MAX_POINTERS];
288 for (size_t i = 0; i < pointerCount; i++) {
289 pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i));
290 }
291
292 nsecs_t eventTime;
Siarhei Vishniakouae0f9902020-09-14 19:23:31 -0500293 std::vector<Position> positions;
294 positions.resize(pointerCount);
Jeff Brown5912f952013-07-01 19:10:31 -0700295
296 size_t historySize = event->getHistorySize();
297 for (size_t h = 0; h < historySize; h++) {
298 eventTime = event->getHistoricalEventTime(h);
299 for (size_t i = 0; i < pointerCount; i++) {
300 uint32_t index = pointerIndex[i];
Siarhei Vishniakou4c3137a2018-11-13 13:33:52 -0800301 positions[index].x = event->getHistoricalX(i, h);
302 positions[index].y = event->getHistoricalY(i, h);
Jeff Brown5912f952013-07-01 19:10:31 -0700303 }
304 addMovement(eventTime, idBits, positions);
305 }
306
307 eventTime = event->getEventTime();
308 for (size_t i = 0; i < pointerCount; i++) {
309 uint32_t index = pointerIndex[i];
Siarhei Vishniakou4c3137a2018-11-13 13:33:52 -0800310 positions[index].x = event->getX(i);
311 positions[index].y = event->getY(i);
Jeff Brown5912f952013-07-01 19:10:31 -0700312 }
313 addMovement(eventTime, idBits, positions);
314}
315
316bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const {
317 Estimator estimator;
318 if (getEstimator(id, &estimator) && estimator.degree >= 1) {
319 *outVx = estimator.xCoeff[1];
320 *outVy = estimator.yCoeff[1];
321 return true;
322 }
323 *outVx = 0;
324 *outVy = 0;
325 return false;
326}
327
328bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const {
329 return mStrategy->getEstimator(id, outEstimator);
330}
331
332
333// --- LeastSquaresVelocityTrackerStrategy ---
334
Jeff Brown5912f952013-07-01 19:10:31 -0700335LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(
336 uint32_t degree, Weighting weighting) :
337 mDegree(degree), mWeighting(weighting) {
338 clear();
339}
340
341LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {
342}
343
344void LeastSquaresVelocityTrackerStrategy::clear() {
345 mIndex = 0;
346 mMovements[0].idBits.clear();
347}
348
349void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
350 BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
351 mMovements[mIndex].idBits = remainingIdBits;
352}
353
Siarhei Vishniakouae0f9902020-09-14 19:23:31 -0500354void LeastSquaresVelocityTrackerStrategy::addMovement(
355 nsecs_t eventTime, BitSet32 idBits,
356 const std::vector<VelocityTracker::Position>& positions) {
Siarhei Vishniakou346ac6a2019-04-10 09:58:05 -0700357 if (mMovements[mIndex].eventTime != eventTime) {
358 // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
359 // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
360 // the new pointer. If the eventtimes for both events are identical, just update the data
361 // for this time.
362 // We only compare against the last value, as it is likely that addMovement is called
363 // in chronological order as events occur.
364 mIndex++;
365 }
366 if (mIndex == HISTORY_SIZE) {
Jeff Brown5912f952013-07-01 19:10:31 -0700367 mIndex = 0;
368 }
369
370 Movement& movement = mMovements[mIndex];
371 movement.eventTime = eventTime;
372 movement.idBits = idBits;
373 uint32_t count = idBits.count();
374 for (uint32_t i = 0; i < count; i++) {
375 movement.positions[i] = positions[i];
376 }
377}
378
379/**
380 * Solves a linear least squares problem to obtain a N degree polynomial that fits
381 * the specified input data as nearly as possible.
382 *
383 * Returns true if a solution is found, false otherwise.
384 *
385 * The input consists of two vectors of data points X and Y with indices 0..m-1
386 * along with a weight vector W of the same size.
387 *
388 * The output is a vector B with indices 0..n that describes a polynomial
389 * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] X[i]
390 * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is minimized.
391 *
392 * Accordingly, the weight vector W should be initialized by the caller with the
393 * reciprocal square root of the variance of the error in each input data point.
394 * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / stddev(Y[i]).
395 * The weights express the relative importance of each data point. If the weights are
396 * all 1, then the data points are considered to be of equal importance when fitting
397 * the polynomial. It is a good idea to choose weights that diminish the importance
398 * of data points that may have higher than usual error margins.
399 *
400 * Errors among data points are assumed to be independent. W is represented here
401 * as a vector although in the literature it is typically taken to be a diagonal matrix.
402 *
403 * That is to say, the function that generated the input data can be approximated
404 * by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
405 *
406 * The coefficient of determination (R^2) is also returned to describe the goodness
407 * of fit of the model for the given data. It is a value between 0 and 1, where 1
408 * indicates perfect correspondence.
409 *
410 * This function first expands the X vector to a m by n matrix A such that
411 * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then
412 * multiplies it by w[i]./
413 *
414 * Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q
415 * and an m by n upper triangular matrix R. Because R is upper triangular (lower
416 * part is all zeroes), we can simplify the decomposition into an m by n matrix
417 * Q1 and a n by n matrix R1 such that A = Q1 R1.
418 *
419 * Finally we solve the system of linear equations given by R1 B = (Qtranspose W Y)
420 * to find B.
421 *
422 * For efficiency, we lay out A and Q column-wise in memory because we frequently
423 * operate on the column vectors. Conversely, we lay out R row-wise.
424 *
425 * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
426 * http://en.wikipedia.org/wiki/Gram-Schmidt
427 */
428static bool solveLeastSquares(const float* x, const float* y,
429 const float* w, uint32_t m, uint32_t n, float* outB, float* outDet) {
430#if DEBUG_STRATEGY
431 ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700432 vectorToString(x, m).c_str(), vectorToString(y, m).c_str(),
433 vectorToString(w, m).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700434#endif
435
436 // Expand the X vector to a matrix A, pre-multiplied by the weights.
437 float a[n][m]; // column-major order
438 for (uint32_t h = 0; h < m; h++) {
439 a[0][h] = w[h];
440 for (uint32_t i = 1; i < n; i++) {
441 a[i][h] = a[i - 1][h] * x[h];
442 }
443 }
444#if DEBUG_STRATEGY
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700445 ALOGD(" - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700446#endif
447
448 // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
449 float q[n][m]; // orthonormal basis, column-major order
450 float r[n][n]; // upper triangular matrix, row-major order
451 for (uint32_t j = 0; j < n; j++) {
452 for (uint32_t h = 0; h < m; h++) {
453 q[j][h] = a[j][h];
454 }
455 for (uint32_t i = 0; i < j; i++) {
456 float dot = vectorDot(&q[j][0], &q[i][0], m);
457 for (uint32_t h = 0; h < m; h++) {
458 q[j][h] -= dot * q[i][h];
459 }
460 }
461
462 float norm = vectorNorm(&q[j][0], m);
463 if (norm < 0.000001f) {
464 // vectors are linearly dependent or zero so no solution
465#if DEBUG_STRATEGY
466 ALOGD(" - no solution, norm=%f", norm);
467#endif
468 return false;
469 }
470
471 float invNorm = 1.0f / norm;
472 for (uint32_t h = 0; h < m; h++) {
473 q[j][h] *= invNorm;
474 }
475 for (uint32_t i = 0; i < n; i++) {
476 r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m);
477 }
478 }
479#if DEBUG_STRATEGY
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700480 ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).c_str());
481 ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700482
483 // calculate QR, if we factored A correctly then QR should equal A
484 float qr[n][m];
485 for (uint32_t h = 0; h < m; h++) {
486 for (uint32_t i = 0; i < n; i++) {
487 qr[i][h] = 0;
488 for (uint32_t j = 0; j < n; j++) {
489 qr[i][h] += q[j][h] * r[j][i];
490 }
491 }
492 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700493 ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700494#endif
495
496 // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
497 // We just work from bottom-right to top-left calculating B's coefficients.
498 float wy[m];
499 for (uint32_t h = 0; h < m; h++) {
500 wy[h] = y[h] * w[h];
501 }
Dan Austin389ddba2015-09-22 14:32:03 -0700502 for (uint32_t i = n; i != 0; ) {
503 i--;
Jeff Brown5912f952013-07-01 19:10:31 -0700504 outB[i] = vectorDot(&q[i][0], wy, m);
505 for (uint32_t j = n - 1; j > i; j--) {
506 outB[i] -= r[i][j] * outB[j];
507 }
508 outB[i] /= r[i][i];
509 }
510#if DEBUG_STRATEGY
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700511 ALOGD(" - b=%s", vectorToString(outB, n).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700512#endif
513
514 // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
515 // SSerr is the residual sum of squares (variance of the error),
516 // and SStot is the total sum of squares (variance of the data) where each
517 // has been weighted.
518 float ymean = 0;
519 for (uint32_t h = 0; h < m; h++) {
520 ymean += y[h];
521 }
522 ymean /= m;
523
524 float sserr = 0;
525 float sstot = 0;
526 for (uint32_t h = 0; h < m; h++) {
527 float err = y[h] - outB[0];
528 float term = 1;
529 for (uint32_t i = 1; i < n; i++) {
530 term *= x[h];
531 err -= term * outB[i];
532 }
533 sserr += w[h] * w[h] * err * err;
534 float var = y[h] - ymean;
535 sstot += w[h] * w[h] * var * var;
536 }
537 *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
538#if DEBUG_STRATEGY
539 ALOGD(" - sserr=%f", sserr);
540 ALOGD(" - sstot=%f", sstot);
541 ALOGD(" - det=%f", *outDet);
542#endif
543 return true;
544}
545
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100546/*
547 * Optimized unweighted second-order least squares fit. About 2x speed improvement compared to
548 * the default implementation
549 */
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700550static std::optional<std::array<float, 3>> solveUnweightedLeastSquaresDeg2(
551 const float* x, const float* y, size_t count) {
552 // Solving y = a*x^2 + b*x + c
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100553 float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
554
555 for (size_t i = 0; i < count; i++) {
556 float xi = x[i];
557 float yi = y[i];
558 float xi2 = xi*xi;
559 float xi3 = xi2*xi;
560 float xi4 = xi3*xi;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100561 float xiyi = xi*yi;
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700562 float xi2yi = xi2*yi;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100563
564 sxi += xi;
565 sxi2 += xi2;
566 sxiyi += xiyi;
567 sxi2yi += xi2yi;
568 syi += yi;
569 sxi3 += xi3;
570 sxi4 += xi4;
571 }
572
573 float Sxx = sxi2 - sxi*sxi / count;
574 float Sxy = sxiyi - sxi*syi / count;
575 float Sxx2 = sxi3 - sxi*sxi2 / count;
576 float Sx2y = sxi2yi - sxi2*syi / count;
577 float Sx2x2 = sxi4 - sxi2*sxi2 / count;
578
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100579 float denominator = Sxx*Sx2x2 - Sxx2*Sxx2;
580 if (denominator == 0) {
581 ALOGW("division by 0 when computing velocity, Sxx=%f, Sx2x2=%f, Sxx2=%f", Sxx, Sx2x2, Sxx2);
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700582 return std::nullopt;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100583 }
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700584 // Compute a
585 float numerator = Sx2y*Sxx - Sxy*Sxx2;
586 float a = numerator / denominator;
587
588 // Compute b
589 numerator = Sxy*Sx2x2 - Sx2y*Sxx2;
590 float b = numerator / denominator;
591
592 // Compute c
593 float c = syi/count - b * sxi/count - a * sxi2/count;
594
595 return std::make_optional(std::array<float, 3>({c, b, a}));
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100596}
597
Jeff Brown5912f952013-07-01 19:10:31 -0700598bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
599 VelocityTracker::Estimator* outEstimator) const {
600 outEstimator->clear();
601
602 // Iterate over movement samples in reverse time order and collect samples.
603 float x[HISTORY_SIZE];
604 float y[HISTORY_SIZE];
605 float w[HISTORY_SIZE];
606 float time[HISTORY_SIZE];
607 uint32_t m = 0;
608 uint32_t index = mIndex;
609 const Movement& newestMovement = mMovements[mIndex];
610 do {
611 const Movement& movement = mMovements[index];
612 if (!movement.idBits.hasBit(id)) {
613 break;
614 }
615
616 nsecs_t age = newestMovement.eventTime - movement.eventTime;
617 if (age > HORIZON) {
618 break;
619 }
620
621 const VelocityTracker::Position& position = movement.getPosition(id);
622 x[m] = position.x;
623 y[m] = position.y;
624 w[m] = chooseWeight(index);
625 time[m] = -age * 0.000000001f;
626 index = (index == 0 ? HISTORY_SIZE : index) - 1;
627 } while (++m < HISTORY_SIZE);
628
629 if (m == 0) {
630 return false; // no data
631 }
632
633 // Calculate a least squares polynomial fit.
634 uint32_t degree = mDegree;
635 if (degree > m - 1) {
636 degree = m - 1;
637 }
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700638
639 if (degree == 2 && mWeighting == WEIGHTING_NONE) {
640 // Optimize unweighted, quadratic polynomial fit
641 std::optional<std::array<float, 3>> xCoeff = solveUnweightedLeastSquaresDeg2(time, x, m);
642 std::optional<std::array<float, 3>> yCoeff = solveUnweightedLeastSquaresDeg2(time, y, m);
643 if (xCoeff && yCoeff) {
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100644 outEstimator->time = newestMovement.eventTime;
645 outEstimator->degree = 2;
646 outEstimator->confidence = 1;
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700647 for (size_t i = 0; i <= outEstimator->degree; i++) {
648 outEstimator->xCoeff[i] = (*xCoeff)[i];
649 outEstimator->yCoeff[i] = (*yCoeff)[i];
650 }
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100651 return true;
652 }
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700653 } else if (degree >= 1) {
654 // General case for an Nth degree polynomial fit
Jeff Brown5912f952013-07-01 19:10:31 -0700655 float xdet, ydet;
656 uint32_t n = degree + 1;
657 if (solveLeastSquares(time, x, w, m, n, outEstimator->xCoeff, &xdet)
658 && solveLeastSquares(time, y, w, m, n, outEstimator->yCoeff, &ydet)) {
659 outEstimator->time = newestMovement.eventTime;
660 outEstimator->degree = degree;
661 outEstimator->confidence = xdet * ydet;
662#if DEBUG_STRATEGY
663 ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f",
664 int(outEstimator->degree),
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700665 vectorToString(outEstimator->xCoeff, n).c_str(),
666 vectorToString(outEstimator->yCoeff, n).c_str(),
Jeff Brown5912f952013-07-01 19:10:31 -0700667 outEstimator->confidence);
668#endif
669 return true;
670 }
671 }
672
673 // No velocity data available for this pointer, but we do have its current position.
674 outEstimator->xCoeff[0] = x[0];
675 outEstimator->yCoeff[0] = y[0];
676 outEstimator->time = newestMovement.eventTime;
677 outEstimator->degree = 0;
678 outEstimator->confidence = 1;
679 return true;
680}
681
682float LeastSquaresVelocityTrackerStrategy::chooseWeight(uint32_t index) const {
683 switch (mWeighting) {
684 case WEIGHTING_DELTA: {
685 // Weight points based on how much time elapsed between them and the next
686 // point so that points that "cover" a shorter time span are weighed less.
687 // delta 0ms: 0.5
688 // delta 10ms: 1.0
689 if (index == mIndex) {
690 return 1.0f;
691 }
692 uint32_t nextIndex = (index + 1) % HISTORY_SIZE;
693 float deltaMillis = (mMovements[nextIndex].eventTime- mMovements[index].eventTime)
694 * 0.000001f;
695 if (deltaMillis < 0) {
696 return 0.5f;
697 }
698 if (deltaMillis < 10) {
699 return 0.5f + deltaMillis * 0.05;
700 }
701 return 1.0f;
702 }
703
704 case WEIGHTING_CENTRAL: {
705 // Weight points based on their age, weighing very recent and very old points less.
706 // age 0ms: 0.5
707 // age 10ms: 1.0
708 // age 50ms: 1.0
709 // age 60ms: 0.5
710 float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
711 * 0.000001f;
712 if (ageMillis < 0) {
713 return 0.5f;
714 }
715 if (ageMillis < 10) {
716 return 0.5f + ageMillis * 0.05;
717 }
718 if (ageMillis < 50) {
719 return 1.0f;
720 }
721 if (ageMillis < 60) {
722 return 0.5f + (60 - ageMillis) * 0.05;
723 }
724 return 0.5f;
725 }
726
727 case WEIGHTING_RECENT: {
728 // Weight points based on their age, weighing older points less.
729 // age 0ms: 1.0
730 // age 50ms: 1.0
731 // age 100ms: 0.5
732 float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
733 * 0.000001f;
734 if (ageMillis < 50) {
735 return 1.0f;
736 }
737 if (ageMillis < 100) {
738 return 0.5f + (100 - ageMillis) * 0.01f;
739 }
740 return 0.5f;
741 }
742
743 case WEIGHTING_NONE:
744 default:
745 return 1.0f;
746 }
747}
748
749
750// --- IntegratingVelocityTrackerStrategy ---
751
752IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) :
753 mDegree(degree) {
754}
755
756IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {
757}
758
759void IntegratingVelocityTrackerStrategy::clear() {
760 mPointerIdBits.clear();
761}
762
763void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
764 mPointerIdBits.value &= ~idBits.value;
765}
766
Siarhei Vishniakouae0f9902020-09-14 19:23:31 -0500767void IntegratingVelocityTrackerStrategy::addMovement(
768 nsecs_t eventTime, BitSet32 idBits,
769 const std::vector<VelocityTracker::Position>& positions) {
Jeff Brown5912f952013-07-01 19:10:31 -0700770 uint32_t index = 0;
771 for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) {
772 uint32_t id = iterIdBits.clearFirstMarkedBit();
773 State& state = mPointerState[id];
774 const VelocityTracker::Position& position = positions[index++];
775 if (mPointerIdBits.hasBit(id)) {
776 updateState(state, eventTime, position.x, position.y);
777 } else {
778 initState(state, eventTime, position.x, position.y);
779 }
780 }
781
782 mPointerIdBits = idBits;
783}
784
785bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id,
786 VelocityTracker::Estimator* outEstimator) const {
787 outEstimator->clear();
788
789 if (mPointerIdBits.hasBit(id)) {
790 const State& state = mPointerState[id];
791 populateEstimator(state, outEstimator);
792 return true;
793 }
794
795 return false;
796}
797
798void IntegratingVelocityTrackerStrategy::initState(State& state,
799 nsecs_t eventTime, float xpos, float ypos) const {
800 state.updateTime = eventTime;
801 state.degree = 0;
802
803 state.xpos = xpos;
804 state.xvel = 0;
805 state.xaccel = 0;
806 state.ypos = ypos;
807 state.yvel = 0;
808 state.yaccel = 0;
809}
810
811void IntegratingVelocityTrackerStrategy::updateState(State& state,
812 nsecs_t eventTime, float xpos, float ypos) const {
813 const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS;
814 const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds
815
816 if (eventTime <= state.updateTime + MIN_TIME_DELTA) {
817 return;
818 }
819
820 float dt = (eventTime - state.updateTime) * 0.000000001f;
821 state.updateTime = eventTime;
822
823 float xvel = (xpos - state.xpos) / dt;
824 float yvel = (ypos - state.ypos) / dt;
825 if (state.degree == 0) {
826 state.xvel = xvel;
827 state.yvel = yvel;
828 state.degree = 1;
829 } else {
830 float alpha = dt / (FILTER_TIME_CONSTANT + dt);
831 if (mDegree == 1) {
832 state.xvel += (xvel - state.xvel) * alpha;
833 state.yvel += (yvel - state.yvel) * alpha;
834 } else {
835 float xaccel = (xvel - state.xvel) / dt;
836 float yaccel = (yvel - state.yvel) / dt;
837 if (state.degree == 1) {
838 state.xaccel = xaccel;
839 state.yaccel = yaccel;
840 state.degree = 2;
841 } else {
842 state.xaccel += (xaccel - state.xaccel) * alpha;
843 state.yaccel += (yaccel - state.yaccel) * alpha;
844 }
845 state.xvel += (state.xaccel * dt) * alpha;
846 state.yvel += (state.yaccel * dt) * alpha;
847 }
848 }
849 state.xpos = xpos;
850 state.ypos = ypos;
851}
852
853void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state,
854 VelocityTracker::Estimator* outEstimator) const {
855 outEstimator->time = state.updateTime;
856 outEstimator->confidence = 1.0f;
857 outEstimator->degree = state.degree;
858 outEstimator->xCoeff[0] = state.xpos;
859 outEstimator->xCoeff[1] = state.xvel;
860 outEstimator->xCoeff[2] = state.xaccel / 2;
861 outEstimator->yCoeff[0] = state.ypos;
862 outEstimator->yCoeff[1] = state.yvel;
863 outEstimator->yCoeff[2] = state.yaccel / 2;
864}
865
866
867// --- LegacyVelocityTrackerStrategy ---
868
Jeff Brown5912f952013-07-01 19:10:31 -0700869LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {
870 clear();
871}
872
873LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
874}
875
876void LegacyVelocityTrackerStrategy::clear() {
877 mIndex = 0;
878 mMovements[0].idBits.clear();
879}
880
881void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
882 BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
883 mMovements[mIndex].idBits = remainingIdBits;
884}
885
Siarhei Vishniakouae0f9902020-09-14 19:23:31 -0500886void LegacyVelocityTrackerStrategy::addMovement(
887 nsecs_t eventTime, BitSet32 idBits,
888 const std::vector<VelocityTracker::Position>& positions) {
Jeff Brown5912f952013-07-01 19:10:31 -0700889 if (++mIndex == HISTORY_SIZE) {
890 mIndex = 0;
891 }
892
893 Movement& movement = mMovements[mIndex];
894 movement.eventTime = eventTime;
895 movement.idBits = idBits;
896 uint32_t count = idBits.count();
897 for (uint32_t i = 0; i < count; i++) {
898 movement.positions[i] = positions[i];
899 }
900}
901
902bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id,
903 VelocityTracker::Estimator* outEstimator) const {
904 outEstimator->clear();
905
906 const Movement& newestMovement = mMovements[mIndex];
907 if (!newestMovement.idBits.hasBit(id)) {
908 return false; // no data
909 }
910
911 // Find the oldest sample that contains the pointer and that is not older than HORIZON.
912 nsecs_t minTime = newestMovement.eventTime - HORIZON;
913 uint32_t oldestIndex = mIndex;
914 uint32_t numTouches = 1;
915 do {
916 uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1;
917 const Movement& nextOldestMovement = mMovements[nextOldestIndex];
918 if (!nextOldestMovement.idBits.hasBit(id)
919 || nextOldestMovement.eventTime < minTime) {
920 break;
921 }
922 oldestIndex = nextOldestIndex;
923 } while (++numTouches < HISTORY_SIZE);
924
925 // Calculate an exponentially weighted moving average of the velocity estimate
926 // at different points in time measured relative to the oldest sample.
927 // This is essentially an IIR filter. Newer samples are weighted more heavily
928 // than older samples. Samples at equal time points are weighted more or less
929 // equally.
930 //
931 // One tricky problem is that the sample data may be poorly conditioned.
932 // Sometimes samples arrive very close together in time which can cause us to
933 // overestimate the velocity at that time point. Most samples might be measured
934 // 16ms apart but some consecutive samples could be only 0.5sm apart because
935 // the hardware or driver reports them irregularly or in bursts.
936 float accumVx = 0;
937 float accumVy = 0;
938 uint32_t index = oldestIndex;
939 uint32_t samplesUsed = 0;
940 const Movement& oldestMovement = mMovements[oldestIndex];
941 const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id);
942 nsecs_t lastDuration = 0;
943
944 while (numTouches-- > 1) {
945 if (++index == HISTORY_SIZE) {
946 index = 0;
947 }
948 const Movement& movement = mMovements[index];
949 nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
950
951 // If the duration between samples is small, we may significantly overestimate
952 // the velocity. Consequently, we impose a minimum duration constraint on the
953 // samples that we include in the calculation.
954 if (duration >= MIN_DURATION) {
955 const VelocityTracker::Position& position = movement.getPosition(id);
956 float scale = 1000000000.0f / duration; // one over time delta in seconds
957 float vx = (position.x - oldestPosition.x) * scale;
958 float vy = (position.y - oldestPosition.y) * scale;
959 accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration);
960 accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration);
961 lastDuration = duration;
962 samplesUsed += 1;
963 }
964 }
965
966 // Report velocity.
967 const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id);
968 outEstimator->time = newestMovement.eventTime;
969 outEstimator->confidence = 1;
970 outEstimator->xCoeff[0] = newestPosition.x;
971 outEstimator->yCoeff[0] = newestPosition.y;
972 if (samplesUsed) {
973 outEstimator->xCoeff[1] = accumVx;
974 outEstimator->yCoeff[1] = accumVy;
975 outEstimator->degree = 1;
976 } else {
977 outEstimator->degree = 0;
978 }
979 return true;
980}
981
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100982// --- ImpulseVelocityTrackerStrategy ---
983
984ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy() {
985 clear();
986}
987
988ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() {
989}
990
991void ImpulseVelocityTrackerStrategy::clear() {
992 mIndex = 0;
993 mMovements[0].idBits.clear();
994}
995
996void ImpulseVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
997 BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
998 mMovements[mIndex].idBits = remainingIdBits;
999}
1000
Siarhei Vishniakouae0f9902020-09-14 19:23:31 -05001001void ImpulseVelocityTrackerStrategy::addMovement(
1002 nsecs_t eventTime, BitSet32 idBits,
1003 const std::vector<VelocityTracker::Position>& positions) {
Siarhei Vishniakou346ac6a2019-04-10 09:58:05 -07001004 if (mMovements[mIndex].eventTime != eventTime) {
1005 // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
1006 // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
1007 // the new pointer. If the eventtimes for both events are identical, just update the data
1008 // for this time.
1009 // We only compare against the last value, as it is likely that addMovement is called
1010 // in chronological order as events occur.
1011 mIndex++;
1012 }
1013 if (mIndex == HISTORY_SIZE) {
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001014 mIndex = 0;
1015 }
1016
1017 Movement& movement = mMovements[mIndex];
1018 movement.eventTime = eventTime;
1019 movement.idBits = idBits;
1020 uint32_t count = idBits.count();
1021 for (uint32_t i = 0; i < count; i++) {
1022 movement.positions[i] = positions[i];
1023 }
1024}
1025
1026/**
1027 * Calculate the total impulse provided to the screen and the resulting velocity.
1028 *
1029 * The touchscreen is modeled as a physical object.
1030 * Initial condition is discussed below, but for now suppose that v(t=0) = 0
1031 *
1032 * The kinetic energy of the object at the release is E=0.5*m*v^2
1033 * Then vfinal = sqrt(2E/m). The goal is to calculate E.
1034 *
1035 * The kinetic energy at the release is equal to the total work done on the object by the finger.
1036 * The total work W is the sum of all dW along the path.
1037 *
1038 * dW = F*dx, where dx is the piece of path traveled.
1039 * Force is change of momentum over time, F = dp/dt = m dv/dt.
1040 * Then substituting:
1041 * dW = m (dv/dt) * dx = m * v * dv
1042 *
1043 * Summing along the path, we get:
1044 * W = sum(dW) = sum(m * v * dv) = m * sum(v * dv)
1045 * Since the mass stays constant, the equation for final velocity is:
1046 * vfinal = sqrt(2*sum(v * dv))
1047 *
1048 * Here,
1049 * dv : change of velocity = (v[i+1]-v[i])
1050 * dx : change of distance = (x[i+1]-x[i])
1051 * dt : change of time = (t[i+1]-t[i])
1052 * v : instantaneous velocity = dx/dt
1053 *
1054 * The final formula is:
1055 * vfinal = sqrt(2) * sqrt(sum((v[i]-v[i-1])*|v[i]|)) for all i
1056 * The absolute value is needed to properly account for the sign. If the velocity over a
1057 * particular segment descreases, then this indicates braking, which means that negative
1058 * work was done. So for two positive, but decreasing, velocities, this contribution would be
1059 * negative and will cause a smaller final velocity.
1060 *
1061 * Initial condition
1062 * There are two ways to deal with initial condition:
1063 * 1) Assume that v(0) = 0, which would mean that the screen is initially at rest.
1064 * This is not entirely accurate. We are only taking the past X ms of touch data, where X is
1065 * currently equal to 100. However, a touch event that created a fling probably lasted for longer
1066 * than that, which would mean that the user has already been interacting with the touchscreen
1067 * and it has probably already been moving.
1068 * 2) Assume that the touchscreen has already been moving at a certain velocity, calculate this
1069 * initial velocity and the equivalent energy, and start with this initial energy.
1070 * Consider an example where we have the following data, consisting of 3 points:
1071 * time: t0, t1, t2
1072 * x : x0, x1, x2
1073 * v : 0 , v1, v2
1074 * Here is what will happen in each of these scenarios:
1075 * 1) By directly applying the formula above with the v(0) = 0 boundary condition, we will get
1076 * vfinal = sqrt(2*(|v1|*(v1-v0) + |v2|*(v2-v1))). This can be simplified since v0=0
1077 * vfinal = sqrt(2*(|v1|*v1 + |v2|*(v2-v1))) = sqrt(2*(v1^2 + |v2|*(v2 - v1)))
1078 * since velocity is a real number
1079 * 2) If we treat the screen as already moving, then it must already have an energy (per mass)
1080 * equal to 1/2*v1^2. Then the initial energy should be 1/2*v1*2, and only the second segment
1081 * will contribute to the total kinetic energy (since we can effectively consider that v0=v1).
1082 * This will give the following expression for the final velocity:
1083 * vfinal = sqrt(2*(1/2*v1^2 + |v2|*(v2-v1)))
1084 * This analysis can be generalized to an arbitrary number of samples.
1085 *
1086 *
1087 * Comparing the two equations above, we see that the only mathematical difference
1088 * is the factor of 1/2 in front of the first velocity term.
1089 * This boundary condition would allow for the "proper" calculation of the case when all of the
1090 * samples are equally spaced in time and distance, which should suggest a constant velocity.
1091 *
1092 * Note that approach 2) is sensitive to the proper ordering of the data in time, since
1093 * the boundary condition must be applied to the oldest sample to be accurate.
1094 */
Siarhei Vishniakou97b5e182017-09-01 13:52:33 -07001095static float kineticEnergyToVelocity(float work) {
1096 static constexpr float sqrt2 = 1.41421356237;
1097 return (work < 0 ? -1.0 : 1.0) * sqrtf(fabsf(work)) * sqrt2;
1098}
1099
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001100static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t count) {
1101 // The input should be in reversed time order (most recent sample at index i=0)
1102 // t[i] is in nanoseconds, but due to FP arithmetic, convert to seconds inside this function
Siarhei Vishniakou6de8f5e2018-03-02 18:48:15 -08001103 static constexpr float SECONDS_PER_NANO = 1E-9;
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001104
1105 if (count < 2) {
1106 return 0; // if 0 or 1 points, velocity is zero
1107 }
1108 if (t[1] > t[0]) { // Algorithm will still work, but not perfectly
1109 ALOGE("Samples provided to calculateImpulseVelocity in the wrong order");
1110 }
1111 if (count == 2) { // if 2 points, basic linear calculation
1112 if (t[1] == t[0]) {
1113 ALOGE("Events have identical time stamps t=%" PRId64 ", setting velocity = 0", t[0]);
1114 return 0;
1115 }
Siarhei Vishniakou6de8f5e2018-03-02 18:48:15 -08001116 return (x[1] - x[0]) / (SECONDS_PER_NANO * (t[1] - t[0]));
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001117 }
1118 // Guaranteed to have at least 3 points here
1119 float work = 0;
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001120 for (size_t i = count - 1; i > 0 ; i--) { // start with the oldest sample and go forward in time
1121 if (t[i] == t[i-1]) {
1122 ALOGE("Events have identical time stamps t=%" PRId64 ", skipping sample", t[i]);
1123 continue;
1124 }
Siarhei Vishniakou97b5e182017-09-01 13:52:33 -07001125 float vprev = kineticEnergyToVelocity(work); // v[i-1]
Siarhei Vishniakou6de8f5e2018-03-02 18:48:15 -08001126 float vcurr = (x[i] - x[i-1]) / (SECONDS_PER_NANO * (t[i] - t[i-1])); // v[i]
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001127 work += (vcurr - vprev) * fabsf(vcurr);
1128 if (i == count - 1) {
1129 work *= 0.5; // initial condition, case 2) above
1130 }
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001131 }
Siarhei Vishniakou97b5e182017-09-01 13:52:33 -07001132 return kineticEnergyToVelocity(work);
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001133}
1134
1135bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id,
1136 VelocityTracker::Estimator* outEstimator) const {
1137 outEstimator->clear();
1138
1139 // Iterate over movement samples in reverse time order and collect samples.
1140 float x[HISTORY_SIZE];
1141 float y[HISTORY_SIZE];
1142 nsecs_t time[HISTORY_SIZE];
1143 size_t m = 0; // number of points that will be used for fitting
1144 size_t index = mIndex;
1145 const Movement& newestMovement = mMovements[mIndex];
1146 do {
1147 const Movement& movement = mMovements[index];
1148 if (!movement.idBits.hasBit(id)) {
1149 break;
1150 }
1151
1152 nsecs_t age = newestMovement.eventTime - movement.eventTime;
1153 if (age > HORIZON) {
1154 break;
1155 }
1156
1157 const VelocityTracker::Position& position = movement.getPosition(id);
1158 x[m] = position.x;
1159 y[m] = position.y;
1160 time[m] = movement.eventTime;
1161 index = (index == 0 ? HISTORY_SIZE : index) - 1;
1162 } while (++m < HISTORY_SIZE);
1163
1164 if (m == 0) {
1165 return false; // no data
1166 }
1167 outEstimator->xCoeff[0] = 0;
1168 outEstimator->yCoeff[0] = 0;
1169 outEstimator->xCoeff[1] = calculateImpulseVelocity(time, x, m);
1170 outEstimator->yCoeff[1] = calculateImpulseVelocity(time, y, m);
1171 outEstimator->xCoeff[2] = 0;
1172 outEstimator->yCoeff[2] = 0;
1173 outEstimator->time = newestMovement.eventTime;
1174 outEstimator->degree = 2; // similar results to 2nd degree fit
1175 outEstimator->confidence = 1;
1176#if DEBUG_STRATEGY
1177 ALOGD("velocity: (%f, %f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]);
1178#endif
1179 return true;
1180}
1181
Jeff Brown5912f952013-07-01 19:10:31 -07001182} // namespace android