RIT VEXU Core API
Loading...
Searching...
No Matches
spline_base.h
1#pragma once
2
3#include <vector>
4
5#include "core/math/geometry/rotation2d.h"
6#include "core/math/geometry/translation2d.h"
7#include "core/math/units.h"
8
9using namespace units::literals;
10
15 double u = 0.0;
16 double s = 0.0;
17 Translation2d position;
18 Translation2d velocity;
19 Translation2d acceleration;
21 units::Curvature curvature = 0_radpm;
22};
23
31 public:
32 virtual ~SplineBase() = default;
33
39 virtual Translation2d position(double u) const = 0;
40
46 virtual Translation2d velocity(double u) const = 0;
47
53 virtual Translation2d acceleration(double u) const = 0;
54
60 Rotation2d heading(double u) const {
61 const Translation2d vel = velocity(clamp_u(u));
62 return vel.norm().to(units::in) > 1e-9 ? vel.theta() : Rotation2d();
63 }
64
70 units::Curvature curvature(double u) const {
71 const Translation2d vel = velocity(clamp_u(u));
72 const Translation2d acc = acceleration(clamp_u(u));
73 const units::Length speed = vel.norm();
74 if (speed < 1e-4_in) {
75 return 0_radpm;
76 }
77
78 const units::Area cross_product = (vel.x() * acc.y()) - (vel.y() * acc.x());
79 return (cross_product * 1_rad) / units::cube(speed);
80 }
81
86 double length() const { return arc_lengths_.empty() ? 0.0 : arc_lengths_.back(); }
87
93 double u_from_s(double s) const {
94 if (arc_lengths_.empty() || us_.empty()) {
95 return 0.0;
96 }
97 if (s <= 0.0) {
98 return 0.0;
99 }
100 if (s >= length()) {
101 return 1.0;
102 }
103
104 auto upper = std::lower_bound(arc_lengths_.begin(), arc_lengths_.end(), s);
105 const size_t idx = static_cast<size_t>(upper - arc_lengths_.begin());
106 if (idx == 0) {
107 return us_.front();
108 }
109
110 const double s0 = arc_lengths_[idx - 1];
111 const double s1 = arc_lengths_[idx];
112 const double u0 = us_[idx - 1];
113 const double u1 = us_[idx];
114 const double alpha = (s - s0) / std::max(s1 - s0, 1e-9);
115 return u0 + ((u1 - u0) * alpha);
116 }
117
124 SplineSample sample(double u) const {
125 const double clamped_u = clamp_u(u);
126 return SplineSample{
127 .u = clamped_u,
128 .s = s_from_u(clamped_u),
129 .position = position(clamped_u),
130 .velocity = velocity(clamped_u),
131 .acceleration = acceleration(clamped_u),
132 .heading = heading(clamped_u),
133 .curvature = curvature(clamped_u),
134 };
135 }
136
142 SplineSample sample_by_s(double s) const {
143 SplineSample out = sample(u_from_s(s));
144 out.s = clamp_value(s, 0.0, length());
145 return out;
146 }
147
153 std::vector<Translation2d> sample_by_count(int count) const {
154 std::vector<Translation2d> points;
155 if (count <= 0) {
156 return points;
157 }
158 if (count == 1) {
159 points.push_back(position(0.0));
160 return points;
161 }
162
163 points.reserve(static_cast<size_t>(count));
164 for (int i = 0; i < count; ++i) {
165 const double u = static_cast<double>(i) / static_cast<double>(count - 1);
166 points.push_back(position(u));
167 }
168 return points;
169 }
170
176 std::vector<Translation2d> sample_by_spacing(double ds) const {
177 std::vector<Translation2d> points;
178 if (ds <= 0.0 || length() <= 0.0) {
179 return points;
180 }
181
182 for (double s = 0.0; s < length(); s += ds) {
183 points.push_back(sample_by_s(s).position);
184 }
185 points.push_back(position(1.0));
186 return points;
187 }
188
189 protected:
195 template <size_t N>
196 static double eval_poly(const std::array<double, N>& coeffs, double u) {
197 double out = 0.0;
198 for (size_t i = N; i-- > 0;) {
199 out = (out * u) + coeffs[i];
200 }
201 return out;
202 }
203
207 template <size_t N>
208 static double eval_poly_derivative(const std::array<double, N>& coeffs, double u) {
209 double out = 0.0;
210 for (size_t i = N - 1; i-- > 0;) {
211 out = (out * u) + (coeffs[i + 1] * static_cast<double>(i + 1));
212 }
213 return out;
214 }
215
219 template <size_t N>
220 static double eval_poly_second_derivative(const std::array<double, N>& coeffs, double u) {
221 double out = 0.0;
222 for (size_t i = N - 2; i-- > 0;) {
223 out = (out * u) + (coeffs[i + 2] * static_cast<double>((i + 1) * (i + 2)));
224 }
225 return out;
226 }
227
232 double integrate_segment_length(double u0, double u1) const {
233 constexpr double x[5] = {0.0, -0.5384693101056831, 0.5384693101056831, -0.9061798459386640,
234 0.9061798459386640};
235 constexpr double w[5] = {0.5688888888888889, 0.4786286704993665, 0.4786286704993665,
236 0.2369268850561891, 0.2369268850561891};
237
238 const double half_len = (u1 - u0) * 0.5;
239 const double mid = (u0 + u1) * 0.5;
240 double sum = 0.0;
241 for (int i = 0; i < 5; ++i) {
242 const double u = mid + (half_len * x[i]);
243 sum += w[i] * velocity(u).norm().to(units::in);
244 }
245 return half_len * sum;
246 }
247
251 void build_arc_table_recursive(double u0, double u1, double s0, double segment_length,
252 double max_err) {
253 double mid_u = (u0 + u1) * 0.5;
254 double len1 = integrate_segment_length(u0, mid_u);
255 double len2 = integrate_segment_length(mid_u, u1);
256
257 double actual_mid_s = s0 + len1;
258 double linear_mid_s = s0 + segment_length * 0.5;
259
260 double integral_err = units::abs((len1 + len2) - segment_length);
261 double interp_err = units::abs(actual_mid_s - linear_mid_s);
262
263 if ((integral_err > max_err || interp_err > max_err) && (u1 - u0) > 1e-5) {
264 build_arc_table_recursive(u0, mid_u, s0, len1, max_err);
265 build_arc_table_recursive(mid_u, u1, actual_mid_s, len2, max_err);
266 } else {
267 us_.push_back(u1);
268 arc_lengths_.push_back(s0 + len1 + len2);
269 }
270 }
271
277 void build_arc_table(double max_err = 1e-4) {
278 us_.clear();
279 arc_lengths_.clear();
280
281 us_.push_back(0.0);
282 arc_lengths_.push_back(0.0);
283
284 const int initial_segments = 10;
285 double s0 = 0.0;
286 for (int i = 0; i < initial_segments; ++i) {
287 double u0 = static_cast<double>(i) / initial_segments;
288 double u1 = static_cast<double>(i + 1) / initial_segments;
289 double len = integrate_segment_length(u0, u1);
290 build_arc_table_recursive(u0, u1, s0, len, max_err);
291 s0 = arc_lengths_.back();
292 }
293 }
294
295 static double clamp_u(double u) { return clamp_value(u, 0.0, 1.0); }
296
297 static double clamp_value(double value, double lo, double hi) {
298 if (value < lo) {
299 return lo;
300 }
301 if (value > hi) {
302 return hi;
303 }
304 return value;
305 }
306
307 private:
308 double s_from_u(double u) const {
309 if (us_.empty() || arc_lengths_.empty()) {
310 return 0.0;
311 }
312 if (u <= 0.0) {
313 return 0.0;
314 }
315 if (u >= 1.0) {
316 return length();
317 }
318
319 auto upper = std::lower_bound(us_.begin(), us_.end(), u);
320 const size_t idx = static_cast<size_t>(upper - us_.begin());
321 if (idx == 0) {
322 return arc_lengths_.front();
323 }
324
325 const double u0 = us_[idx - 1];
326 const double u1 = us_[idx];
327 const double s0 = arc_lengths_[idx - 1];
328 const double s1 = arc_lengths_[idx];
329 const double alpha = (u - u0) / std::max(u1 - u0, 1e-9);
330 return s0 + ((s1 - s0) * alpha);
331 }
332
333 std::vector<double> us_;
334 std::vector<double> arc_lengths_;
335};
336
341template <size_t Order>
342class HermiteSpline : public SplineBase {
343 public:
344 Translation2d position(double u) const override {
345 const double clamped_u = clamp_u(u);
346 return Translation2d(eval_poly(x_, clamped_u) * 1_in, eval_poly(y_, clamped_u) * 1_in);
347 }
348
349 Translation2d velocity(double u) const override {
350 const double clamped_u = clamp_u(u);
351 return Translation2d(eval_poly_derivative(x_, clamped_u) * 1_in,
352 eval_poly_derivative(y_, clamped_u) * 1_in);
353 }
354
355 Translation2d acceleration(double u) const override {
356 const double clamped_u = clamp_u(u);
357 return Translation2d(eval_poly_second_derivative(x_, clamped_u) * 1_in,
358 eval_poly_second_derivative(y_, clamped_u) * 1_in);
359 }
360
361 protected:
362 std::array<double, Order + 1> x_{};
363 std::array<double, Order + 1> y_{};
364};
CRTP base class for Hermite splines of specific polynomial order.
Definition spline_base.h:342
constexpr Rotation2d theta() const
Gets the angle of the vector from the x axis.
Definition translation2d.h:83
constexpr Q x() const
Gets the x component.
Definition translation2d.h:65
constexpr Q norm() const
Gets the norm of the vector.
Definition translation2d.h:94
constexpr Q y() const
Gets the y component.
Definition translation2d.h:74
Definition rotation2d.h:16
constexpr Rotation2d()=default
Default Constructor for Rotation2d.
Abstract base class for parametric 2D Hermite splines.
Definition spline_base.h:30
SplineSample sample_by_s(double s) const
Samples complete state at arc distance s.
Definition spline_base.h:142
virtual Translation2d position(double u) const =0
Computes 2D position at spline parameter u.
virtual Translation2d acceleration(double u) const =0
Computes 2D parametric acceleration (second derivative) at parameter u.
static double eval_poly_derivative(const std::array< double, N > &coeffs, double u)
Evaluates first derivative P'(u) using Horner's method.
Definition spline_base.h:208
static double eval_poly(const std::array< double, N > &coeffs, double u)
Evaluates polynomial P(u) using Horner's method.
Definition spline_base.h:196
void build_arc_table_recursive(double u0, double u1, double s0, double segment_length, double max_err)
Recursively builds the arc table to satisfy a linear interpolation error bound.
Definition spline_base.h:251
Rotation2d heading(double u) const
Computes heading angle theta at spline parameter u.
Definition spline_base.h:60
SplineSample sample(double u) const
Samples complete state (position, velocity, acceleration, heading, curvature) at parameter u.
Definition spline_base.h:124
double u_from_s(double s) const
Inverts arc distance s to spline parameter u.
Definition spline_base.h:93
double length() const
Returns total arc length of the spline in inches.
Definition spline_base.h:86
static double eval_poly_second_derivative(const std::array< double, N > &coeffs, double u)
Evaluates second derivative P''(u) using Horner's method.
Definition spline_base.h:220
double integrate_segment_length(double u0, double u1) const
Computes high-accuracy arc length over sub-interval [u0, u1] using 5-point Gauss-Legendre quadrature.
Definition spline_base.h:232
void build_arc_table(double max_err=1e-4)
Builds pre-computed parameter u and arc-length lookup tables using adaptive refinement.
Definition spline_base.h:277
virtual Translation2d velocity(double u) const =0
Computes 2D parametric velocity (first derivative) at parameter u.
std::vector< Translation2d > sample_by_count(int count) const
Samples 2D positions uniformly spaced in parameter u.
Definition spline_base.h:153
units::Curvature curvature(double u) const
Computes path curvature at parameter u.
Definition spline_base.h:70
std::vector< Translation2d > sample_by_spacing(double ds) const
Samples 2D positions uniformly spaced by arc distance ds.
Definition spline_base.h:176
Evaluated state sample at a specific parameter u or distance s along a spline.
Definition spline_base.h:14
units::Curvature curvature
Path curvature (rad/meter).
Definition spline_base.h:21
Translation2d acceleration
1D/2D parametric acceleration vector (d^2x/du^2, d^2y/du^2)
Definition spline_base.h:19
Translation2d position
2D position (x, y)
Definition spline_base.h:17
double s
Arc length along spline from start in inches.
Definition spline_base.h:16
double u
Spline parameter in range [0, 1].
Definition spline_base.h:15
Translation2d velocity
1D/2D parametric velocity vector (dx/du, dy/du)
Definition spline_base.h:18
Rotation2d heading
Heading angle theta along path.
Definition spline_base.h:20