8#include "lemlib/util.hpp"
17RamsetePathFollower::Vector2::Vector2(
float x,
float y) : x(x), y(y) {}
19std::string RamsetePathFollower::Vector2::latex()
const {
20 std::ostringstream oss;
21 oss <<
"\\left(" << std::fixed << this->x <<
"," << std::fixed << this->y <<
"\\right)";
25double RamsetePathFollower::sinc(
double x) {
26 if (std::abs(x) < 1e-6) {
27 return 1.0 - (x * x) / 6.0 + (x * x * x * x) / 120.0;
29 return std::sin(x) / x;
32double RamsetePathFollower::angleError(
double robotAngle,
double targetAngle) {
33 constexpr double TWO_PI = 2.0 * M_PI;
34 double diff = std::fmod(targetAngle - robotAngle, TWO_PI);
35 if (diff < -M_PI) diff += TWO_PI;
36 else if (diff >= M_PI) diff -= TWO_PI;
40RamsetePathFollower::RamsetePathFollower(
const VelocityControllerConfig&
config,
float b_,
float zeta_)
46 ), b(b_), zeta(zeta_) {}
48void RamsetePathFollower::followPath(
const std::string& path_name,
const ramseteConfig& r_config) {
55 cancel_request =
false;
56 distance_traveled = 0.0f;
58 TaskParams* params =
new TaskParams{
this, path_name, r_config};
60 task =
new pros::Task(task_trampoline, params,
"RamseteTask");
65void RamsetePathFollower::task_trampoline(
void* params) {
66 TaskParams* p =
static_cast<TaskParams*
>(params);
67 p->instance->followPathImpl(p->path_name, p->config);
71void RamsetePathFollower::waitUntilDone() {
77void RamsetePathFollower::waitUntil(
float dist_inches) {
78 while (is_running && distance_traveled < dist_inches) {
83void RamsetePathFollower::cancel() {
84 cancel_request =
true;
87bool RamsetePathFollower::isRunning() {
91void RamsetePathFollower::followPathImpl(
const std::string& path_name,
const ramseteConfig& r_config) {
92 std::vector<State> trajectory;
94 if(r_config.path_index >= 0 && (
size_t)(r_config.path_index) < precomputed_paths.size()) {
95 if (!precomputed_paths.at(r_config.path_index).empty()) {
96 trajectory = precomputed_paths.at(r_config.path_index);
99 if (trajectory.empty()) {
100 trajectory = prepare_trajectory(path_name);
102 if (trajectory.empty()) {
108 chassis.setPose(trajectory[0].x /
INCH_TO_METER, trajectory[0].y /
INCH_TO_METER, r_config.backwards ? M_PI_2 - trajectory[0].heading + M_PI : M_PI_2 - trajectory[0].heading,
true);
109 }
else if(r_config.turnFirst) {
110 double targetH = lemlib::radToDeg(M_PI_2 - trajectory[0].heading);
111 chassis.turnToHeading(r_config.backwards ? targetH + 180 : targetH, 1000);
114 std::vector<std::string> logs;
115 int trajectory_size = trajectory.size();
116 const float min_k_sq_vel = 0.0f;
117 const int path_dt_ms = 10;
119 lemlib::Pose start_pose =
chassis.getPose();
120 uint32_t global_start_time = pros::millis();
122 while (!cancel_request) {
123 uint32_t now = pros::millis() - global_start_time;
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 != -1 ? r_config.b : b;
132 if (idx >= trajectory_size - 1) {
136 float alpha = exact_index - idx;
137 const State& s0 = trajectory[idx];
138 const State& s1 = trajectory[idx+1];
140 target_state.x = s0.x + alpha * (s1.x - s0.x);
141 target_state.y = s0.y + alpha * (s1.y - s0.y);
142 target_state.linear_vel = s0.linear_vel + alpha * (s1.linear_vel - s0.linear_vel);
143 target_state.angular_vel = s0.angular_vel + alpha * (s1.angular_vel - s0.angular_vel);
145 double dh = s1.heading - s0.heading;
146 while (dh > M_PI) dh -= 2 * M_PI;
147 while (dh < -M_PI) dh += 2 * M_PI;
148 target_state.heading = s0.heading + alpha * dh;
150 lemlib::Pose current_pose =
chassis.getPose(
true);
151 distance_traveled = start_pose.distance(current_pose);
155 current_pose.theta = M_PI_2 - current_pose.theta;
157 double targetHeadingAdjusted = target_state.heading + (r_config.backwards ? M_PI : 0);
158 double errorTheta = angleError(current_pose.theta, targetHeadingAdjusted);
160 Eigen::Vector3d global_error;
161 global_error << target_state.x - current_pose.x, target_state.y - current_pose.y, errorTheta;
163 Eigen::Matrix3d rotation_matrix;
164 rotation_matrix << std::cos(current_pose.theta), std::sin(current_pose.theta), 0,
165 -std::sin(current_pose.theta), std::cos(current_pose.theta), 0,
168 Eigen::Vector3d local_error = rotation_matrix * global_error;
169 float e_x = local_error(0);
170 float e_y = local_error(1);
171 float e_t = local_error(2);
173 float vd = target_state.linear_vel * (r_config.backwards ? -1.0 : 1.0);
174 float wd = target_state.angular_vel;
176 float k = 2.0 * r_config.zeta * std::sqrt(wd * wd + current_b * std::max((
float)(vd * vd), min_k_sq_vel));
178 float v_desired_ramsete = vd * std::cos(e_t) + k * e_x;
179 float w_desired_ramsete = wd + k * e_t + (current_b * vd * sinc(e_t) * e_y);
184 DrivetrainVoltages output_voltages =
controller.update(
191 rightMotors.move_voltage(output_voltages.rightVoltage * 1000.0);
192 leftMotors.move_voltage(output_voltages.leftVoltage * 1000.0);
195 std::ostringstream ss;
196 ss << Vector2(current_pose.x, current_pose.y).latex() <<
",";
197 logs.push_back(ss.str());
200 pros::Task::delay_until(&now, path_dt_ms);
207 if(!r_config.test && r_config.end_correction && !cancel_request) {
211 lemlib::radToDeg(M_PI_2 - trajectory.back().heading),
213 {.lead = r_config.mpose_lead}
219 for (
const auto& line : logs) {
228void RamsetePathFollower::precompute_paths(
const std::vector<std::string>& path_names) {
229 auto* stored =
new std::vector<std::string>(path_names);
230 pros::Task t(precompute_paths_task, stored);
233void RamsetePathFollower::precompute_paths_task(
void* param) {
234 auto* path_names =
static_cast<std::vector<std::string>*
>(param);
236 precomputed_paths.clear();
237 precomputed_paths.reserve(path_names->size());
239 for (
const auto& name : *path_names) {
240 precomputed_paths.push_back(prepare_trajectory(name));
246std::vector<std::pair<double,double>> RamsetePathFollower::parse_pairs(
const std::string& line) {
247 std::vector<std::pair<double,double>> result;
249 bool inside_parens =
false;
250 for (
char c : line) {
253 inside_parens =
true;
254 }
else if (c ==
')') {
255 std::replace(temp.begin(), temp.end(),
',',
' ');
256 std::istringstream ss(temp);
257 double first, second;
258 ss >> first >> second;
259 result.emplace_back(first, second);
260 inside_parens =
false;
261 }
else if (inside_parens) {
268std::vector<State> RamsetePathFollower::prepare_trajectory(
const std::string& data) {
269 std::istringstream ss(data);
270 std::vector<std::pair<double,double>> X, L, A;
272 X.reserve(1000); L.reserve(1000); A.reserve(1000);
274 while (std::getline(ss, line)) {
275 if (line.find(
"X =") != std::string::npos) X = parse_pairs(line.substr(line.find(
'[')));
276 else if (line.find(
"L =") != std::string::npos) L = parse_pairs(line.substr(line.find(
'[')));
277 else if (line.find(
"A =") != std::string::npos) A = parse_pairs(line.substr(line.find(
'[')));
281 if (n == 0)
return {};
283 std::vector<State> states(n);
285 for (
size_t i = 0; i < n; i++) {
286 states[i].x = X[i].first;
287 states[i].y = X[i].second;
288 states[i].linear_vel = L[i].second;
289 states[i].angular_vel = A[i].second;
293 states[0].heading = atan2(states[1].y - states[0].y, states[1].x - states[0].x);
295 for (
size_t i = 1; i < n - 1; i++) {
296 double dy = states[i + 1].y - states[i - 1].y;
297 double dx = states[i + 1].x - states[i - 1].x;
298 states[i].heading = atan2(dy, dx);
301 states[n - 1].heading = atan2(states[n - 1].y - states[n - 2].y, states[n - 1].x - states[n - 2].x);
304 for(
size_t i = 1; i < n; ++i) {
305 double diff = states[i].heading - states[i-1].heading;
306 while (diff > M_PI) diff -= 2 * M_PI;
307 while (diff < -M_PI) diff += 2 * M_PI;
308 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