2#include "lemlib/util.hpp"
10RamsetePathFollower::Vector2::Vector2(
float x,
float y) : x(x), y(y) {}
12std::string RamsetePathFollower::Vector2::latex()
const {
13 std::ostringstream oss;
14 oss <<
"\\left(" << std::fixed << this->x <<
"," << std::fixed << this->y <<
"\\right)";
18double RamsetePathFollower::sinc(
double x) {
19 if (std::abs(x) < 1e-6) {
20 return 1.0 - (x * x) / 6.0 + (x * x * x * x) / 120.0;
22 return std::sin(x) / x;
25double RamsetePathFollower::angleError(
double robotAngle,
double targetAngle) {
26 constexpr double TWO_PI = 2.0 * M_PI;
27 double diff = std::fmod(targetAngle - robotAngle, TWO_PI);
28 if (diff < -M_PI) diff += TWO_PI;
29 else if (diff >= M_PI) diff -= TWO_PI;
33RamsetePathFollower::RamsetePathFollower(
const VelocityControllerConfig&
config,
float b_,
float zeta_)
39 ), b(b_), zeta(zeta_) {}
41void RamsetePathFollower::followPath(
const std::string& path_name,
const ramseteConfig& r_config) {
48 cancel_request =
false;
49 distance_traveled = 0.0f;
51 TaskParams* params =
new TaskParams{
this, path_name, r_config};
53 task =
new pros::Task(task_trampoline, params,
"RamseteTask");
58void RamsetePathFollower::task_trampoline(
void* params) {
59 TaskParams* p =
static_cast<TaskParams*
>(params);
60 p->instance->followPathImpl(p->path_name, p->config);
64void RamsetePathFollower::waitUntilDone() {
70void RamsetePathFollower::waitUntil(
float dist_inches) {
71 while (is_running && distance_traveled < dist_inches) {
76void RamsetePathFollower::cancel() {
77 cancel_request =
true;
80bool RamsetePathFollower::isRunning() {
84void RamsetePathFollower::followPathImpl(
const std::string& path_name,
const ramseteConfig& r_config) {
85 std::vector<State> trajectory;
87 if(r_config.path_index >= 0 && (
size_t)(r_config.path_index) < precomputed_paths.size()) {
88 if (!precomputed_paths.at(r_config.path_index).empty()) {
89 trajectory = precomputed_paths.at(r_config.path_index);
92 if (trajectory.empty()) {
93 trajectory = prepare_trajectory(path_name);
95 if (trajectory.empty()) {
102 }
else if(r_config.turnFirst) {
103 double targetH = lemlib::radToDeg(M_PI_2 - trajectory[0].heading);
104 chassis.turnToHeading(r_config.backwards ? targetH + 180 : targetH, 1000);
107 std::vector<std::string> logs;
108 int trajectory_size = trajectory.size();
110 const float min_k_sq_vel = 0.5f;
111 const int path_dt_ms = 10;
113 const double success_tolerance_inches = 0.5;
114 const int max_settle_time_ms = 1500;
116 lemlib::Pose start_pose =
chassis.getPose();
117 uint32_t global_start_time = pros::millis();
119 bool is_settling =
false;
120 uint32_t settle_start_time = 0;
122 while (!cancel_request) {
123 uint32_t now = pros::millis();
125 float t_elapsed_sec = (now - global_start_time) / 1000.0f;
126 float exact_index = t_elapsed_sec / (path_dt_ms / 1000.0f);
127 int idx =
static_cast<int>(exact_index);
130 float current_b = r_config.b;
132 if (idx < trajectory_size - 1) {
133 float alpha = exact_index - idx;
134 const State& s0 = trajectory[idx];
135 const State& s1 = trajectory[idx+1];
137 target_state.x = s0.x + alpha * (s1.x - s0.x);
138 target_state.y = s0.y + alpha * (s1.y - s0.y);
139 target_state.linear_vel = s0.linear_vel + alpha * (s1.linear_vel - s0.linear_vel);
140 target_state.angular_vel = s0.angular_vel + alpha * (s1.angular_vel - s0.angular_vel);
142 double dh = s1.heading - s0.heading;
143 while (dh > M_PI) dh -= 2 * M_PI;
144 while (dh < -M_PI) dh += 2 * M_PI;
145 target_state.heading = s0.heading + alpha * dh;
150 settle_start_time = now;
153 lemlib::Pose p =
chassis.getPose();
157 double dist_to_end_m = std::hypot(
158 trajectory.back().x - p_x_m,
159 trajectory.back().y - p_y_m
164 if (dist_to_end_in < success_tolerance_inches) {
167 if (now - settle_start_time > max_settle_time_ms) {
171 target_state = trajectory.back();
172 target_state.linear_vel = 0;
173 target_state.angular_vel = 0;
175 current_b = r_config.b * 4.0;
178 lemlib::Pose current_pose =
chassis.getPose(
true);
179 distance_traveled = start_pose.distance(current_pose);
183 current_pose.theta = M_PI_2 - current_pose.theta;
185 double targetHeadingAdjusted = target_state.heading + (r_config.backwards ? M_PI : 0);
186 double errorTheta = angleError(current_pose.theta, targetHeadingAdjusted);
188 Eigen::Vector3d global_error;
189 global_error << target_state.x - current_pose.x, target_state.y - current_pose.y, errorTheta;
191 Eigen::Matrix3d rotation_matrix;
192 rotation_matrix << std::cos(current_pose.theta), std::sin(current_pose.theta), 0,
193 -std::sin(current_pose.theta), std::cos(current_pose.theta), 0,
196 Eigen::Vector3d local_error = rotation_matrix * global_error;
197 float e_x = local_error(0);
198 float e_y = local_error(1);
199 float e_t = local_error(2);
201 float vd = target_state.linear_vel * (r_config.backwards ? -1.0 : 1.0);
202 float wd = target_state.angular_vel;
204 float k = 2.0 * r_config.zeta * std::sqrt(wd * wd + current_b * std::max((
float)(vd * vd), min_k_sq_vel));
206 float v_desired_ramsete = vd * std::cos(e_t) + k * e_x;
207 float w_desired_ramsete = wd + k * e_t + (current_b * vd * sinc(e_t) * e_y);
212 DrivetrainVoltages output_voltages =
controller.update(
219 rightMotors.move_voltage(output_voltages.rightVoltage * 1000.0);
220 leftMotors.move_voltage(output_voltages.leftVoltage * 1000.0);
223 std::ostringstream ss;
224 ss << Vector2(current_pose.x, current_pose.y).latex() <<
",";
225 logs.push_back(ss.str());
234 if(!r_config.test && r_config.end_correction && !cancel_request) {
238 lemlib::radToDeg(M_PI_2 - trajectory.back().heading),
240 {.lead = r_config.mpose_lead}
246 for (
const auto& line : logs) {
255void RamsetePathFollower::precompute_paths(
const std::vector<std::string>& path_names) {
256 auto* stored =
new std::vector<std::string>(path_names);
257 pros::Task t(precompute_paths_task, stored);
260void RamsetePathFollower::precompute_paths_task(
void* param) {
261 auto* path_names =
static_cast<std::vector<std::string>*
>(param);
263 precomputed_paths.clear();
264 precomputed_paths.reserve(path_names->size());
266 for (
const auto& name : *path_names) {
267 precomputed_paths.push_back(prepare_trajectory(name));
273std::vector<std::pair<double,double>> RamsetePathFollower::parse_pairs(
const std::string& line) {
274 std::vector<std::pair<double,double>> result;
276 bool inside_parens =
false;
277 for (
char c : line) {
280 inside_parens =
true;
281 }
else if (c ==
')') {
282 std::replace(temp.begin(), temp.end(),
',',
' ');
283 std::istringstream ss(temp);
284 double first, second;
285 ss >> first >> second;
286 result.emplace_back(first, second);
287 inside_parens =
false;
288 }
else if (inside_parens) {
295std::vector<State> RamsetePathFollower::prepare_trajectory(
const std::string& data) {
296 std::istringstream ss(data);
297 std::vector<std::pair<double,double>>
X, L, A;
299 X.reserve(1000); L.reserve(1000); A.reserve(1000);
301 while (std::getline(ss, line)) {
302 if (line.find(
"X =") != std::string::npos)
X = parse_pairs(line.substr(line.find(
'[')));
303 else if (line.find(
"L =") != std::string::npos) L = parse_pairs(line.substr(line.find(
'[')));
304 else if (line.find(
"A =") != std::string::npos) A = parse_pairs(line.substr(line.find(
'[')));
308 if (n == 0)
return {};
310 std::vector<State> states(n);
312 for (
size_t i = 0; i < n; i++) {
313 states[i].x =
X[i].first;
314 states[i].y =
X[i].second;
315 states[i].linear_vel = L[i].second;
316 states[i].angular_vel = A[i].second;
320 states[0].heading = atan2(states[1].y - states[0].y, states[1].x - states[0].x);
322 for (
size_t i = 1; i < n - 1; i++) {
323 double dy = states[i + 1].y - states[i - 1].y;
324 double dx = states[i + 1].x - states[i - 1].x;
325 states[i].heading = atan2(dy, dx);
328 states[n - 1].heading = atan2(states[n - 1].y - states[n - 2].y, states[n - 1].x - states[n - 2].x);
331 for(
size_t i = 1; i < n; ++i) {
332 double diff = states[i].heading - states[i-1].heading;
333 while (diff > M_PI) diff -= 2 * M_PI;
334 while (diff < -M_PI) diff += 2 * M_PI;
335 states[i].heading = states[i-1].heading + diff;
const VelocityControllerConfig config
pros::MotorGroup rightMotors
pros::MotorGroup leftMotors
pros::Controller controller
static constexpr float INCH_TO_METER
static constexpr float rpm_to_mps_factor