WPILibC++ 2027.0.0-alpha-4
Loading...
Searching...
No Matches
TimeInterpolatableBuffer.hpp
Go to the documentation of this file.
1// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
4
5#pragma once
6
7#include <algorithm>
8#include <functional>
9#include <optional>
10#include <utility>
11#include <vector>
12
16#include "wpi/units/time.hpp"
18
19namespace wpi::math {
20
21/**
22 * The TimeInterpolatableBuffer provides an easy way to estimate past
23 * measurements. One application might be in conjunction with the
24 * DifferentialDrivePoseEstimator, where knowledge of the robot pose at the time
25 * when vision or other global measurement were recorded is necessary, or for
26 * recording the past angles of mechanisms as measured by encoders.
27 *
28 * When sampling this buffer, a user-provided function or wpi::util::Lerp can be
29 * used. For Pose2ds, we use Twists.
30 *
31 * @tparam T The type stored in this buffer.
32 */
33template <typename T>
35 public:
36 /**
37 * Create a new TimeInterpolatableBuffer.
38 *
39 * @param historySize The history size of the buffer.
40 * @param func The function used to interpolate between values.
41 */
42 TimeInterpolatableBuffer(wpi::units::second_t historySize,
43 std::function<T(const T&, const T&, double)> func)
44 : m_historySize(historySize), m_interpolatingFunc(func) {}
45
46 /**
47 * Create a new TimeInterpolatableBuffer. By default, the interpolation
48 * function is wpi::util::Lerp. If the arithmetic operators aren't supported
49 * (usually because they wouldn't be commutative), Interpolate() is used
50 * instead.
51 *
52 * @param historySize The history size of the buffer.
53 */
54 explicit TimeInterpolatableBuffer(wpi::units::second_t historySize)
55 : m_historySize(historySize),
56 m_interpolatingFunc([](const T& start, const T& end, double t) {
57 if constexpr (requires(T a, T b, double t) { a + (b - a) * t; }) {
58 return wpi::util::Lerp(start, end, t);
59 } else {
60 return start.Interpolate(end, t);
61 }
62 }) {}
63
64 /**
65 * Add a sample to the buffer.
66 *
67 * @param time The timestamp of the sample.
68 * @param sample The sample object.
69 */
70 void AddSample(wpi::units::second_t time, T sample) {
71 // Add the new state into the vector
72 if (m_pastSnapshots.size() == 0 || time > m_pastSnapshots.back().first) {
73 m_pastSnapshots.emplace_back(time, sample);
74 } else {
75 auto first_after = std::upper_bound(
76 m_pastSnapshots.begin(), m_pastSnapshots.end(), time,
77 [](auto t, const auto& pair) { return t < pair.first; });
78
79 if (first_after == m_pastSnapshots.begin()) {
80 // All entries come after the sample
81 m_pastSnapshots.insert(first_after, std::pair{time, sample});
82 } else if (auto last_not_greater_than = first_after - 1;
83 last_not_greater_than == m_pastSnapshots.begin() ||
84 last_not_greater_than->first < time) {
85 // Some entries come before the sample, but none are recorded with the
86 // same time
87 m_pastSnapshots.insert(first_after, std::pair{time, sample});
88 } else {
89 // An entry exists with the same recorded time
90 last_not_greater_than->second = sample;
91 }
92 }
93 while (time - m_pastSnapshots[0].first > m_historySize) {
94 m_pastSnapshots.erase(m_pastSnapshots.begin());
95 }
96 }
97
98 /** Clear all old samples. */
99 void Clear() { m_pastSnapshots.clear(); }
100
101 /**
102 * Sample the buffer at the given time. If the buffer is empty, an empty
103 * optional is returned.
104 *
105 * @param time The time at which to sample the buffer.
106 */
107 std::optional<T> Sample(wpi::units::second_t time) const {
108 if (m_pastSnapshots.empty()) {
109 return {};
110 }
111
112 // We will perform a binary search to find the index of the element in the
113 // vector that has a timestamp that is equal to or greater than the vision
114 // measurement timestamp.
115
116 if (time <= m_pastSnapshots.front().first) {
117 return m_pastSnapshots.front().second;
118 }
119 if (time > m_pastSnapshots.back().first) {
120 return m_pastSnapshots.back().second;
121 }
122 if (m_pastSnapshots.size() < 2) {
123 return m_pastSnapshots[0].second;
124 }
125
126 // Get the iterator which has a key no less than the requested key.
127 auto upper_bound = std::lower_bound(
128 m_pastSnapshots.begin(), m_pastSnapshots.end(), time,
129 [](const auto& pair, auto t) { return t > pair.first; });
130
131 if (upper_bound == m_pastSnapshots.begin()) {
132 return upper_bound->second;
133 }
134
135 auto lower_bound = upper_bound - 1;
136
137 double t = ((time - lower_bound->first) /
138 (upper_bound->first - lower_bound->first));
139
140 return m_interpolatingFunc(lower_bound->second, upper_bound->second, t);
141 }
142
143 /**
144 * Grant access to the internal sample buffer. Used in Pose Estimation to
145 * replay odometry inputs stored within this buffer.
146 */
147 std::vector<std::pair<wpi::units::second_t, T>>& GetInternalBuffer() {
148 return m_pastSnapshots;
149 }
150
151 /**
152 * Grant access to the internal sample buffer.
153 */
154 const std::vector<std::pair<wpi::units::second_t, T>>& GetInternalBuffer()
155 const {
156 return m_pastSnapshots;
157 }
158
159 private:
160 wpi::units::second_t m_historySize;
161 std::vector<std::pair<wpi::units::second_t, T>> m_pastSnapshots;
162 std::function<T(const T&, const T&, double)> m_interpolatingFunc;
163};
164
165// Template specializations to ensure that Pose2d and Pose3d use pose
166// exponential
167template <>
169 wpi::units::second_t historySize)
170 : m_historySize(historySize),
171 m_interpolatingFunc([](const Pose2d& start, const Pose2d& end, double t) {
172 if (t < 0) {
173 return start;
174 } else if (t >= 1) {
175 return end;
176 } else {
177 Twist2d twist = (end - start).Log();
178 Twist2d scaledTwist = twist * t;
179 return start + scaledTwist.Exp();
180 }
181 }) {}
182
183template <>
185 wpi::units::second_t historySize)
186 : m_historySize(historySize),
187 m_interpolatingFunc([](const Pose3d& start, const Pose3d& end, double t) {
188 if (t < 0) {
189 return start;
190 } else if (t >= 1) {
191 return end;
192 } else {
193 Twist3d twist = (end - start).Log();
194 Twist3d scaledTwist = twist * t;
195 return start + scaledTwist.Exp();
196 }
197 }) {}
198
199} // namespace wpi::math
Represents a 2D pose containing translational and rotational elements.
Definition Pose2d.hpp:27
Represents a 3D pose containing translational and rotational elements.
Definition Pose3d.hpp:28
TimeInterpolatableBuffer(wpi::units::second_t historySize)
Create a new TimeInterpolatableBuffer.
Definition TimeInterpolatableBuffer.hpp:54
TimeInterpolatableBuffer(wpi::units::second_t historySize, std::function< T(const T &, const T &, double)> func)
Create a new TimeInterpolatableBuffer.
Definition TimeInterpolatableBuffer.hpp:42
void Clear()
Clear all old samples.
Definition TimeInterpolatableBuffer.hpp:99
std::optional< T > Sample(wpi::units::second_t time) const
Sample the buffer at the given time.
Definition TimeInterpolatableBuffer.hpp:107
const std::vector< std::pair< wpi::units::second_t, T > > & GetInternalBuffer() const
Grant access to the internal sample buffer.
Definition TimeInterpolatableBuffer.hpp:154
std::vector< std::pair< wpi::units::second_t, T > > & GetInternalBuffer()
Grant access to the internal sample buffer.
Definition TimeInterpolatableBuffer.hpp:147
void AddSample(wpi::units::second_t time, T sample)
Add a sample to the buffer.
Definition TimeInterpolatableBuffer.hpp:70
Definition LinearSystem.hpp:20
constexpr T Lerp(const T &startValue, const T &endValue, double t)
Linearly interpolates between two values.
Definition MathExtras.hpp:780
A change in distance along a 2D arc since the last pose update.
Definition Twist2d.hpp:23
constexpr Transform2d Exp() const
Obtain a new Transform2d from a (constant curvature) velocity.
Definition Twist2d.hpp:86
A change in distance along a 3D arc since the last pose update.
Definition Twist3d.hpp:23
constexpr Transform3d Exp() const
Obtain a new Transform3d from a (constant curvature) velocity.
Definition Twist3d.hpp:106