blob: 7f427f2364ea71c20e2d50c0a138144ed584d658 [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"
Jeff Brown5912f952013-07-01 19:10:31 -070018
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -070019#include <array>
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070020#include <inttypes.h>
Jeff Brown5912f952013-07-01 19:10:31 -070021#include <limits.h>
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070022#include <math.h>
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -070023#include <optional>
Jeff Brown5912f952013-07-01 19:10:31 -070024
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070025#include <android-base/stringprintf.h>
Jeff Brown5912f952013-07-01 19:10:31 -070026#include <input/VelocityTracker.h>
27#include <utils/BitSet.h>
Jeff Brown5912f952013-07-01 19:10:31 -070028#include <utils/Timers.h>
29
30namespace android {
31
Siarhei Vishniakou276467b2022-03-17 09:43:28 -070032/**
33 * Log debug messages about velocity tracking.
34 * Enable this via "adb shell setprop log.tag.VelocityTrackerVelocity DEBUG" (requires restart)
35 */
36const bool DEBUG_VELOCITY =
37 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Velocity", ANDROID_LOG_INFO);
38
39/**
40 * Log debug messages about the progress of the algorithm itself.
41 * Enable this via "adb shell setprop log.tag.VelocityTrackerStrategy DEBUG" (requires restart)
42 */
43const bool DEBUG_STRATEGY =
44 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Strategy", ANDROID_LOG_INFO);
45
46/**
47 * Log debug messages about the 'impulse' strategy.
48 * Enable this via "adb shell setprop log.tag.VelocityTrackerImpulse DEBUG" (requires restart)
49 */
50const bool DEBUG_IMPULSE =
51 __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Impulse", ANDROID_LOG_INFO);
52
Jeff Brown5912f952013-07-01 19:10:31 -070053// Nanoseconds per milliseconds.
54static const nsecs_t NANOS_PER_MS = 1000000;
55
56// Threshold for determining that a pointer has stopped moving.
57// Some input devices do not send ACTION_MOVE events in the case where a pointer has
58// stopped. We need to detect this case so that we can accurately predict the
59// velocity after the pointer starts moving again.
Siarhei Vishniakou21fcc2b2022-06-17 21:29:02 +000060static const nsecs_t ASSUME_POINTER_STOPPED_TIME = 40 * NANOS_PER_MS;
Jeff Brown5912f952013-07-01 19:10:31 -070061
62
63static float vectorDot(const float* a, const float* b, uint32_t m) {
64 float r = 0;
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070065 for (size_t i = 0; i < m; i++) {
Jeff Brown5912f952013-07-01 19:10:31 -070066 r += *(a++) * *(b++);
67 }
68 return r;
69}
70
71static float vectorNorm(const float* a, uint32_t m) {
72 float r = 0;
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070073 for (size_t i = 0; i < m; i++) {
Jeff Brown5912f952013-07-01 19:10:31 -070074 float t = *(a++);
75 r += t * t;
76 }
77 return sqrtf(r);
78}
79
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070080static std::string vectorToString(const float* a, uint32_t m) {
81 std::string str;
82 str += "[";
Siarhei Vishniakou7b9d1892017-07-05 18:58:41 -070083 for (size_t i = 0; i < m; i++) {
84 if (i) {
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070085 str += ",";
Jeff Brown5912f952013-07-01 19:10:31 -070086 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070087 str += android::base::StringPrintf(" %f", *(a++));
Jeff Brown5912f952013-07-01 19:10:31 -070088 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070089 str += " ]";
Jeff Brown5912f952013-07-01 19:10:31 -070090 return str;
91}
92
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -070093static std::string vectorToString(const std::vector<float>& v) {
94 return vectorToString(v.data(), v.size());
95}
96
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -070097static std::string matrixToString(const float* a, uint32_t m, uint32_t n, bool rowMajor) {
98 std::string str;
99 str = "[";
Jeff Brown5912f952013-07-01 19:10:31 -0700100 for (size_t i = 0; i < m; i++) {
101 if (i) {
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700102 str += ",";
Jeff Brown5912f952013-07-01 19:10:31 -0700103 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700104 str += " [";
Jeff Brown5912f952013-07-01 19:10:31 -0700105 for (size_t j = 0; j < n; j++) {
106 if (j) {
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700107 str += ",";
Jeff Brown5912f952013-07-01 19:10:31 -0700108 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700109 str += android::base::StringPrintf(" %f", a[rowMajor ? i * n + j : j * m + i]);
Jeff Brown5912f952013-07-01 19:10:31 -0700110 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700111 str += " ]";
Jeff Brown5912f952013-07-01 19:10:31 -0700112 }
Siarhei Vishniakouec2727e2017-07-06 10:22:03 -0700113 str += " ]";
Jeff Brown5912f952013-07-01 19:10:31 -0700114 return str;
115}
Jeff Brown5912f952013-07-01 19:10:31 -0700116
117
118// --- VelocityTracker ---
119
Chris Yef8591482020-04-17 11:49:17 -0700120VelocityTracker::VelocityTracker(const Strategy strategy)
121 : mLastEventTime(0), mCurrentPointerIdBits(0), mActivePointerId(-1) {
Jeff Brown5912f952013-07-01 19:10:31 -0700122 // Configure the strategy.
123 if (!configureStrategy(strategy)) {
Chris Yef8591482020-04-17 11:49:17 -0700124 ALOGE("Unrecognized velocity tracker strategy %" PRId32 ".", strategy);
125 if (!configureStrategy(VelocityTracker::DEFAULT_STRATEGY)) {
126 LOG_ALWAYS_FATAL("Could not create the default velocity tracker strategy '%" PRId32
127 "'!",
128 strategy);
Jeff Brown5912f952013-07-01 19:10:31 -0700129 }
130 }
131}
132
133VelocityTracker::~VelocityTracker() {
Jeff Brown5912f952013-07-01 19:10:31 -0700134}
135
Chris Yef8591482020-04-17 11:49:17 -0700136bool VelocityTracker::configureStrategy(Strategy strategy) {
137 if (strategy == VelocityTracker::Strategy::DEFAULT) {
138 mStrategy = createStrategy(VelocityTracker::DEFAULT_STRATEGY);
139 } else {
140 mStrategy = createStrategy(strategy);
141 }
Yi Kong5bed83b2018-07-17 12:53:47 -0700142 return mStrategy != nullptr;
Jeff Brown5912f952013-07-01 19:10:31 -0700143}
144
Chris Yef8591482020-04-17 11:49:17 -0700145std::unique_ptr<VelocityTrackerStrategy> VelocityTracker::createStrategy(
146 VelocityTracker::Strategy strategy) {
147 switch (strategy) {
148 case VelocityTracker::Strategy::IMPULSE:
Siarhei Vishniakou21fcc2b2022-06-17 21:29:02 +0000149 if (DEBUG_STRATEGY) {
150 ALOGI("Initializing impulse strategy");
151 }
Chris Yef8591482020-04-17 11:49:17 -0700152 return std::make_unique<ImpulseVelocityTrackerStrategy>();
153
154 case VelocityTracker::Strategy::LSQ1:
155 return std::make_unique<LeastSquaresVelocityTrackerStrategy>(1);
156
157 case VelocityTracker::Strategy::LSQ2:
Siarhei Vishniakou21fcc2b2022-06-17 21:29:02 +0000158 if (DEBUG_STRATEGY && !DEBUG_IMPULSE) {
159 ALOGI("Initializing lsq2 strategy");
160 }
Chris Yef8591482020-04-17 11:49:17 -0700161 return std::make_unique<LeastSquaresVelocityTrackerStrategy>(2);
162
163 case VelocityTracker::Strategy::LSQ3:
164 return std::make_unique<LeastSquaresVelocityTrackerStrategy>(3);
165
166 case VelocityTracker::Strategy::WLSQ2_DELTA:
167 return std::make_unique<
168 LeastSquaresVelocityTrackerStrategy>(2,
169 LeastSquaresVelocityTrackerStrategy::
170 WEIGHTING_DELTA);
171 case VelocityTracker::Strategy::WLSQ2_CENTRAL:
172 return std::make_unique<
173 LeastSquaresVelocityTrackerStrategy>(2,
174 LeastSquaresVelocityTrackerStrategy::
175 WEIGHTING_CENTRAL);
176 case VelocityTracker::Strategy::WLSQ2_RECENT:
177 return std::make_unique<
178 LeastSquaresVelocityTrackerStrategy>(2,
179 LeastSquaresVelocityTrackerStrategy::
180 WEIGHTING_RECENT);
181
182 case VelocityTracker::Strategy::INT1:
183 return std::make_unique<IntegratingVelocityTrackerStrategy>(1);
184
185 case VelocityTracker::Strategy::INT2:
186 return std::make_unique<IntegratingVelocityTrackerStrategy>(2);
187
188 case VelocityTracker::Strategy::LEGACY:
189 return std::make_unique<LegacyVelocityTrackerStrategy>();
190
191 default:
192 break;
Jeff Brown5912f952013-07-01 19:10:31 -0700193 }
Yi Kong5bed83b2018-07-17 12:53:47 -0700194 return nullptr;
Jeff Brown5912f952013-07-01 19:10:31 -0700195}
196
197void VelocityTracker::clear() {
198 mCurrentPointerIdBits.clear();
199 mActivePointerId = -1;
200
201 mStrategy->clear();
202}
203
204void VelocityTracker::clearPointers(BitSet32 idBits) {
205 BitSet32 remainingIdBits(mCurrentPointerIdBits.value & ~idBits.value);
206 mCurrentPointerIdBits = remainingIdBits;
207
208 if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) {
209 mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1;
210 }
211
212 mStrategy->clearPointers(idBits);
213}
214
Siarhei Vishniakouae0f9902020-09-14 19:23:31 -0500215void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits,
216 const std::vector<VelocityTracker::Position>& positions) {
217 LOG_ALWAYS_FATAL_IF(idBits.count() != positions.size(),
218 "Mismatching number of pointers, idBits=%" PRIu32 ", positions=%zu",
219 idBits.count(), positions.size());
Jeff Brown5912f952013-07-01 19:10:31 -0700220 while (idBits.count() > MAX_POINTERS) {
221 idBits.clearLastMarkedBit();
222 }
223
Siarhei Vishniakou21fcc2b2022-06-17 21:29:02 +0000224 if ((mCurrentPointerIdBits.value & idBits.value)
225 && eventTime >= mLastEventTime + ASSUME_POINTER_STOPPED_TIME) {
226 if (DEBUG_VELOCITY) {
227 ALOGD("VelocityTracker: stopped for %0.3f ms, clearing state.",
228 (eventTime - mLastEventTime) * 0.000001f);
229 }
Jeff Brown5912f952013-07-01 19:10:31 -0700230 // We have not received any movements for too long. Assume that all pointers
231 // have stopped.
232 mStrategy->clear();
233 }
234 mLastEventTime = eventTime;
235
236 mCurrentPointerIdBits = idBits;
237 if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) {
238 mActivePointerId = idBits.isEmpty() ? -1 : idBits.firstMarkedBit();
239 }
240
241 mStrategy->addMovement(eventTime, idBits, positions);
242
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700243 if (DEBUG_VELOCITY) {
244 ALOGD("VelocityTracker: addMovement eventTime=%" PRId64
245 ", idBits=0x%08x, activePointerId=%d",
246 eventTime, idBits.value, mActivePointerId);
247 for (BitSet32 iterBits(idBits); !iterBits.isEmpty();) {
248 uint32_t id = iterBits.firstMarkedBit();
249 uint32_t index = idBits.getIndexOfBit(id);
250 iterBits.clearBit(id);
251 Estimator estimator;
252 getEstimator(id, &estimator);
253 ALOGD(" %d: position (%0.3f, %0.3f), "
254 "estimator (degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f)",
255 id, positions[index].x, positions[index].y, int(estimator.degree),
256 vectorToString(estimator.xCoeff, estimator.degree + 1).c_str(),
257 vectorToString(estimator.yCoeff, estimator.degree + 1).c_str(),
258 estimator.confidence);
259 }
Jeff Brown5912f952013-07-01 19:10:31 -0700260 }
Jeff Brown5912f952013-07-01 19:10:31 -0700261}
262
263void VelocityTracker::addMovement(const MotionEvent* event) {
264 int32_t actionMasked = event->getActionMasked();
265
266 switch (actionMasked) {
267 case AMOTION_EVENT_ACTION_DOWN:
268 case AMOTION_EVENT_ACTION_HOVER_ENTER:
269 // Clear all pointers on down before adding the new movement.
270 clear();
271 break;
272 case AMOTION_EVENT_ACTION_POINTER_DOWN: {
273 // Start a new movement trace for a pointer that just went down.
274 // We do this on down instead of on up because the client may want to query the
275 // final velocity for a pointer that just went up.
276 BitSet32 downIdBits;
277 downIdBits.markBit(event->getPointerId(event->getActionIndex()));
278 clearPointers(downIdBits);
279 break;
280 }
281 case AMOTION_EVENT_ACTION_MOVE:
282 case AMOTION_EVENT_ACTION_HOVER_MOVE:
283 break;
Siarhei Vishniakou21fcc2b2022-06-17 21:29:02 +0000284 default:
285 // Ignore all other actions because they do not convey any new information about
Jeff Brown5912f952013-07-01 19:10:31 -0700286 // pointer movement. We also want to preserve the last known velocity of the pointers.
287 // Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
288 // of the pointers that went up. ACTION_POINTER_UP does include the new position of
289 // pointers that remained down but we will also receive an ACTION_MOVE with this
290 // information if any of them actually moved. Since we don't know how many pointers
291 // will be going up at once it makes sense to just wait for the following ACTION_MOVE
292 // before adding the movement.
293 return;
294 }
295
296 size_t pointerCount = event->getPointerCount();
297 if (pointerCount > MAX_POINTERS) {
298 pointerCount = MAX_POINTERS;
299 }
300
301 BitSet32 idBits;
302 for (size_t i = 0; i < pointerCount; i++) {
303 idBits.markBit(event->getPointerId(i));
304 }
305
306 uint32_t pointerIndex[MAX_POINTERS];
307 for (size_t i = 0; i < pointerCount; i++) {
308 pointerIndex[i] = idBits.getIndexOfBit(event->getPointerId(i));
309 }
310
Siarhei Vishniakouae0f9902020-09-14 19:23:31 -0500311 std::vector<Position> positions;
312 positions.resize(pointerCount);
Jeff Brown5912f952013-07-01 19:10:31 -0700313
314 size_t historySize = event->getHistorySize();
Siarhei Vishniakou69e4d0f2020-09-14 19:53:29 -0500315 for (size_t h = 0; h <= historySize; h++) {
316 nsecs_t eventTime = event->getHistoricalEventTime(h);
Jeff Brown5912f952013-07-01 19:10:31 -0700317 for (size_t i = 0; i < pointerCount; i++) {
318 uint32_t index = pointerIndex[i];
Siarhei Vishniakou4c3137a2018-11-13 13:33:52 -0800319 positions[index].x = event->getHistoricalX(i, h);
320 positions[index].y = event->getHistoricalY(i, h);
Jeff Brown5912f952013-07-01 19:10:31 -0700321 }
322 addMovement(eventTime, idBits, positions);
323 }
Jeff Brown5912f952013-07-01 19:10:31 -0700324}
325
326bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const {
327 Estimator estimator;
328 if (getEstimator(id, &estimator) && estimator.degree >= 1) {
329 *outVx = estimator.xCoeff[1];
330 *outVy = estimator.yCoeff[1];
331 return true;
332 }
333 *outVx = 0;
334 *outVy = 0;
335 return false;
336}
337
338bool VelocityTracker::getEstimator(uint32_t id, Estimator* outEstimator) const {
339 return mStrategy->getEstimator(id, outEstimator);
340}
341
342
343// --- LeastSquaresVelocityTrackerStrategy ---
344
Jeff Brown5912f952013-07-01 19:10:31 -0700345LeastSquaresVelocityTrackerStrategy::LeastSquaresVelocityTrackerStrategy(
346 uint32_t degree, Weighting weighting) :
347 mDegree(degree), mWeighting(weighting) {
348 clear();
349}
350
351LeastSquaresVelocityTrackerStrategy::~LeastSquaresVelocityTrackerStrategy() {
352}
353
354void LeastSquaresVelocityTrackerStrategy::clear() {
355 mIndex = 0;
356 mMovements[0].idBits.clear();
357}
358
359void LeastSquaresVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
360 BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
361 mMovements[mIndex].idBits = remainingIdBits;
362}
363
Siarhei Vishniakouae0f9902020-09-14 19:23:31 -0500364void LeastSquaresVelocityTrackerStrategy::addMovement(
365 nsecs_t eventTime, BitSet32 idBits,
366 const std::vector<VelocityTracker::Position>& positions) {
Siarhei Vishniakou346ac6a2019-04-10 09:58:05 -0700367 if (mMovements[mIndex].eventTime != eventTime) {
368 // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
369 // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
370 // the new pointer. If the eventtimes for both events are identical, just update the data
371 // for this time.
372 // We only compare against the last value, as it is likely that addMovement is called
373 // in chronological order as events occur.
374 mIndex++;
375 }
376 if (mIndex == HISTORY_SIZE) {
Jeff Brown5912f952013-07-01 19:10:31 -0700377 mIndex = 0;
378 }
379
380 Movement& movement = mMovements[mIndex];
381 movement.eventTime = eventTime;
382 movement.idBits = idBits;
383 uint32_t count = idBits.count();
384 for (uint32_t i = 0; i < count; i++) {
385 movement.positions[i] = positions[i];
386 }
387}
388
389/**
390 * Solves a linear least squares problem to obtain a N degree polynomial that fits
391 * the specified input data as nearly as possible.
392 *
393 * Returns true if a solution is found, false otherwise.
394 *
395 * The input consists of two vectors of data points X and Y with indices 0..m-1
396 * along with a weight vector W of the same size.
397 *
398 * The output is a vector B with indices 0..n that describes a polynomial
399 * that fits the data, such the sum of W[i] * W[i] * abs(Y[i] - (B[0] + B[1] X[i]
400 * + B[2] X[i]^2 ... B[n] X[i]^n)) for all i between 0 and m-1 is minimized.
401 *
402 * Accordingly, the weight vector W should be initialized by the caller with the
403 * reciprocal square root of the variance of the error in each input data point.
404 * In other words, an ideal choice for W would be W[i] = 1 / var(Y[i]) = 1 / stddev(Y[i]).
405 * The weights express the relative importance of each data point. If the weights are
406 * all 1, then the data points are considered to be of equal importance when fitting
407 * the polynomial. It is a good idea to choose weights that diminish the importance
408 * of data points that may have higher than usual error margins.
409 *
410 * Errors among data points are assumed to be independent. W is represented here
411 * as a vector although in the literature it is typically taken to be a diagonal matrix.
412 *
413 * That is to say, the function that generated the input data can be approximated
414 * by y(x) ~= B[0] + B[1] x + B[2] x^2 + ... + B[n] x^n.
415 *
416 * The coefficient of determination (R^2) is also returned to describe the goodness
417 * of fit of the model for the given data. It is a value between 0 and 1, where 1
418 * indicates perfect correspondence.
419 *
420 * This function first expands the X vector to a m by n matrix A such that
421 * A[i][0] = 1, A[i][1] = X[i], A[i][2] = X[i]^2, ..., A[i][n] = X[i]^n, then
422 * multiplies it by w[i]./
423 *
424 * Then it calculates the QR decomposition of A yielding an m by m orthonormal matrix Q
425 * and an m by n upper triangular matrix R. Because R is upper triangular (lower
426 * part is all zeroes), we can simplify the decomposition into an m by n matrix
427 * Q1 and a n by n matrix R1 such that A = Q1 R1.
428 *
429 * Finally we solve the system of linear equations given by R1 B = (Qtranspose W Y)
430 * to find B.
431 *
432 * For efficiency, we lay out A and Q column-wise in memory because we frequently
433 * operate on the column vectors. Conversely, we lay out R row-wise.
434 *
435 * http://en.wikipedia.org/wiki/Numerical_methods_for_linear_least_squares
436 * http://en.wikipedia.org/wiki/Gram-Schmidt
437 */
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500438static bool solveLeastSquares(const std::vector<float>& x, const std::vector<float>& y,
439 const std::vector<float>& w, uint32_t n, float* outB, float* outDet) {
440 const size_t m = x.size();
Siarhei Vishniakou21fcc2b2022-06-17 21:29:02 +0000441 if (DEBUG_STRATEGY) {
442 ALOGD("solveLeastSquares: m=%d, n=%d, x=%s, y=%s, w=%s", int(m), int(n),
443 vectorToString(x).c_str(), vectorToString(y).c_str(), vectorToString(w).c_str());
444 }
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500445 LOG_ALWAYS_FATAL_IF(m != y.size() || m != w.size(), "Mismatched vector sizes");
Jeff Brown5912f952013-07-01 19:10:31 -0700446
447 // Expand the X vector to a matrix A, pre-multiplied by the weights.
448 float a[n][m]; // column-major order
449 for (uint32_t h = 0; h < m; h++) {
450 a[0][h] = w[h];
451 for (uint32_t i = 1; i < n; i++) {
452 a[i][h] = a[i - 1][h] * x[h];
453 }
454 }
Siarhei Vishniakou21fcc2b2022-06-17 21:29:02 +0000455 if (DEBUG_STRATEGY) {
456 ALOGD(" - a=%s", matrixToString(&a[0][0], m, n, false /*rowMajor*/).c_str());
457 }
Jeff Brown5912f952013-07-01 19:10:31 -0700458
459 // Apply the Gram-Schmidt process to A to obtain its QR decomposition.
460 float q[n][m]; // orthonormal basis, column-major order
461 float r[n][n]; // upper triangular matrix, row-major order
462 for (uint32_t j = 0; j < n; j++) {
463 for (uint32_t h = 0; h < m; h++) {
464 q[j][h] = a[j][h];
465 }
466 for (uint32_t i = 0; i < j; i++) {
467 float dot = vectorDot(&q[j][0], &q[i][0], m);
468 for (uint32_t h = 0; h < m; h++) {
469 q[j][h] -= dot * q[i][h];
470 }
471 }
472
473 float norm = vectorNorm(&q[j][0], m);
474 if (norm < 0.000001f) {
475 // vectors are linearly dependent or zero so no solution
Siarhei Vishniakou21fcc2b2022-06-17 21:29:02 +0000476 if (DEBUG_STRATEGY) {
477 ALOGD(" - no solution, norm=%f", norm);
478 }
Jeff Brown5912f952013-07-01 19:10:31 -0700479 return false;
480 }
481
482 float invNorm = 1.0f / norm;
483 for (uint32_t h = 0; h < m; h++) {
484 q[j][h] *= invNorm;
485 }
486 for (uint32_t i = 0; i < n; i++) {
487 r[j][i] = i < j ? 0 : vectorDot(&q[j][0], &a[i][0], m);
488 }
489 }
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700490 if (DEBUG_STRATEGY) {
491 ALOGD(" - q=%s", matrixToString(&q[0][0], m, n, false /*rowMajor*/).c_str());
492 ALOGD(" - r=%s", matrixToString(&r[0][0], n, n, true /*rowMajor*/).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700493
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700494 // calculate QR, if we factored A correctly then QR should equal A
495 float qr[n][m];
496 for (uint32_t h = 0; h < m; h++) {
497 for (uint32_t i = 0; i < n; i++) {
498 qr[i][h] = 0;
499 for (uint32_t j = 0; j < n; j++) {
500 qr[i][h] += q[j][h] * r[j][i];
501 }
Jeff Brown5912f952013-07-01 19:10:31 -0700502 }
503 }
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -0700504 ALOGD(" - qr=%s", matrixToString(&qr[0][0], m, n, false /*rowMajor*/).c_str());
Jeff Brown5912f952013-07-01 19:10:31 -0700505 }
Jeff Brown5912f952013-07-01 19:10:31 -0700506
507 // Solve R B = Qt W Y to find B. This is easy because R is upper triangular.
508 // We just work from bottom-right to top-left calculating B's coefficients.
509 float wy[m];
510 for (uint32_t h = 0; h < m; h++) {
511 wy[h] = y[h] * w[h];
512 }
Dan Austin389ddba2015-09-22 14:32:03 -0700513 for (uint32_t i = n; i != 0; ) {
514 i--;
Jeff Brown5912f952013-07-01 19:10:31 -0700515 outB[i] = vectorDot(&q[i][0], wy, m);
516 for (uint32_t j = n - 1; j > i; j--) {
517 outB[i] -= r[i][j] * outB[j];
518 }
519 outB[i] /= r[i][i];
520 }
Siarhei Vishniakou21fcc2b2022-06-17 21:29:02 +0000521 if (DEBUG_STRATEGY) {
522 ALOGD(" - b=%s", vectorToString(outB, n).c_str());
523 }
Jeff Brown5912f952013-07-01 19:10:31 -0700524
525 // Calculate the coefficient of determination as 1 - (SSerr / SStot) where
526 // SSerr is the residual sum of squares (variance of the error),
527 // and SStot is the total sum of squares (variance of the data) where each
528 // has been weighted.
529 float ymean = 0;
530 for (uint32_t h = 0; h < m; h++) {
531 ymean += y[h];
532 }
533 ymean /= m;
534
535 float sserr = 0;
536 float sstot = 0;
537 for (uint32_t h = 0; h < m; h++) {
538 float err = y[h] - outB[0];
539 float term = 1;
540 for (uint32_t i = 1; i < n; i++) {
541 term *= x[h];
542 err -= term * outB[i];
543 }
544 sserr += w[h] * w[h] * err * err;
545 float var = y[h] - ymean;
546 sstot += w[h] * w[h] * var * var;
547 }
548 *outDet = sstot > 0.000001f ? 1.0f - (sserr / sstot) : 1;
Siarhei Vishniakou21fcc2b2022-06-17 21:29:02 +0000549 if (DEBUG_STRATEGY) {
550 ALOGD(" - sserr=%f", sserr);
551 ALOGD(" - sstot=%f", sstot);
552 ALOGD(" - det=%f", *outDet);
553 }
Jeff Brown5912f952013-07-01 19:10:31 -0700554 return true;
555}
556
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100557/*
558 * Optimized unweighted second-order least squares fit. About 2x speed improvement compared to
559 * the default implementation
560 */
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700561static std::optional<std::array<float, 3>> solveUnweightedLeastSquaresDeg2(
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500562 const std::vector<float>& x, const std::vector<float>& y) {
563 const size_t count = x.size();
564 LOG_ALWAYS_FATAL_IF(count != y.size(), "Mismatching array sizes");
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700565 // Solving y = a*x^2 + b*x + c
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100566 float sxi = 0, sxiyi = 0, syi = 0, sxi2 = 0, sxi3 = 0, sxi2yi = 0, sxi4 = 0;
567
568 for (size_t i = 0; i < count; i++) {
569 float xi = x[i];
570 float yi = y[i];
571 float xi2 = xi*xi;
572 float xi3 = xi2*xi;
573 float xi4 = xi3*xi;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100574 float xiyi = xi*yi;
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700575 float xi2yi = xi2*yi;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100576
577 sxi += xi;
578 sxi2 += xi2;
579 sxiyi += xiyi;
580 sxi2yi += xi2yi;
581 syi += yi;
582 sxi3 += xi3;
583 sxi4 += xi4;
584 }
585
586 float Sxx = sxi2 - sxi*sxi / count;
587 float Sxy = sxiyi - sxi*syi / count;
588 float Sxx2 = sxi3 - sxi*sxi2 / count;
589 float Sx2y = sxi2yi - sxi2*syi / count;
590 float Sx2x2 = sxi4 - sxi2*sxi2 / count;
591
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100592 float denominator = Sxx*Sx2x2 - Sxx2*Sxx2;
593 if (denominator == 0) {
594 ALOGW("division by 0 when computing velocity, Sxx=%f, Sx2x2=%f, Sxx2=%f", Sxx, Sx2x2, Sxx2);
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700595 return std::nullopt;
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100596 }
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700597 // Compute a
598 float numerator = Sx2y*Sxx - Sxy*Sxx2;
599 float a = numerator / denominator;
600
601 // Compute b
602 numerator = Sxy*Sx2x2 - Sx2y*Sxx2;
603 float b = numerator / denominator;
604
605 // Compute c
606 float c = syi/count - b * sxi/count - a * sxi2/count;
607
608 return std::make_optional(std::array<float, 3>({c, b, a}));
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100609}
610
Jeff Brown5912f952013-07-01 19:10:31 -0700611bool LeastSquaresVelocityTrackerStrategy::getEstimator(uint32_t id,
612 VelocityTracker::Estimator* outEstimator) const {
613 outEstimator->clear();
614
615 // Iterate over movement samples in reverse time order and collect samples.
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500616 std::vector<float> x;
617 std::vector<float> y;
618 std::vector<float> w;
619 std::vector<float> time;
620
Jeff Brown5912f952013-07-01 19:10:31 -0700621 uint32_t index = mIndex;
622 const Movement& newestMovement = mMovements[mIndex];
623 do {
624 const Movement& movement = mMovements[index];
625 if (!movement.idBits.hasBit(id)) {
626 break;
627 }
628
629 nsecs_t age = newestMovement.eventTime - movement.eventTime;
630 if (age > HORIZON) {
631 break;
632 }
633
634 const VelocityTracker::Position& position = movement.getPosition(id);
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500635 x.push_back(position.x);
636 y.push_back(position.y);
637 w.push_back(chooseWeight(index));
638 time.push_back(-age * 0.000000001f);
Jeff Brown5912f952013-07-01 19:10:31 -0700639 index = (index == 0 ? HISTORY_SIZE : index) - 1;
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500640 } while (x.size() < HISTORY_SIZE);
Jeff Brown5912f952013-07-01 19:10:31 -0700641
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500642 const size_t m = x.size();
Jeff Brown5912f952013-07-01 19:10:31 -0700643 if (m == 0) {
644 return false; // no data
645 }
646
647 // Calculate a least squares polynomial fit.
648 uint32_t degree = mDegree;
649 if (degree > m - 1) {
650 degree = m - 1;
651 }
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700652
653 if (degree == 2 && mWeighting == WEIGHTING_NONE) {
654 // Optimize unweighted, quadratic polynomial fit
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500655 std::optional<std::array<float, 3>> xCoeff = solveUnweightedLeastSquaresDeg2(time, x);
656 std::optional<std::array<float, 3>> yCoeff = solveUnweightedLeastSquaresDeg2(time, y);
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700657 if (xCoeff && yCoeff) {
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100658 outEstimator->time = newestMovement.eventTime;
659 outEstimator->degree = 2;
660 outEstimator->confidence = 1;
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700661 for (size_t i = 0; i <= outEstimator->degree; i++) {
662 outEstimator->xCoeff[i] = (*xCoeff)[i];
663 outEstimator->yCoeff[i] = (*yCoeff)[i];
664 }
Siarhei Vishniakou489d38e2017-06-16 17:16:25 +0100665 return true;
666 }
Siarhei Vishniakoue96bc7a2018-09-06 10:19:16 -0700667 } else if (degree >= 1) {
668 // General case for an Nth degree polynomial fit
Jeff Brown5912f952013-07-01 19:10:31 -0700669 float xdet, ydet;
670 uint32_t n = degree + 1;
Siarhei Vishniakou81e8b162020-09-14 22:10:11 -0500671 if (solveLeastSquares(time, x, w, n, outEstimator->xCoeff, &xdet) &&
672 solveLeastSquares(time, y, w, n, outEstimator->yCoeff, &ydet)) {
Jeff Brown5912f952013-07-01 19:10:31 -0700673 outEstimator->time = newestMovement.eventTime;
674 outEstimator->degree = degree;
675 outEstimator->confidence = xdet * ydet;
Siarhei Vishniakou21fcc2b2022-06-17 21:29:02 +0000676 if (DEBUG_STRATEGY) {
677 ALOGD("estimate: degree=%d, xCoeff=%s, yCoeff=%s, confidence=%f",
678 int(outEstimator->degree), vectorToString(outEstimator->xCoeff, n).c_str(),
679 vectorToString(outEstimator->yCoeff, n).c_str(), outEstimator->confidence);
680 }
Jeff Brown5912f952013-07-01 19:10:31 -0700681 return true;
682 }
683 }
684
685 // No velocity data available for this pointer, but we do have its current position.
686 outEstimator->xCoeff[0] = x[0];
687 outEstimator->yCoeff[0] = y[0];
688 outEstimator->time = newestMovement.eventTime;
689 outEstimator->degree = 0;
690 outEstimator->confidence = 1;
691 return true;
692}
693
694float LeastSquaresVelocityTrackerStrategy::chooseWeight(uint32_t index) const {
695 switch (mWeighting) {
696 case WEIGHTING_DELTA: {
697 // Weight points based on how much time elapsed between them and the next
698 // point so that points that "cover" a shorter time span are weighed less.
699 // delta 0ms: 0.5
700 // delta 10ms: 1.0
701 if (index == mIndex) {
702 return 1.0f;
703 }
704 uint32_t nextIndex = (index + 1) % HISTORY_SIZE;
705 float deltaMillis = (mMovements[nextIndex].eventTime- mMovements[index].eventTime)
706 * 0.000001f;
707 if (deltaMillis < 0) {
708 return 0.5f;
709 }
710 if (deltaMillis < 10) {
711 return 0.5f + deltaMillis * 0.05;
712 }
713 return 1.0f;
714 }
715
716 case WEIGHTING_CENTRAL: {
717 // Weight points based on their age, weighing very recent and very old points less.
718 // age 0ms: 0.5
719 // age 10ms: 1.0
720 // age 50ms: 1.0
721 // age 60ms: 0.5
722 float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
723 * 0.000001f;
724 if (ageMillis < 0) {
725 return 0.5f;
726 }
727 if (ageMillis < 10) {
728 return 0.5f + ageMillis * 0.05;
729 }
730 if (ageMillis < 50) {
731 return 1.0f;
732 }
733 if (ageMillis < 60) {
734 return 0.5f + (60 - ageMillis) * 0.05;
735 }
736 return 0.5f;
737 }
738
739 case WEIGHTING_RECENT: {
740 // Weight points based on their age, weighing older points less.
741 // age 0ms: 1.0
742 // age 50ms: 1.0
743 // age 100ms: 0.5
744 float ageMillis = (mMovements[mIndex].eventTime - mMovements[index].eventTime)
745 * 0.000001f;
746 if (ageMillis < 50) {
747 return 1.0f;
748 }
749 if (ageMillis < 100) {
750 return 0.5f + (100 - ageMillis) * 0.01f;
751 }
752 return 0.5f;
753 }
754
755 case WEIGHTING_NONE:
756 default:
757 return 1.0f;
758 }
759}
760
761
762// --- IntegratingVelocityTrackerStrategy ---
763
764IntegratingVelocityTrackerStrategy::IntegratingVelocityTrackerStrategy(uint32_t degree) :
765 mDegree(degree) {
766}
767
768IntegratingVelocityTrackerStrategy::~IntegratingVelocityTrackerStrategy() {
769}
770
771void IntegratingVelocityTrackerStrategy::clear() {
772 mPointerIdBits.clear();
773}
774
775void IntegratingVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
776 mPointerIdBits.value &= ~idBits.value;
777}
778
Siarhei Vishniakouae0f9902020-09-14 19:23:31 -0500779void IntegratingVelocityTrackerStrategy::addMovement(
780 nsecs_t eventTime, BitSet32 idBits,
781 const std::vector<VelocityTracker::Position>& positions) {
Jeff Brown5912f952013-07-01 19:10:31 -0700782 uint32_t index = 0;
783 for (BitSet32 iterIdBits(idBits); !iterIdBits.isEmpty();) {
784 uint32_t id = iterIdBits.clearFirstMarkedBit();
785 State& state = mPointerState[id];
786 const VelocityTracker::Position& position = positions[index++];
787 if (mPointerIdBits.hasBit(id)) {
788 updateState(state, eventTime, position.x, position.y);
789 } else {
790 initState(state, eventTime, position.x, position.y);
791 }
792 }
793
794 mPointerIdBits = idBits;
795}
796
797bool IntegratingVelocityTrackerStrategy::getEstimator(uint32_t id,
798 VelocityTracker::Estimator* outEstimator) const {
799 outEstimator->clear();
800
801 if (mPointerIdBits.hasBit(id)) {
802 const State& state = mPointerState[id];
803 populateEstimator(state, outEstimator);
804 return true;
805 }
806
807 return false;
808}
809
810void IntegratingVelocityTrackerStrategy::initState(State& state,
811 nsecs_t eventTime, float xpos, float ypos) const {
812 state.updateTime = eventTime;
813 state.degree = 0;
814
815 state.xpos = xpos;
816 state.xvel = 0;
817 state.xaccel = 0;
818 state.ypos = ypos;
819 state.yvel = 0;
820 state.yaccel = 0;
821}
822
823void IntegratingVelocityTrackerStrategy::updateState(State& state,
824 nsecs_t eventTime, float xpos, float ypos) const {
825 const nsecs_t MIN_TIME_DELTA = 2 * NANOS_PER_MS;
826 const float FILTER_TIME_CONSTANT = 0.010f; // 10 milliseconds
827
828 if (eventTime <= state.updateTime + MIN_TIME_DELTA) {
829 return;
830 }
831
832 float dt = (eventTime - state.updateTime) * 0.000000001f;
833 state.updateTime = eventTime;
834
835 float xvel = (xpos - state.xpos) / dt;
836 float yvel = (ypos - state.ypos) / dt;
837 if (state.degree == 0) {
838 state.xvel = xvel;
839 state.yvel = yvel;
840 state.degree = 1;
841 } else {
842 float alpha = dt / (FILTER_TIME_CONSTANT + dt);
843 if (mDegree == 1) {
844 state.xvel += (xvel - state.xvel) * alpha;
845 state.yvel += (yvel - state.yvel) * alpha;
846 } else {
847 float xaccel = (xvel - state.xvel) / dt;
848 float yaccel = (yvel - state.yvel) / dt;
849 if (state.degree == 1) {
850 state.xaccel = xaccel;
851 state.yaccel = yaccel;
852 state.degree = 2;
853 } else {
854 state.xaccel += (xaccel - state.xaccel) * alpha;
855 state.yaccel += (yaccel - state.yaccel) * alpha;
856 }
857 state.xvel += (state.xaccel * dt) * alpha;
858 state.yvel += (state.yaccel * dt) * alpha;
859 }
860 }
861 state.xpos = xpos;
862 state.ypos = ypos;
863}
864
865void IntegratingVelocityTrackerStrategy::populateEstimator(const State& state,
866 VelocityTracker::Estimator* outEstimator) const {
867 outEstimator->time = state.updateTime;
868 outEstimator->confidence = 1.0f;
869 outEstimator->degree = state.degree;
870 outEstimator->xCoeff[0] = state.xpos;
871 outEstimator->xCoeff[1] = state.xvel;
872 outEstimator->xCoeff[2] = state.xaccel / 2;
873 outEstimator->yCoeff[0] = state.ypos;
874 outEstimator->yCoeff[1] = state.yvel;
875 outEstimator->yCoeff[2] = state.yaccel / 2;
876}
877
878
879// --- LegacyVelocityTrackerStrategy ---
880
Jeff Brown5912f952013-07-01 19:10:31 -0700881LegacyVelocityTrackerStrategy::LegacyVelocityTrackerStrategy() {
882 clear();
883}
884
885LegacyVelocityTrackerStrategy::~LegacyVelocityTrackerStrategy() {
886}
887
888void LegacyVelocityTrackerStrategy::clear() {
889 mIndex = 0;
890 mMovements[0].idBits.clear();
891}
892
893void LegacyVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
894 BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
895 mMovements[mIndex].idBits = remainingIdBits;
896}
897
Siarhei Vishniakouae0f9902020-09-14 19:23:31 -0500898void LegacyVelocityTrackerStrategy::addMovement(
899 nsecs_t eventTime, BitSet32 idBits,
900 const std::vector<VelocityTracker::Position>& positions) {
Jeff Brown5912f952013-07-01 19:10:31 -0700901 if (++mIndex == HISTORY_SIZE) {
902 mIndex = 0;
903 }
904
905 Movement& movement = mMovements[mIndex];
906 movement.eventTime = eventTime;
907 movement.idBits = idBits;
908 uint32_t count = idBits.count();
909 for (uint32_t i = 0; i < count; i++) {
910 movement.positions[i] = positions[i];
911 }
912}
913
914bool LegacyVelocityTrackerStrategy::getEstimator(uint32_t id,
915 VelocityTracker::Estimator* outEstimator) const {
916 outEstimator->clear();
917
918 const Movement& newestMovement = mMovements[mIndex];
919 if (!newestMovement.idBits.hasBit(id)) {
920 return false; // no data
921 }
922
923 // Find the oldest sample that contains the pointer and that is not older than HORIZON.
924 nsecs_t minTime = newestMovement.eventTime - HORIZON;
925 uint32_t oldestIndex = mIndex;
926 uint32_t numTouches = 1;
927 do {
928 uint32_t nextOldestIndex = (oldestIndex == 0 ? HISTORY_SIZE : oldestIndex) - 1;
929 const Movement& nextOldestMovement = mMovements[nextOldestIndex];
930 if (!nextOldestMovement.idBits.hasBit(id)
931 || nextOldestMovement.eventTime < minTime) {
932 break;
933 }
934 oldestIndex = nextOldestIndex;
935 } while (++numTouches < HISTORY_SIZE);
936
937 // Calculate an exponentially weighted moving average of the velocity estimate
938 // at different points in time measured relative to the oldest sample.
939 // This is essentially an IIR filter. Newer samples are weighted more heavily
940 // than older samples. Samples at equal time points are weighted more or less
941 // equally.
942 //
943 // One tricky problem is that the sample data may be poorly conditioned.
944 // Sometimes samples arrive very close together in time which can cause us to
945 // overestimate the velocity at that time point. Most samples might be measured
946 // 16ms apart but some consecutive samples could be only 0.5sm apart because
947 // the hardware or driver reports them irregularly or in bursts.
948 float accumVx = 0;
949 float accumVy = 0;
950 uint32_t index = oldestIndex;
951 uint32_t samplesUsed = 0;
952 const Movement& oldestMovement = mMovements[oldestIndex];
953 const VelocityTracker::Position& oldestPosition = oldestMovement.getPosition(id);
954 nsecs_t lastDuration = 0;
955
956 while (numTouches-- > 1) {
957 if (++index == HISTORY_SIZE) {
958 index = 0;
959 }
960 const Movement& movement = mMovements[index];
961 nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
962
963 // If the duration between samples is small, we may significantly overestimate
964 // the velocity. Consequently, we impose a minimum duration constraint on the
965 // samples that we include in the calculation.
966 if (duration >= MIN_DURATION) {
967 const VelocityTracker::Position& position = movement.getPosition(id);
968 float scale = 1000000000.0f / duration; // one over time delta in seconds
969 float vx = (position.x - oldestPosition.x) * scale;
970 float vy = (position.y - oldestPosition.y) * scale;
971 accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration);
972 accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration);
973 lastDuration = duration;
974 samplesUsed += 1;
975 }
976 }
977
978 // Report velocity.
979 const VelocityTracker::Position& newestPosition = newestMovement.getPosition(id);
980 outEstimator->time = newestMovement.eventTime;
981 outEstimator->confidence = 1;
982 outEstimator->xCoeff[0] = newestPosition.x;
983 outEstimator->yCoeff[0] = newestPosition.y;
984 if (samplesUsed) {
985 outEstimator->xCoeff[1] = accumVx;
986 outEstimator->yCoeff[1] = accumVy;
987 outEstimator->degree = 1;
988 } else {
989 outEstimator->degree = 0;
990 }
991 return true;
992}
993
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +0100994// --- ImpulseVelocityTrackerStrategy ---
995
996ImpulseVelocityTrackerStrategy::ImpulseVelocityTrackerStrategy() {
997 clear();
998}
999
1000ImpulseVelocityTrackerStrategy::~ImpulseVelocityTrackerStrategy() {
1001}
1002
1003void ImpulseVelocityTrackerStrategy::clear() {
1004 mIndex = 0;
1005 mMovements[0].idBits.clear();
1006}
1007
1008void ImpulseVelocityTrackerStrategy::clearPointers(BitSet32 idBits) {
1009 BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
1010 mMovements[mIndex].idBits = remainingIdBits;
1011}
1012
Siarhei Vishniakouae0f9902020-09-14 19:23:31 -05001013void ImpulseVelocityTrackerStrategy::addMovement(
1014 nsecs_t eventTime, BitSet32 idBits,
1015 const std::vector<VelocityTracker::Position>& positions) {
Siarhei Vishniakou346ac6a2019-04-10 09:58:05 -07001016 if (mMovements[mIndex].eventTime != eventTime) {
1017 // When ACTION_POINTER_DOWN happens, we will first receive ACTION_MOVE with the coordinates
1018 // of the existing pointers, and then ACTION_POINTER_DOWN with the coordinates that include
1019 // the new pointer. If the eventtimes for both events are identical, just update the data
1020 // for this time.
1021 // We only compare against the last value, as it is likely that addMovement is called
1022 // in chronological order as events occur.
1023 mIndex++;
1024 }
1025 if (mIndex == HISTORY_SIZE) {
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001026 mIndex = 0;
1027 }
1028
1029 Movement& movement = mMovements[mIndex];
1030 movement.eventTime = eventTime;
1031 movement.idBits = idBits;
1032 uint32_t count = idBits.count();
1033 for (uint32_t i = 0; i < count; i++) {
1034 movement.positions[i] = positions[i];
1035 }
1036}
1037
1038/**
1039 * Calculate the total impulse provided to the screen and the resulting velocity.
1040 *
1041 * The touchscreen is modeled as a physical object.
1042 * Initial condition is discussed below, but for now suppose that v(t=0) = 0
1043 *
1044 * The kinetic energy of the object at the release is E=0.5*m*v^2
1045 * Then vfinal = sqrt(2E/m). The goal is to calculate E.
1046 *
1047 * The kinetic energy at the release is equal to the total work done on the object by the finger.
1048 * The total work W is the sum of all dW along the path.
1049 *
1050 * dW = F*dx, where dx is the piece of path traveled.
1051 * Force is change of momentum over time, F = dp/dt = m dv/dt.
1052 * Then substituting:
1053 * dW = m (dv/dt) * dx = m * v * dv
1054 *
1055 * Summing along the path, we get:
1056 * W = sum(dW) = sum(m * v * dv) = m * sum(v * dv)
1057 * Since the mass stays constant, the equation for final velocity is:
1058 * vfinal = sqrt(2*sum(v * dv))
1059 *
1060 * Here,
1061 * dv : change of velocity = (v[i+1]-v[i])
1062 * dx : change of distance = (x[i+1]-x[i])
1063 * dt : change of time = (t[i+1]-t[i])
1064 * v : instantaneous velocity = dx/dt
1065 *
1066 * The final formula is:
1067 * vfinal = sqrt(2) * sqrt(sum((v[i]-v[i-1])*|v[i]|)) for all i
1068 * The absolute value is needed to properly account for the sign. If the velocity over a
1069 * particular segment descreases, then this indicates braking, which means that negative
1070 * work was done. So for two positive, but decreasing, velocities, this contribution would be
1071 * negative and will cause a smaller final velocity.
1072 *
1073 * Initial condition
1074 * There are two ways to deal with initial condition:
1075 * 1) Assume that v(0) = 0, which would mean that the screen is initially at rest.
1076 * This is not entirely accurate. We are only taking the past X ms of touch data, where X is
1077 * currently equal to 100. However, a touch event that created a fling probably lasted for longer
1078 * than that, which would mean that the user has already been interacting with the touchscreen
1079 * and it has probably already been moving.
1080 * 2) Assume that the touchscreen has already been moving at a certain velocity, calculate this
1081 * initial velocity and the equivalent energy, and start with this initial energy.
1082 * Consider an example where we have the following data, consisting of 3 points:
1083 * time: t0, t1, t2
1084 * x : x0, x1, x2
1085 * v : 0 , v1, v2
1086 * Here is what will happen in each of these scenarios:
1087 * 1) By directly applying the formula above with the v(0) = 0 boundary condition, we will get
1088 * vfinal = sqrt(2*(|v1|*(v1-v0) + |v2|*(v2-v1))). This can be simplified since v0=0
1089 * vfinal = sqrt(2*(|v1|*v1 + |v2|*(v2-v1))) = sqrt(2*(v1^2 + |v2|*(v2 - v1)))
1090 * since velocity is a real number
1091 * 2) If we treat the screen as already moving, then it must already have an energy (per mass)
1092 * equal to 1/2*v1^2. Then the initial energy should be 1/2*v1*2, and only the second segment
1093 * will contribute to the total kinetic energy (since we can effectively consider that v0=v1).
1094 * This will give the following expression for the final velocity:
1095 * vfinal = sqrt(2*(1/2*v1^2 + |v2|*(v2-v1)))
1096 * This analysis can be generalized to an arbitrary number of samples.
1097 *
1098 *
1099 * Comparing the two equations above, we see that the only mathematical difference
1100 * is the factor of 1/2 in front of the first velocity term.
1101 * This boundary condition would allow for the "proper" calculation of the case when all of the
1102 * samples are equally spaced in time and distance, which should suggest a constant velocity.
1103 *
1104 * Note that approach 2) is sensitive to the proper ordering of the data in time, since
1105 * the boundary condition must be applied to the oldest sample to be accurate.
1106 */
Siarhei Vishniakou97b5e182017-09-01 13:52:33 -07001107static float kineticEnergyToVelocity(float work) {
1108 static constexpr float sqrt2 = 1.41421356237;
1109 return (work < 0 ? -1.0 : 1.0) * sqrtf(fabsf(work)) * sqrt2;
1110}
1111
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001112static float calculateImpulseVelocity(const nsecs_t* t, const float* x, size_t count) {
1113 // The input should be in reversed time order (most recent sample at index i=0)
1114 // t[i] is in nanoseconds, but due to FP arithmetic, convert to seconds inside this function
Siarhei Vishniakou6de8f5e2018-03-02 18:48:15 -08001115 static constexpr float SECONDS_PER_NANO = 1E-9;
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001116
1117 if (count < 2) {
1118 return 0; // if 0 or 1 points, velocity is zero
1119 }
1120 if (t[1] > t[0]) { // Algorithm will still work, but not perfectly
1121 ALOGE("Samples provided to calculateImpulseVelocity in the wrong order");
1122 }
1123 if (count == 2) { // if 2 points, basic linear calculation
1124 if (t[1] == t[0]) {
1125 ALOGE("Events have identical time stamps t=%" PRId64 ", setting velocity = 0", t[0]);
1126 return 0;
1127 }
Siarhei Vishniakou6de8f5e2018-03-02 18:48:15 -08001128 return (x[1] - x[0]) / (SECONDS_PER_NANO * (t[1] - t[0]));
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001129 }
1130 // Guaranteed to have at least 3 points here
1131 float work = 0;
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001132 for (size_t i = count - 1; i > 0 ; i--) { // start with the oldest sample and go forward in time
1133 if (t[i] == t[i-1]) {
1134 ALOGE("Events have identical time stamps t=%" PRId64 ", skipping sample", t[i]);
1135 continue;
1136 }
Siarhei Vishniakou97b5e182017-09-01 13:52:33 -07001137 float vprev = kineticEnergyToVelocity(work); // v[i-1]
Siarhei Vishniakou6de8f5e2018-03-02 18:48:15 -08001138 float vcurr = (x[i] - x[i-1]) / (SECONDS_PER_NANO * (t[i] - t[i-1])); // v[i]
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001139 work += (vcurr - vprev) * fabsf(vcurr);
1140 if (i == count - 1) {
1141 work *= 0.5; // initial condition, case 2) above
1142 }
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001143 }
Siarhei Vishniakou97b5e182017-09-01 13:52:33 -07001144 return kineticEnergyToVelocity(work);
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001145}
1146
1147bool ImpulseVelocityTrackerStrategy::getEstimator(uint32_t id,
1148 VelocityTracker::Estimator* outEstimator) const {
1149 outEstimator->clear();
1150
1151 // Iterate over movement samples in reverse time order and collect samples.
1152 float x[HISTORY_SIZE];
1153 float y[HISTORY_SIZE];
1154 nsecs_t time[HISTORY_SIZE];
1155 size_t m = 0; // number of points that will be used for fitting
1156 size_t index = mIndex;
1157 const Movement& newestMovement = mMovements[mIndex];
1158 do {
1159 const Movement& movement = mMovements[index];
1160 if (!movement.idBits.hasBit(id)) {
1161 break;
1162 }
1163
1164 nsecs_t age = newestMovement.eventTime - movement.eventTime;
1165 if (age > HORIZON) {
1166 break;
1167 }
1168
1169 const VelocityTracker::Position& position = movement.getPosition(id);
1170 x[m] = position.x;
1171 y[m] = position.y;
1172 time[m] = movement.eventTime;
1173 index = (index == 0 ? HISTORY_SIZE : index) - 1;
1174 } while (++m < HISTORY_SIZE);
1175
1176 if (m == 0) {
1177 return false; // no data
1178 }
1179 outEstimator->xCoeff[0] = 0;
1180 outEstimator->yCoeff[0] = 0;
1181 outEstimator->xCoeff[1] = calculateImpulseVelocity(time, x, m);
1182 outEstimator->yCoeff[1] = calculateImpulseVelocity(time, y, m);
1183 outEstimator->xCoeff[2] = 0;
1184 outEstimator->yCoeff[2] = 0;
1185 outEstimator->time = newestMovement.eventTime;
1186 outEstimator->degree = 2; // similar results to 2nd degree fit
1187 outEstimator->confidence = 1;
Siarhei Vishniakou21fcc2b2022-06-17 21:29:02 +00001188 if (DEBUG_STRATEGY) {
1189 ALOGD("velocity: (%.1f, %.1f)", outEstimator->xCoeff[1], outEstimator->yCoeff[1]);
1190 }
Siarhei Vishniakou276467b2022-03-17 09:43:28 -07001191 if (DEBUG_IMPULSE) {
1192 // TODO(b/134179997): delete this block once the switch to 'impulse' is complete.
1193 // Calculate the lsq2 velocity for the same inputs to allow runtime comparisons
1194 VelocityTracker lsq2(VelocityTracker::Strategy::LSQ2);
1195 BitSet32 idBits;
1196 const uint32_t pointerId = 0;
1197 idBits.markBit(pointerId);
1198 for (ssize_t i = m - 1; i >= 0; i--) {
1199 lsq2.addMovement(time[i], idBits, {{x[i], y[i]}});
1200 }
1201 float outVx = 0, outVy = 0;
1202 const bool computed = lsq2.getVelocity(pointerId, &outVx, &outVy);
1203 if (computed) {
1204 ALOGD("lsq2 velocity: (%.1f, %.1f)", outVx, outVy);
1205 } else {
1206 ALOGD("lsq2 velocity: could not compute velocity");
1207 }
Siarhei Vishniakoue37bcec2021-09-28 14:24:32 -07001208 }
Siarhei Vishniakou00a4ea92017-06-08 21:43:20 +01001209 return true;
1210}
1211
Jeff Brown5912f952013-07-01 19:10:31 -07001212} // namespace android