Track.h
Go to the documentation of this file.
1 #pragma once
2 
3 #include "VariantValue.h"
4 #include "interpolate/linear.h"
5 
6 namespace armarx::trajectory
7 {
8 
9  /**
10  * @brief A keyframe, representing a value at a given time.
11  */
12  template <typename ValueT>
13  struct Keyframe
14  {
15  /// Constructor.
16  Keyframe(float time, const ValueT& value) : time(time), value(value)
17  {
18  }
19 
20  float time; ///< The time on the timeline.
21  ValueT value; ///< The value.
22  };
23 
24  /// A keyframe with of type TValue.
26 
27  /**
28  * @brief A track represents the timeline of a single value, identified by a track ID.
29  * A track is comprised of a sequence of keyframes and used to call a
30  * single update function.
31  */
32  template <typename ValueT>
33  class Track
34  {
35  public:
36  /// The update function type.
37  using UpdateFunc = std::function<void(ValueT)>;
38  // Forward declaration.
39  struct KeyframeProxy;
40 
41  public:
42  /// Construct a track with given ID (and no update function).
43  Track(const TrackID& id) : id(id)
44  {
45  }
46 
47  /// Construct a track with given ID and update function.
48  Track(const TrackID& id, UpdateFunc updateFunc) : id(id), updateFunc(updateFunc)
49  {
50  }
51 
52  /// Indicate whether this track does not contain any keyframes.
53  bool empty() const;
54  /// Clear the track of all keyframes.
55  void clear();
56 
57  /// Add a keyframe to this track.
58  void addKeyframe(const Keyframe<ValueT>& keyframe);
59  /// Add a keyframe to this track.
60  void addKeyframe(float time, const ValueT& value);
61 
62  /// Add a keyframe by assignment: `track[time] = value;`
63  KeyframeProxy operator[](float time);
64 
65 
66  /**
67  * @brief Get the interpolated value at the given time.
68  * @throws `error::EmptyTrack` If the track is empty.
69  */
70  ValueT at(float time) const;
71 
72  /**
73  * @brief Call the update function with the interpolated value at the given time.
74  *
75  * @param ignoreIfEmpty If true and the track is empty, the method does nothing.
76  * @throws `error::EmptyTrack` If the track is empty and `ignoreIfEmpty` is false.
77  */
78  void update(float time, bool ignoreIfEmpty = false);
79 
80  template <typename V>
81  friend std::ostream& operator<<(std::ostream& os, const Track<V>& track);
82 
83 
84  private:
85  /// Sort the keyframes.
86  void sortKeyframes();
87 
88  /**
89  * @throw `error::WrongValueTypeInKeyframe` If the given value's type
90  * conflicts with contained keyframes.
91  */
92  void checkValueType(const ValueT& value);
93 
94 
95  /// The track ID.
96  TrackID id;
97  /// The update function.
98  UpdateFunc updateFunc;
99  /// The sorted array of keyframes.
100  std::vector<Keyframe<ValueT>> keyframes;
101 
102 
103  public:
104  /// A proxy allowing for adding keyframes by: `track[time] = value;`
106  {
107  public:
108  /// Get the value at time.
109  operator ValueT() const;
110  /// Add a keyframe on assignment.
111  void operator=(const ValueT& value);
112  /// Add a keyframe on assignment.
113  void operator=(const KeyframeProxy& value);
114 
115  /// Get the value at time.
116  ValueT value() const;
117 
118  private:
119  friend Track; // Allow TrackBase to use constructor.
120 
121  KeyframeProxy(Track& track, float time);
122 
123  Track& track;
124  float time;
125  };
126  };
127 
128  /// A track with value type TValue.
130 
131  template <typename V>
132  bool
134  {
135  return keyframes.empty();
136  }
137 
138  template <typename V>
139  void
141  {
142  keyframes.clear();
143  }
144 
145  template <typename V>
146  void
147  Track<V>::addKeyframe(const Keyframe<V>& keyframe)
148  {
149  checkValueType(keyframe.value);
150  keyframes.push_back(keyframe);
151  sortKeyframes();
152  }
153 
154  template <typename V>
155  void
156  Track<V>::addKeyframe(float time, const V& value)
157  {
158  addKeyframe(Keyframe<V>(time, value));
159  }
160 
161  template <typename V>
162  auto
164  {
165  return KeyframeProxy{*this, time};
166  }
167 
168  template <typename V>
169  void
171  {
172  std::sort(keyframes.begin(),
173  keyframes.end(),
174  [](const Keyframe<V>& lhs, const Keyframe<V>& rhs)
175  { return lhs.time < rhs.time; });
176  }
177 
178  /// In general, do nothing.
179  template <typename V>
180  void
181  Track<V>::checkValueType(const V&)
182  {
183  }
184 
185  /**
186  * If the track is not empty, check whether the given type of the given
187  * value is the same as the type of the values already in the track.
188  *
189  * @throws `error::WrongValueTypeInKeyframe` if the types do not match.
190  */
191  template <>
192  void Track<VariantValue>::checkValueType(const VariantValue& value);
193 
194  template <typename V>
195  V
196  Track<V>::at(float time) const
197  {
198  if (empty())
199  {
200  throw error::EmptyTrack(id);
201  }
202 
203  if (keyframes.size() == 1)
204  {
205  return keyframes.front().value;
206  }
207 
208  if (time <= keyframes.front().time)
209  {
210  return keyframes.front().value;
211  }
212  if (time >= keyframes.back().time)
213  {
214  return keyframes.back().value;
215  }
216 
217 
218  std::size_t i = 0;
219  while (i + 1 <= keyframes.size() && keyframes[i + 1].time < time)
220  {
221  i++;
222  }
223 
224  // interpolate between i and i+1
225  const Keyframe<V>& kf1 = keyframes.at(i);
226  const Keyframe<V>& kf2 = keyframes.at(i + 1);
227 
228  float t = (time - kf1.time) / (kf2.time - kf1.time);
229  // t = 0 => full kf1, t = 1 => full kf2
230 
231  return interpolate::linear<V>(t, kf1.value, kf2.value);
232  }
233 
234  template <typename V>
235  void
236  Track<V>::update(float time, bool ignoreIfEmpty)
237  {
238  if (updateFunc && !(ignoreIfEmpty && empty()))
239  {
240  updateFunc(at(time));
241  }
242  }
243 
244  template <typename V>
245  Track<V>::KeyframeProxy::KeyframeProxy(Track& track, float time) : track(track), time(time)
246  {
247  }
248 
249  template <typename V>
250  Track<V>::KeyframeProxy::operator V() const
251  {
252  return track.at(time);
253  }
254 
255  template <typename V>
256  void
258  {
259  track.addKeyframe(time, value);
260  }
261 
262  template <typename V>
263  void
265  {
266  track.addKeyframe(time, other.value());
267  }
268 
269  template <typename V>
270  auto
272  {
273  return track.at(time);
274  }
275 
276  template <typename ValueT>
277  std::ostream&
278  operator<<(std::ostream& os, const Track<ValueT>& track)
279  {
280  os << "Track '" << track.id << "' with " << track.keyframes.size() << " keyframes: [";
281  for (const Keyframe<ValueT>& kf : track.keyframes)
282  {
283  os << kf.time << ", ";
284  }
285  return os << "]";
286  }
287 
288 } // namespace armarx::trajectory
armarx::trajectory::VariantValue
std::variant< float, Eigen::MatrixXf, Eigen::Quaternionf > VariantValue
Variant for trajectory values.
Definition: VariantValue.h:12
armarx::trajectory::Keyframe
A keyframe, representing a value at a given time.
Definition: Track.h:13
armarx::trajectory::Track
A track represents the timeline of a single value, identified by a track ID.
Definition: Track.h:33
armarx::trajectory::TrackID
std::string TrackID
ID of tracks.
Definition: VariantValue.h:15
armarx::trajectory::Track::clear
void clear()
Clear the track of all keyframes.
Definition: Track.h:140
armarx::trajectory::Track::KeyframeProxy::operator=
void operator=(const ValueT &value)
Add a keyframe on assignment.
armarx::trajectory::Track::KeyframeProxy::value
ValueT value() const
Get the value at time.
Definition: Track.h:271
armarx::trajectory::Track::update
void update(float time, bool ignoreIfEmpty=false)
Call the update function with the interpolated value at the given time.
Definition: Track.h:236
armarx::trajectory::Track::UpdateFunc
std::function< void(ValueT)> UpdateFunc
The update function type.
Definition: Track.h:37
cxxopts::empty
bool empty(const std::string &s)
Definition: cxxopts.hpp:234
VariantValue.h
cxxopts::value
std::shared_ptr< Value > value()
Definition: cxxopts.hpp:855
armarx::trajectory::Keyframe::value
ValueT value
The value.
Definition: Track.h:21
linear.h
armarx::trajectory::Keyframe::Keyframe
Keyframe(float time, const ValueT &value)
Constructor.
Definition: Track.h:16
armarx::trajectory::Track::operator<<
friend std::ostream & operator<<(std::ostream &os, const Track< V > &track)
armarx::trajectory::Track::operator[]
KeyframeProxy operator[](float time)
Add a keyframe by assignment: track[time] = value;
Definition: Track.h:163
armarx::trajectory::Track::KeyframeProxy
A proxy allowing for adding keyframes by: track[time] = value;
Definition: Track.h:105
armarx::trajectory
Definition: exceptions.cpp:3
armarx::trajectory::Track::at
ValueT at(float time) const
Get the interpolated value at the given time.
Definition: Track.h:196
armarx::trajectory::Keyframe::time
float time
The time on the timeline.
Definition: Track.h:20
armarx::trajectory::Track::Track
Track(const TrackID &id, UpdateFunc updateFunc)
Construct a track with given ID and update function.
Definition: Track.h:48
armarx::trajectory::Track::empty
bool empty() const
Indicate whether this track does not contain any keyframes.
Definition: Track.h:133
armarx::trajectory::Track::Track
Track(const TrackID &id)
Construct a track with given ID (and no update function).
Definition: Track.h:43
armarx::trajectory::Track::addKeyframe
void addKeyframe(const Keyframe< ValueT > &keyframe)
Add a keyframe to this track.
armarx::trajectory::error::EmptyTrack
Definition: exceptions.h:28