WPILibC++ 2025.2.1
Loading...
Searching...
No Matches
SplineHelper.h
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 <cstddef>
8#include <vector>
9
10#include <wpi/SymbolExports.h>
11#include <wpi/array.h>
12
15
16namespace frc {
17/**
18 * Helper class that is used to generate cubic and quintic splines from user
19 * provided waypoints.
20 */
22 public:
23 /**
24 * Returns 2 cubic control vectors from a set of exterior waypoints and
25 * interior translations.
26 *
27 * @param start The starting pose.
28 * @param interiorWaypoints The interior waypoints.
29 * @param end The ending pose.
30 * @return 2 cubic control vectors.
31 */
34 const Pose2d& start, const std::vector<Translation2d>& interiorWaypoints,
35 const Pose2d& end) {
36 double scalar;
37 if (interiorWaypoints.empty()) {
38 scalar = 1.2 * start.Translation().Distance(end.Translation()).value();
39 } else {
40 scalar =
41 1.2 * start.Translation().Distance(interiorWaypoints.front()).value();
42 }
43 const auto initialCV = CubicControlVector(scalar, start);
44 if (!interiorWaypoints.empty()) {
45 scalar =
46 1.2 * end.Translation().Distance(interiorWaypoints.back()).value();
47 }
48 const auto finalCV = CubicControlVector(scalar, end);
49 return {initialCV, finalCV};
50 }
51
52 /**
53 * Returns quintic splines from a set of waypoints.
54 *
55 * @param waypoints The waypoints
56 * @return List of quintic splines.
57 */
58 static std::vector<QuinticHermiteSpline> QuinticSplinesFromWaypoints(
59 const std::vector<Pose2d>& waypoints) {
60 std::vector<QuinticHermiteSpline> splines;
61 splines.reserve(waypoints.size() - 1);
62 for (size_t i = 0; i < waypoints.size() - 1; ++i) {
63 auto& p0 = waypoints[i];
64 auto& p1 = waypoints[i + 1];
65
66 // This just makes the splines look better.
67 const auto scalar =
68 1.2 * p0.Translation().Distance(p1.Translation()).value();
69
70 auto controlVectorA = QuinticControlVector(scalar, p0);
71 auto controlVectorB = QuinticControlVector(scalar, p1);
72 splines.emplace_back(controlVectorA.x, controlVectorB.x, controlVectorA.y,
73 controlVectorB.y);
74 }
75 return splines;
76 }
77
78 /**
79 * Returns a set of cubic splines corresponding to the provided control
80 * vectors. The user is free to set the direction of the start and end
81 * point. The directions for the middle waypoints are determined
82 * automatically to ensure continuous curvature throughout the path.
83 *
84 * The derivation for the algorithm used can be found here:
85 * <https://www.uio.no/studier/emner/matnat/ifi/nedlagte-emner/INF-MAT4350/h08/undervisningsmateriale/chap7alecture.pdf>
86 *
87 * @param start The starting control vector.
88 * @param waypoints The middle waypoints. This can be left blank if you
89 * only wish to create a path with two waypoints.
90 * @param end The ending control vector.
91 *
92 * @return A vector of cubic hermite splines that interpolate through the
93 * provided waypoints.
94 */
95 static std::vector<CubicHermiteSpline> CubicSplinesFromControlVectors(
96 const Spline<3>::ControlVector& start,
97 std::vector<Translation2d> waypoints,
98 const Spline<3>::ControlVector& end) {
99 std::vector<CubicHermiteSpline> splines;
100
101 wpi::array<double, 2> xInitial = start.x;
102 wpi::array<double, 2> yInitial = start.y;
103 wpi::array<double, 2> xFinal = end.x;
104 wpi::array<double, 2> yFinal = end.y;
105
106 if (waypoints.size() > 1) {
107 waypoints.emplace(waypoints.begin(),
108 Translation2d{units::meter_t{xInitial[0]},
109 units::meter_t{yInitial[0]}});
110 waypoints.emplace_back(
111 Translation2d{units::meter_t{xFinal[0]}, units::meter_t{yFinal[0]}});
112
113 // Populate tridiagonal system for clamped cubic
114 /* See:
115 https://www.uio.no/studier/emner/matnat/ifi/nedlagte-emner/INF-MAT4350/h08
116 /undervisningsmateriale/chap7alecture.pdf
117 */
118
119 // Above-diagonal of tridiagonal matrix, zero-padded
120 std::vector<double> a;
121 // Diagonal of tridiagonal matrix
122 std::vector<double> b(waypoints.size() - 2, 4.0);
123 // Below-diagonal of tridiagonal matrix, zero-padded
124 std::vector<double> c;
125 // rhs vectors
126 std::vector<double> dx, dy;
127 // solution vectors
128 std::vector<double> fx(waypoints.size() - 2, 0.0),
129 fy(waypoints.size() - 2, 0.0);
130
131 // populate above-diagonal and below-diagonal vectors
132 a.emplace_back(0);
133 for (size_t i = 0; i < waypoints.size() - 3; ++i) {
134 a.emplace_back(1);
135 c.emplace_back(1);
136 }
137 c.emplace_back(0);
138
139 // populate rhs vectors
140 dx.emplace_back(
141 3 * (waypoints[2].X().value() - waypoints[0].X().value()) -
142 xInitial[1]);
143 dy.emplace_back(
144 3 * (waypoints[2].Y().value() - waypoints[0].Y().value()) -
145 yInitial[1]);
146 if (waypoints.size() > 4) {
147 for (size_t i = 1; i <= waypoints.size() - 4; ++i) {
148 // dx and dy represent the derivatives of the internal waypoints. The
149 // derivative of the second internal waypoint should involve the third
150 // and first internal waypoint, which have indices of 1 and 3 in the
151 // waypoints list (which contains ALL waypoints).
152 dx.emplace_back(
153 3 * (waypoints[i + 2].X().value() - waypoints[i].X().value()));
154 dy.emplace_back(
155 3 * (waypoints[i + 2].Y().value() - waypoints[i].Y().value()));
156 }
157 }
158 dx.emplace_back(3 * (waypoints[waypoints.size() - 1].X().value() -
159 waypoints[waypoints.size() - 3].X().value()) -
160 xFinal[1]);
161 dy.emplace_back(3 * (waypoints[waypoints.size() - 1].Y().value() -
162 waypoints[waypoints.size() - 3].Y().value()) -
163 yFinal[1]);
164
165 // Compute solution to tridiagonal system
166 ThomasAlgorithm(a, b, c, dx, &fx);
167 ThomasAlgorithm(a, b, c, dy, &fy);
168
169 fx.emplace(fx.begin(), xInitial[1]);
170 fx.emplace_back(xFinal[1]);
171 fy.emplace(fy.begin(), yInitial[1]);
172 fy.emplace_back(yFinal[1]);
173
174 for (size_t i = 0; i < fx.size() - 1; ++i) {
175 // Create the spline.
176 const CubicHermiteSpline spline{
177 {waypoints[i].X().value(), fx[i]},
178 {waypoints[i + 1].X().value(), fx[i + 1]},
179 {waypoints[i].Y().value(), fy[i]},
180 {waypoints[i + 1].Y().value(), fy[i + 1]}};
181
182 splines.push_back(spline);
183 }
184 } else if (waypoints.size() == 1) {
185 const double xDeriv =
186 (3 * (xFinal[0] - xInitial[0]) - xFinal[1] - xInitial[1]) / 4.0;
187 const double yDeriv =
188 (3 * (yFinal[0] - yInitial[0]) - yFinal[1] - yInitial[1]) / 4.0;
189
190 wpi::array<double, 2> midXControlVector{waypoints[0].X().value(), xDeriv};
191 wpi::array<double, 2> midYControlVector{waypoints[0].Y().value(), yDeriv};
192
193 splines.emplace_back(xInitial, midXControlVector, yInitial,
194 midYControlVector);
195 splines.emplace_back(midXControlVector, xFinal, midYControlVector,
196 yFinal);
197
198 } else {
199 // Create the spline.
200 const CubicHermiteSpline spline{xInitial, xFinal, yInitial, yFinal};
201 splines.push_back(spline);
202 }
203
204 return splines;
205 }
206
207 /**
208 * Returns a set of quintic splines corresponding to the provided control
209 * vectors. The user is free to set the direction of all waypoints. Continuous
210 * curvature is guaranteed throughout the path.
211 *
212 * @param controlVectors The control vectors.
213 * @return A vector of quintic hermite splines that interpolate through the
214 * provided waypoints.
215 */
216 static std::vector<QuinticHermiteSpline> QuinticSplinesFromControlVectors(
217 const std::vector<Spline<5>::ControlVector>& controlVectors) {
218 std::vector<QuinticHermiteSpline> splines;
219 for (size_t i = 0; i < controlVectors.size() - 1; ++i) {
220 auto& xInitial = controlVectors[i].x;
221 auto& yInitial = controlVectors[i].y;
222 auto& xFinal = controlVectors[i + 1].x;
223 auto& yFinal = controlVectors[i + 1].y;
224 splines.emplace_back(xInitial, xFinal, yInitial, yFinal);
225 }
226 return splines;
227 }
228
229 /**
230 * Optimizes the curvature of 2 or more quintic splines at knot points.
231 * Overall, this reduces the integral of the absolute value of the second
232 * derivative across the set of splines.
233 *
234 * @param splines A vector of un-optimized quintic splines.
235 * @return A vector of optimized quintic splines.
236 */
237 static std::vector<QuinticHermiteSpline> OptimizeCurvature(
238 const std::vector<QuinticHermiteSpline>& splines) {
239 // If there's only one spline in the vector, we can't optimize anything so
240 // just return that.
241 if (splines.size() < 2) {
242 return splines;
243 }
244
245 // Implements Section 4.1.2 of
246 // http://www2.informatik.uni-freiburg.de/~lau/students/Sprunk2008.pdf.
247
248 // Cubic splines minimize the integral of the second derivative's absolute
249 // value. Therefore, we can create cubic splines with the same 0th and 1st
250 // derivatives and the provided quintic splines, find the second derivative
251 // of those cubic splines and then use a weighted average for the second
252 // derivatives of the quintic splines.
253
254 std::vector<QuinticHermiteSpline> optimizedSplines;
255 optimizedSplines.reserve(splines.size());
256 optimizedSplines.push_back(splines[0]);
257
258 for (size_t i = 0; i < splines.size() - 1; ++i) {
259 const auto& a = splines[i];
260 const auto& b = splines[i + 1];
261
262 // Get the control vectors that created the quintic splines above.
263 const auto& aInitial = a.GetInitialControlVector();
264 const auto& aFinal = a.GetFinalControlVector();
265 const auto& bInitial = b.GetInitialControlVector();
266 const auto& bFinal = b.GetFinalControlVector();
267
268 // Create cubic splines with the same control vectors.
269 auto Trim = [](const wpi::array<double, 3>& a) {
270 return wpi::array<double, 2>{a[0], a[1]};
271 };
272 CubicHermiteSpline ca{Trim(aInitial.x), Trim(aFinal.x), Trim(aInitial.y),
273 Trim(aFinal.y)};
274 CubicHermiteSpline cb{Trim(bInitial.x), Trim(bFinal.x), Trim(bInitial.y),
275 Trim(bFinal.y)};
276
277 // Calculate the second derivatives at the knot points.
278 frc::Vectord<4> bases{1.0, 1.0, 1.0, 1.0};
279 frc::Vectord<6> combinedA = ca.Coefficients() * bases;
280
281 double ddxA = combinedA(4);
282 double ddyA = combinedA(5);
283 double ddxB = cb.Coefficients()(4, 1);
284 double ddyB = cb.Coefficients()(5, 1);
285
286 // Calculate the parameters for weighted average.
287 double dAB =
288 std::hypot(aFinal.x[0] - aInitial.x[0], aFinal.y[0] - aInitial.y[0]);
289 double dBC =
290 std::hypot(bFinal.x[0] - bInitial.x[0], bFinal.y[0] - bInitial.y[0]);
291 double alpha = dBC / (dAB + dBC);
292 double beta = dAB / (dAB + dBC);
293
294 // Calculate the weighted average.
295 double ddx = alpha * ddxA + beta * ddxB;
296 double ddy = alpha * ddyA + beta * ddyB;
297
298 // Create new splines.
299 optimizedSplines[i] = {aInitial.x,
300 {aFinal.x[0], aFinal.x[1], ddx},
301 aInitial.y,
302 {aFinal.y[0], aFinal.y[1], ddy}};
303 optimizedSplines.push_back({{bInitial.x[0], bInitial.x[1], ddx},
304 bFinal.x,
305 {bInitial.y[0], bInitial.y[1], ddy},
306 bFinal.y});
307 }
308
309 return optimizedSplines;
310 }
311
312 private:
313 static Spline<3>::ControlVector CubicControlVector(double scalar,
314 const Pose2d& point) {
315 return {{point.X().value(), scalar * point.Rotation().Cos()},
316 {point.Y().value(), scalar * point.Rotation().Sin()}};
317 }
318
319 static Spline<5>::ControlVector QuinticControlVector(double scalar,
320 const Pose2d& point) {
321 return {{point.X().value(), scalar * point.Rotation().Cos(), 0.0},
322 {point.Y().value(), scalar * point.Rotation().Sin(), 0.0}};
323 }
324
325 /**
326 * Thomas algorithm for solving tridiagonal systems Af = d.
327 *
328 * @param a the values of A above the diagonal
329 * @param b the values of A on the diagonal
330 * @param c the values of A below the diagonal
331 * @param d the vector on the rhs
332 * @param solutionVector the unknown (solution) vector, modified in-place
333 */
334 static void ThomasAlgorithm(const std::vector<double>& a,
335 const std::vector<double>& b,
336 const std::vector<double>& c,
337 const std::vector<double>& d,
338 std::vector<double>* solutionVector) {
339 auto& f = *solutionVector;
340 size_t N = d.size();
341
342 // Create the temporary vectors
343 // Note that this is inefficient as it is possible to call
344 // this function many times. A better implementation would
345 // pass these temporary matrices by non-const reference to
346 // save excess allocation and deallocation
347 std::vector<double> c_star(N, 0.0);
348 std::vector<double> d_star(N, 0.0);
349
350 // This updates the coefficients in the first row
351 // Note that we should be checking for division by zero here
352 c_star[0] = c[0] / b[0];
353 d_star[0] = d[0] / b[0];
354
355 // Create the c_star and d_star coefficients in the forward sweep
356 for (size_t i = 1; i < N; ++i) {
357 double m = 1.0 / (b[i] - a[i] * c_star[i - 1]);
358 c_star[i] = c[i] * m;
359 d_star[i] = (d[i] - a[i] * d_star[i - 1]) * m;
360 }
361
362 f[N - 1] = d_star[N - 1];
363 // This is the reverse sweep, used to update the solution vector f
364 for (int i = N - 2; i >= 0; i--) {
365 f[i] = d_star[i] - c_star[i] * f[i + 1];
366 }
367 }
368};
369} // namespace frc
#define WPILIB_DLLEXPORT
Definition SymbolExports.h:36
Represents a hermite spline of degree 3.
Definition CubicHermiteSpline.h:17
Matrixd< 6, 3+1 > Coefficients() const override
Returns the coefficients matrix.
Definition CubicHermiteSpline.h:74
Represents a 2D pose containing translational and rotational elements.
Definition Pose2d.h:28
constexpr const Rotation2d & Rotation() const
Returns the underlying rotation.
Definition Pose2d.h:128
constexpr units::meter_t Y() const
Returns the Y component of the pose's translation.
Definition Pose2d.h:121
constexpr units::meter_t X() const
Returns the X component of the pose's translation.
Definition Pose2d.h:114
constexpr double Cos() const
Returns the cosine of the rotation.
Definition Rotation2d.h:231
constexpr double Sin() const
Returns the sine of the rotation.
Definition Rotation2d.h:238
Helper class that is used to generate cubic and quintic splines from user provided waypoints.
Definition SplineHelper.h:21
static std::vector< CubicHermiteSpline > CubicSplinesFromControlVectors(const Spline< 3 >::ControlVector &start, std::vector< Translation2d > waypoints, const Spline< 3 >::ControlVector &end)
Returns a set of cubic splines corresponding to the provided control vectors.
Definition SplineHelper.h:95
static std::vector< QuinticHermiteSpline > OptimizeCurvature(const std::vector< QuinticHermiteSpline > &splines)
Optimizes the curvature of 2 or more quintic splines at knot points.
Definition SplineHelper.h:237
static std::vector< QuinticHermiteSpline > QuinticSplinesFromControlVectors(const std::vector< Spline< 5 >::ControlVector > &controlVectors)
Returns a set of quintic splines corresponding to the provided control vectors.
Definition SplineHelper.h:216
static wpi::array< Spline< 3 >::ControlVector, 2 > CubicControlVectorsFromWaypoints(const Pose2d &start, const std::vector< Translation2d > &interiorWaypoints, const Pose2d &end)
Returns 2 cubic control vectors from a set of exterior waypoints and interior translations.
Definition SplineHelper.h:33
static std::vector< QuinticHermiteSpline > QuinticSplinesFromWaypoints(const std::vector< Pose2d > &waypoints)
Returns quintic splines from a set of waypoints.
Definition SplineHelper.h:58
Represents a translation in 2D space.
Definition Translation2d.h:29
This class is a wrapper around std::array that does compile time size checking.
Definition array.h:26
Definition CAN.h:11
Eigen::Vector< double, Size > Vectord
Definition EigenCore.h:12
unit< std::ratio< 1 >, units::category::scalar_unit > scalar
Definition base.h:2514
b
Definition data.h:44
Represents a control vector for a spline.
Definition Spline.h:48
Type representing an arbitrary unit.
Definition base.h:888