emilib
movement_tracker.hpp
1 // By Emil Ernerfeldt 2012-2016
2 // LICENSE:
3 // This software is dual-licensed to the public domain and under the following
4 // license: you are granted a perpetual, irrevocable license to copy, modify,
5 // publish, and distribute this file as you see fit.
6 // HISTORY:
7 // Created in 2012-11-11
8 
9 #pragma once
10 
11 #include <deque>
12 #include <utility>
13 #include <vector>
14 
15 #include <loguru.hpp>
16 
17 namespace emilib {
18 
21 template<typename T>
23 {
24 protected:
25  struct TimePosPair
26  {
27  double when;
28  T where;
29  };
30  using TimePosList = std::deque<TimePosPair>;
31 
32 public:
33  using value_type = T;
34 
35  MovementTracker() { }
36  virtual ~MovementTracker() { }
37 
38  // ------------------------------------------------
39 
40  void clear()
41  {
42  _list.clear();
43  _has_start = false;
44  }
45 
46  void add(const T& pos, double time)
47  {
48  if (!_has_start) {
49  _start = TimePosPair{time, pos};
50  _has_start = true;
51  }
52 
53  _list.push_back(TimePosPair{time, pos});
54 
55  flush(time);
56  }
57 
58  std::vector<T> points() const
59  {
60  std::vector<T> points;
61  for (auto&& p : _list) {
62  points.push_back(p.where);
63  }
64  return points;
65  }
66 
67  bool empty() const { return _list.empty(); }
68  size_t size() const { return _list.size(); }
69 
70  double start_time() const
71  {
72  CHECK_F(_has_start);
73  return _start.when;
74  }
75 
76  T start_pos() const
77  {
78  CHECK_F(_has_start);
79  return _start.where;
80  }
81 
82  double latest_time() const
83  {
84  CHECK_F(!empty());
85  return _list.back().when;
86  }
87 
88  T latest_pos() const
89  {
90  CHECK_F(!empty());
91  return _list.back().where;
92  }
93 
95  T rel() const
96  {
97  CHECK_GE_F(size(), 2);
98  return _list[_list.size()-1].where - _list[_list.size()-2].where;
99  }
100 
101  double duration() const
102  {
103  CHECK_F(!empty());
104  return latest_time() - start_time();
105  }
106 
109  virtual T velocity(double now) const
110  {
111  size_t begin;
112  if (!velocity_calc_begin(begin, now)) {
113  return T();
114  }
115 
116  double dt = _list.back().when - _list[begin].when;
117 
118  if (dt <= 0) {
119  return T();
120  }
121 
122  T dx = _list.back().where - _list[begin].where;
123 
124  return dx / (float)dt;
125  }
126 
127  T velocity() const
128  {
129  return velocity(latest_time());
130  }
131 
133  template<typename F>
134  bool is_still(F max_dist, double duration) const
135  {
136  CHECK_F(!empty());
137  const double now = latest_time(); // well.. whatever
138 
139  for (size_t i=0; i<_list.size(); ++i) {
140  if (now - _list[i].when < duration) {
141  if (distance(_list[i].where, _list.back().where) > max_dist) {
142  return false;
143  }
144  }
145  }
146 
147  return true;
148  }
149 
150  // ------------------------------------------------
151 
153  void flush(double now)
154  {
155  while (!_list.empty() && _list.front().when < now - _max_history_time) {
156  _list.pop_front();
157  }
158  }
159 
160 protected:
162  bool velocity_calc_begin(size_t& out_index, double now) const
163  {
164  if (_list.size() < 2) {
165  return false; // Not enough data
166  }
167 
168  auto duration = now - start_time();
169 
170  if (duration < min_velocity_time()) {
171  return false; // Not enough data
172  }
173 
174  double vel_time = velocity_time();
175 
176  for (size_t i=0; i<_list.size()-1; ++i) {
177  if (now - _list[i].when < vel_time) {
178  if (_list.size() - i < min_velocity_samples()) {
179  return false; // Too few samples
180  }
181 
182  out_index = i;
183  return true;
184  }
185  }
186 
187  return false;
188  }
189 
191  static constexpr size_t min_velocity_samples()
192  {
193  return 3;
194  }
195 
197  static constexpr double min_velocity_time()
198  {
199  return 0.01f;
200  }
201 
203  static constexpr double velocity_time()
204  {
205  return 0.1f;
206  }
207 
208  // ------------------------------------------------
209 
210  bool _has_start = false;
211  TimePosPair _start; // Since it can be pruned from _list
212  TimePosList _list;
213  double _max_history_time = 10; // Don't keep points older than this
214 };
215 
216 // ------------------------------------------------
217 
219 class RotationTracker : public MovementTracker<float>
220 {
222 public:
223  virtual float velocity(double now) const override;
224 };
225 
226 // ------------------------------------------------
227 
228 // Example:
229 // using PositionTracker = MovementTracker<Vec2>;
230 
231 } // namespace emilib
Definition: movement_tracker.hpp:22
static constexpr double velocity_time()
The time over which we calculate velocity.
Definition: movement_tracker.hpp:203
Definition: movement_tracker.hpp:25
static constexpr size_t min_velocity_samples()
The minimum number of samples for there to be any velocity calculated.
Definition: movement_tracker.hpp:191
void flush(double now)
Flush out oldes entries.
Definition: movement_tracker.hpp:153
T rel() const
Last movement delta.
Definition: movement_tracker.hpp:95
bool is_still(F max_dist, double duration) const
Has all movement been within "max_dist" radius, during the last "duration" seconds?
Definition: movement_tracker.hpp:134
Made to take into account the cyclic nature of angles (in radians, btw)
Definition: movement_tracker.hpp:219
virtual T velocity(double now) const
Definition: movement_tracker.hpp:109
static constexpr double min_velocity_time()
Minimum time before we have a good velocity.
Definition: movement_tracker.hpp:197
Definition: coroutine.hpp:18
bool velocity_calc_begin(size_t &out_index, double now) const
From where shall we calculate velocity? Return false on "not at all".
Definition: movement_tracker.hpp:162