@@ -46,9 +46,9 @@ private const val MinSampleSize: Int = 3
46
46
*/
47
47
class VelocityTracker {
48
48
49
- // Circular buffer; current sample at index.
50
- private val samples : Array < PointAtTime ?> = Array ( HistorySize ) { null }
51
- private var index : Int = 0
49
+ private val xVelocityTracker = VelocityTracker1D ()
50
+ private val yVelocityTracker = VelocityTracker1D ()
51
+
52
52
internal var currentPointerPositionAccumulator = Offset .Zero
53
53
54
54
/* *
@@ -62,8 +62,8 @@ class VelocityTracker {
62
62
// positions. For velocity tracking, the only thing that is important is the change in
63
63
// position over time.
64
64
fun addPosition (timeMillis : Long , position : Offset ) {
65
- index = (index + 1 ) % HistorySize
66
- samples[index] = PointAtTime (position, timeMillis )
65
+ xVelocityTracker.addDataPoint(timeMillis, position.x)
66
+ yVelocityTracker.addDataPoint(timeMillis, position.y )
67
67
}
68
68
69
69
/* *
@@ -72,42 +72,85 @@ class VelocityTracker {
72
72
* This can be expensive. Only call this when you need the velocity.
73
73
*/
74
74
fun calculateVelocity (): Velocity {
75
- val estimate = getVelocityEstimate().pixelsPerSecond
76
- return Velocity (estimate.x, estimate.y)
75
+ return Velocity (xVelocityTracker.calculateVelocity(), yVelocityTracker.calculateVelocity())
77
76
}
78
77
79
78
/* *
80
79
* Clears the tracked positions added by [addPosition].
81
80
*/
81
+ fun resetTracking () {
82
+ xVelocityTracker.resetTracking()
83
+ yVelocityTracker.resetTracking()
84
+ }
85
+ }
86
+
87
+ /* *
88
+ * A velocity tracker calculating velocity in 1 dimension.
89
+ *
90
+ * Add displacement data points using [addDataPoint], and obtain velocity using [calculateVelocity].
91
+ */
92
+ private class VelocityTracker1D {
93
+ // Circular buffer; current sample at index.
94
+ private val samples: Array <DataPointAtTime ?> = Array (HistorySize ) { null }
95
+ private var index: Int = 0
96
+
97
+ /* *
98
+ * Adds a data point for velocity calculation. A data point should represent a position along
99
+ * the tracked axis at a given time, [timeMillis].
100
+ *
101
+ * Use the same units for the data points provided. For example, having some data points in `cm`
102
+ * and some in `m` will result in incorrect velocity calculations, as this method (and the
103
+ * tracker) has no knowledge of the units used.
104
+ */
105
+ fun addDataPoint (timeMillis : Long , dataPoint : Float ) {
106
+ index = (index + 1 ) % HistorySize
107
+ samples[index] = DataPointAtTime (dataPoint, timeMillis)
108
+ }
109
+
110
+ /* *
111
+ * Computes the estimated velocity at the time of the last provided data point. The units of
112
+ * velocity will be `units/second`, where `units` is the units of the data points provided via
113
+ * [addDataPoint].
114
+ *
115
+ * This can be expensive. Only call this when you need the velocity.
116
+ */
117
+ fun calculateVelocity (): Float {
118
+ return getVelocityEstimate().velocity
119
+ }
120
+
121
+ /* *
122
+ * Clears the tracked positions added by [addDataPoint].
123
+ */
82
124
fun resetTracking () {
83
125
samples.fill(element = null )
126
+ index = 0
84
127
}
85
128
86
129
/* *
87
130
* Returns an estimate of the velocity of the object being tracked by the
88
131
* tracker given the current information available to the tracker.
89
132
*
90
- * Information is added using [addPosition ].
133
+ * Information is added using [addDataPoint ].
91
134
*
92
- * Returns null if there is no data on which to base an estimate.
135
+ * Returns an estimate of 0 velocity if there is no data on which to base an estimate.
93
136
*/
137
+
94
138
private fun getVelocityEstimate (): VelocityEstimate {
95
- val x: MutableList <Float > = mutableListOf ()
96
- val y: MutableList <Float > = mutableListOf ()
139
+ val dataPoints: MutableList <Float > = mutableListOf ()
97
140
val time: MutableList <Float > = mutableListOf ()
98
141
var sampleCount = 0
99
142
var index: Int = index
100
143
101
144
// The sample at index is our newest sample. If it is null, we have no samples so return.
102
- val newestSample: PointAtTime = samples[index] ? : return VelocityEstimate .None
145
+ val newestSample: DataPointAtTime = samples[index] ? : return VelocityEstimate .None
103
146
104
- var previousSample: PointAtTime = newestSample
105
- var oldestSample: PointAtTime = newestSample
147
+ var previousSample: DataPointAtTime = newestSample
148
+ var oldestSample: DataPointAtTime = newestSample
106
149
107
150
// Starting with the most recent PointAtTime sample, iterate backwards while
108
151
// the samples represent continuous motion.
109
152
do {
110
- val sample: PointAtTime = samples[index] ? : break
153
+ val sample: DataPointAtTime = samples[index] ? : break
111
154
112
155
val age: Float = (newestSample.time - sample.time).toFloat()
113
156
val delta: Float =
@@ -118,9 +161,7 @@ class VelocityTracker {
118
161
}
119
162
120
163
oldestSample = sample
121
- val position: Offset = sample.point
122
- x.add(position.x)
123
- y.add(position.y)
164
+ dataPoints.add(sample.dataPoint)
124
165
time.add(- age)
125
166
index = (if (index == 0 ) HistorySize else index) - 1
126
167
@@ -129,23 +170,17 @@ class VelocityTracker {
129
170
130
171
if (sampleCount >= MinSampleSize ) {
131
172
try {
132
- val xFit: PolynomialFit = polyFitLeastSquares(time, x, 2 )
133
- val yFit: PolynomialFit = polyFitLeastSquares(time, y, 2 )
134
-
173
+ val fit = polyFitLeastSquares(time, dataPoints, 2 )
135
174
// The 2nd coefficient is the derivative of the quadratic polynomial at
136
175
// x = 0, and that happens to be the last timestamp that we end up
137
- // passing to polyFitLeastSquares for both x and y.
138
- val xSlope = xFit.coefficients[1 ]
139
- val ySlope = yFit.coefficients[1 ]
176
+ // passing to polyFitLeastSquares.
177
+ val slope = fit.coefficients[1 ]
140
178
return VelocityEstimate (
141
- pixelsPerSecond = Offset (
142
- // Convert from pixels/ms to pixels/s
143
- (xSlope * 1000 ),
144
- (ySlope * 1000 )
145
- ),
146
- confidence = xFit.confidence * yFit.confidence,
179
+ // Convert from units/ms to units/s
180
+ velocity = slope * 1000 ,
181
+ confidence = fit.confidence,
147
182
durationMillis = newestSample.time - oldestSample.time,
148
- offset = newestSample.point - oldestSample.point
183
+ offset = newestSample.dataPoint - oldestSample.dataPoint
149
184
)
150
185
} catch (exception: IllegalArgumentException ) {
151
186
// TODO(b/129494918): Is catching an exception here something we really want to do?
@@ -156,10 +191,10 @@ class VelocityTracker {
156
191
// We're unable to make a velocity estimate but we did have at least one
157
192
// valid pointer position.
158
193
return VelocityEstimate (
159
- pixelsPerSecond = Offset . Zero ,
194
+ velocity = 0f ,
160
195
confidence = 1.0f ,
161
196
durationMillis = newestSample.time - oldestSample.time,
162
- offset = newestSample.point - oldestSample.point
197
+ offset = newestSample.dataPoint - oldestSample.dataPoint
163
198
)
164
199
}
165
200
}
@@ -214,26 +249,23 @@ fun VelocityTracker.addPointerInputChange(event: PointerInputChange) {
214
249
addPosition(event.uptimeMillis, currentPointerPositionAccumulator)
215
250
}
216
251
217
- private data class PointAtTime (val point : Offset , val time : Long )
252
+ private data class DataPointAtTime (val dataPoint : Float , val time : Long )
218
253
219
254
/* *
220
- * A two dimensional velocity estimate.
255
+ * A velocity estimate.
221
256
*
222
- * VelocityEstimates are computed by [VelocityTracker.getImpulseVelocity]. An
223
- * estimate's [confidence] measures how well the velocity tracker's position
257
+ * An estimate's [confidence] measures how well the velocity tracker's position
224
258
* data fit a straight line, [durationMillis] is the time that elapsed between the
225
259
* first and last position sample used to compute the velocity, and [offset]
226
260
* is similarly the difference between the first and last positions.
227
261
*
228
262
* See also:
229
263
*
230
264
* * VelocityTracker, which computes [VelocityEstimate]s.
231
- * * Velocity, which encapsulates (just) a velocity vector and provides some
232
- * useful velocity operations.
233
265
*/
234
266
private data class VelocityEstimate (
235
- /* * The number of pixels per second of velocity in the x and y directions . */
236
- val pixelsPerSecond : Offset ,
267
+ /* * The velocity, in units per second . */
268
+ val velocity : Float ,
237
269
/* *
238
270
* A value between 0.0 and 1.0 that indicates how well [VelocityTracker]
239
271
* was able to fit a straight line to its position data.
@@ -243,17 +275,17 @@ private data class VelocityEstimate(
243
275
val confidence : Float ,
244
276
/* *
245
277
* The time that elapsed between the first and last position sample used
246
- * to compute [pixelsPerSecond ].
278
+ * to compute [velocity ].
247
279
*/
248
280
val durationMillis : Long ,
249
281
/* *
250
- * The difference between the first and last position sample used
251
- * to compute [pixelsPerSecond ].
282
+ * The difference between the first and last datapoint used
283
+ * to compute [velocity ].
252
284
*/
253
- val offset : Offset
285
+ val offset : Float
254
286
) {
255
287
companion object {
256
- val None = VelocityEstimate (Offset . Zero , 1f , 0 , Offset . Zero )
288
+ val None = VelocityEstimate (0f , 1f , 0 , 0f )
257
289
}
258
290
}
259
291
0 commit comments