@@ -22,6 +22,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2222#include " flickable.hpp"
2323
2424#include < utki/time.hpp>
25+ #include < utki/views.hpp>
2526
2627using namespace ruis ::touch;
2728
@@ -185,17 +186,26 @@ void flickable::update(uint32_t dt_ms)
185186 // TODO:
186187}
187188
189+ namespace {
190+ constexpr auto max_history_records = 10 ;
191+ }
192+
188193void flickable::push_touch_move_to_history (touch_move_info tm){
189- constexpr auto max_history_records = 10 ;
190194 constexpr auto max_history_age_ms = 200 ;
191195 constexpr auto min_time_between_points_ms = 1 ;
192196
197+ // drop too old records
198+ while (!this ->touch_history .empty () && tm.timestamp_ms - this ->touch_history .front ().timestamp_ms > max_history_age_ms){
199+ this ->touch_history .pop_front ();
200+ }
201+
193202 if (this ->touch_history .empty ()){
194203 this ->touch_history .push_back (std::move (tm));
195204 return ;
196205 }
197206
198207 utki::assert (!this ->touch_history .empty (), SL);
208+
199209 auto & last_record = this ->touch_history .back ();
200210
201211 if (tm.timestamp_ms - last_record.timestamp_ms <= min_time_between_points_ms){
@@ -213,7 +223,7 @@ void flickable::push_touch_move_to_history(touch_move_info tm){
213223
214224ruis::vec2 flickable::calculate_touch_velocity (){
215225 if (this ->touch_history .size () < 2 ){
216- std::cout << " flickable::calculate_touch_velocity(): return 0. this->touch_history.size() = " << this ->touch_history .size () << std::endl;
226+ // std::cout << "flickable::calculate_touch_velocity(): return 0. this->touch_history.size() = " << this->touch_history.size() << std::endl;
217227 return {0 };
218228 }
219229
@@ -228,13 +238,94 @@ ruis::vec2 flickable::calculate_touch_velocity(){
228238
229239 auto vel = dp / ruis::real (dt_ms);
230240
231- std::cout << " flickable::calculate_touch_velocity(): return " << vel << " . this->touch_history.size() = " << this ->touch_history .size () << std::endl;
241+ // std::cout << "flickable::calculate_touch_velocity(): return " << vel << ". this->touch_history.size() = " << this->touch_history.size() << std::endl;
232242 return vel;
233243 }
234244
235- // TODO:
236- ruis::vec2 vel = 0 ;
245+ // use Ordinary Least Squares method to fit a quadratic curve to the points of touch history
246+ ruis::vec2 vel = calculate_touch_velocity_for_at_least_3_points_using_ols_method () ;
237247
238- std::cout << " flickable::calculate_touch_velocity(): return " << vel << " . this->touch_history.size() = " << this ->touch_history .size () << std::endl;
248+ // std::cout << "flickable::calculate_touch_velocity(): return " << vel << ". this->touch_history.size() = " << this->touch_history.size() << std::endl;
239249 return vel;
240250}
251+
252+ ruis::vec2 flickable::calculate_touch_velocity_for_at_least_3_points_using_ols_method (){
253+ // Ordinary Least Squares method fits quadratic curve y(t)=a*t^2+b*t+c to a set of n >= 3 points.
254+ // Coefficients a, b and c can be found by solving the following system of linear equations
255+ //
256+ // / sum(t^4) sum(t^3) sum(t^2) \ / a \ / sum(y * t^2) \
257+ // | sum(t^3) sum(t^2) sum(t) | * | b | = | sum(y * t) |
258+ // \ sum(t^2) sum(t) n / \ c / \ sum(y) /
259+ //
260+ // The touch velocity is effectively v = dy/dt = 2*a*t + b
261+ //
262+ // If we bias t foe each point to the last point, i.e. last point will be at t=0 and previous
263+ // points will be at some negative times, then v(0) = b.
264+ // Effecively the touch velocity is the b coefficient.
265+
266+ // need at least 3 points for quadratic curve fitting
267+ utki::assert (this ->touch_history .size () >= 3 , SL);
268+
269+ uint32_t time_bias_ms = this ->touch_history .back ().timestamp_ms ;
270+
271+ ruis::real time_sum = 0 ;
272+ ruis::real time_pow2_sum = 0 ;
273+ ruis::real time_pow3_sum = 0 ;
274+ ruis::real time_pow4_sum = 0 ;
275+ ruis::vec2 pos_time_pow2_sum = 0 ;
276+ ruis::vec2 pos_time_sum = 0 ;
277+ ruis::vec2 pos_sum = 0 ;
278+
279+ for (const auto & rec : this ->touch_history ){
280+ // Here we still do arithmetics in uint32_t space.
281+ // The time_bias_ms is the latest timestamp, so subtract from it to get positive biased time.
282+ uint32_t shifted_time = time_bias_ms - rec.timestamp_ms ;
283+
284+ // since we bias to lates timestamp, the actual biased time should be negative
285+ ruis::real t = -ruis::real (shifted_time);
286+
287+ ruis::real t_pow2 = t * t;
288+ ruis::real t_pow3 = t_pow2 * t;
289+ ruis::real t_pow4 = t_pow3 * t;
290+
291+ // std::cout << "shifted time = " << t << std::endl;
292+
293+ time_sum += t;
294+ time_pow2_sum += t_pow2;
295+ time_pow3_sum += t_pow3;
296+ time_pow4_sum += t_pow4;
297+
298+ pos_sum += rec.position ;
299+ pos_time_sum += rec.position * t;
300+ pos_time_pow2_sum += rec.position * t_pow2;
301+ }
302+
303+ // clang-format off
304+ ruis::mat3 m{
305+ {time_pow4_sum, time_pow3_sum, time_pow2_sum},
306+ {time_pow3_sum, time_pow2_sum, time_sum},
307+ {time_pow2_sum, time_sum, this ->touch_history .size ()}
308+ };
309+ // clang-format on
310+
311+ auto det = m.det ();
312+
313+ constexpr auto epsilon = ruis::real (1e-9 );
314+
315+ using std::abs;
316+ if (abs (det) < epsilon){
317+ // the matrix is not invertible
318+ return {0 };
319+ }
320+
321+ // replace 2nd column with right-hand side vector and calculate determinant
322+ ruis::vec2 det_b;
323+ for (auto [p0, p1, p2, out] : utki::views::zip (pos_time_pow2_sum, pos_time_sum, pos_sum, det_b)){
324+ m[0 ][1 ] = p0;
325+ m[1 ][1 ] = p1;
326+ m[2 ][1 ] = p2;
327+ out = m.det ();
328+ }
329+
330+ return det_b.comp_div (det);
331+ }
0 commit comments