1516X Push Back 1.0
1516X's robot code for the 2025-2026 VEX Robotics Competition
Loading...
Searching...
No Matches
MCL.cpp
Go to the documentation of this file.
1#include "MCL.h"
2#include "globals.h" // Assuming this is where your chassis and sensors are actually instantiated
3#include "pros/rtos.hpp"
4#include <algorithm>
5#include <cmath>
6#include <vector>
7#include <cstdio>
8
9namespace MCL {
10
11 double PARAMS_TRANS_BASE = 0.3;
12 double PARAMS_TRANS_GAIN = 0.025;
13
15
16 float w_slow = 0.0f, w_fast = 0.0f;
17 constexpr float ALPHA_SLOW = 0.001f;
18 constexpr float ALPHA_FAST = 0.1f;
19
20 // ── Field boundary ───────────────────────────────────────────────────
21 constexpr float FIELD_SIZE = 140.42f; // Could be 141.2
22 constexpr float HALF_SIZE = FIELD_SIZE * 0.5f; // 70.21"
23 constexpr float FIELD_MIN = -HALF_SIZE;
24 constexpr float FIELD_MAX = HALF_SIZE;
25
26 // Reject any sensor reading beyond this (inches)
27 constexpr float MAX_SENSOR_READING = 55.0f;
28
29 // ─────────────────────────── Particle Storage ────────────────────────
30 float particle_x[NUM_PARTICLES];
31 float particle_y[NUM_PARTICLES];
32 float particle_weights[NUM_PARTICLES];
33
34 pros::Mutex particle_mutex;
35
36 // ─────────────────────────── Sensor Configuration ──────────────────
37 struct SensorConfig {
38 float x; // offset Right of robot center (inches)
39 float y; // offset Forward of robot center (inches)
40 float angle; // mounting angle relative to robot forward (deg)
41 };
42
43
44 const std::vector<SensorConfig> SENSOR_CONFIGS = {
45 { 3.66f, 4.067f + 2.5, 0.0f }, // 0: front
46 { -6.309f -1.1, -0.068f, 90.0f }, // 1: right
47 { 3.93f, -2.691f + 2.7, 180.0f }, // 2: back
48 { -6.039f + 1.4, -0.068f, -90.0f } // 3: left
49 };
50
51 struct XorShift32 {
52 uint32_t state;
53 explicit XorShift32(uint32_t seed = pros::micros())
54 : state(seed == 0 ? 0x12345678u : seed) {}
55 inline uint32_t next_u32() {
56 uint32_t x = state;
57 x ^= x << 13; x ^= x >> 17; x ^= x << 5;
58 return state = x;
59 }
60 inline float next_f32() { return (next_u32() >> 8) * (1.0f / (1u << 24)); }
61 inline float uniform(float lo, float hi) { return lo + next_f32() * (hi - lo); }
62 inline float gaussian(float std_dev) {
63 const float u1 = std::max(next_f32(), 1e-12f);
64 const float u2 = next_f32();
65 return std_dev * std::sqrt(-2.0f * std::log(u1))
66 * std::cos(2.0f * (float)M_PI * u2);
67 }
68 } rng;
69
70 // ─────────────────────────── Utilities ──────────────────────────────
71 inline float degToRad(float d) { return d * (float)M_PI / 180.0f; }
72 inline float wrapAngle(float a) {
73 a = std::fmod(a + 180.0f, 360.0f);
74 if (a < 0.0f) a += 360.0f;
75 return a - 180.0f;
76 }
77
78 // ─────────────────────────── Initialisation ─────────────────────────
79 void StartMCL(double x, double y) {
80 particle_mutex.take();
81 rng = XorShift32(pros::micros());
82 for (int i = 0; i < NUM_PARTICLES; ++i) {
83 particle_x[i] = std::clamp<float>(
84 (float)x + rng.gaussian(2.0f), FIELD_MIN + 0.1f, FIELD_MAX - 0.1f);
85 particle_y[i] = std::clamp<float>(
86 (float)y + rng.gaussian(2.0f), FIELD_MIN + 0.1f, FIELD_MAX - 0.1f);
87 particle_weights[i] = 1.0f / NUM_PARTICLES;
88 }
89 w_slow = w_fast = 1.0f / NUM_PARTICLES;
90 particle_mutex.give();
91 }
92
93 // ─────────────────────────── Motion Update ──────────────────────────
94 void MotionUpdate(double dX_global, double dY_global, double dTheta, double robot_theta_deg) {
95 const float dist = (float)std::hypot(dX_global, dY_global);
96 const float turn_factor = std::abs((float)dTheta) * 0.05f;
97
98 const float c = 1.0f - std::clamp((float)global_Confidence, 0.0f, 1.0f);
99 const float transStd = (float)(PARAMS_TRANS_BASE + c * 0.3)
100 + (float)(PARAMS_TRANS_GAIN + c * 0.04) * (dist + turn_factor);
101
102 float math_theta_deg = 90.0f - (float)robot_theta_deg;
103 const float theta_rad = degToRad(math_theta_deg);
104
105 const float cos_t = std::cos(theta_rad);
106 const float sin_t = std::sin(theta_rad);
107
108 particle_mutex.take();
109 for (int i = 0; i < NUM_PARTICLES; ++i) {
110 const float nF = rng.gaussian(transStd);
111 const float nS = rng.gaussian(transStd * 0.6f);
112
113 const float noise_X = nF * cos_t - nS * sin_t;
114 const float noise_Y = nF * sin_t + nS * cos_t;
115
116 particle_x[i] = std::clamp<float>(
117 particle_x[i] + (float)dX_global + noise_X,
118 FIELD_MIN + 0.1f, FIELD_MAX - 0.1f);
119 particle_y[i] = std::clamp<float>(
120 particle_y[i] + (float)dY_global + noise_Y,
121 FIELD_MIN + 0.1f, FIELD_MAX - 0.1f);
122 }
123 particle_mutex.give();
124 }
125
126 // ─────────────────────────── Dynamic Raycast Sensor Update ──────────
127 void SensorUpdate(const std::vector<float>& measurements, float robot_theta_deg, float current_confidence) {
128 particle_mutex.take();
129 float sum_w = 0.0f;
130
131 float math_theta_deg = 90.0f - robot_theta_deg;
132 const float theta_rad = degToRad(math_theta_deg);
133 const float cos_t = std::cos(theta_rad);
134 const float sin_t = std::sin(theta_rad);
135
136 const float dynamic_sensor_sig = 1.5f + (1.0f - std::clamp(current_confidence, 0.0f, 1.0f)) * 4.0f;
137 const float dynamic_margin = dynamic_sensor_sig * 3.0f;
138
139 for (int i = 0; i < NUM_PARTICLES; ++i) {
140 float w = 1.0f;
141
142 for (size_t s = 0; s < measurements.size(); ++s) {
143 if (measurements[s] < 0.0f) continue;
144
145 const SensorConfig& sc = SENSOR_CONFIGS[s];
146
147 float sensor_x = particle_x[i] + sc.y * cos_t + sc.x * sin_t;
148 float sensor_y = particle_y[i] + sc.y * sin_t - sc.x * cos_t;
149
150 float absolute_lemlib_deg = robot_theta_deg + sc.angle;
151 float math_beam_deg = 90.0f - absolute_lemlib_deg;
152 const float beam_rad = degToRad(math_beam_deg);
153
154 float v_x = std::cos(beam_rad);
155 float v_y = std::sin(beam_rad);
156
157 float d_x = 999.0f;
158 if (v_x > 1e-4f) d_x = (FIELD_MAX - sensor_x) / v_x;
159 else if (v_x < -1e-4f) d_x = (FIELD_MIN - sensor_x) / v_x;
160
161 float d_y = 999.0f;
162 if (v_y > 1e-4f) d_y = (FIELD_MAX - sensor_y) / v_y;
163 else if (v_y < -1e-4f) d_y = (FIELD_MIN - sensor_y) / v_y;
164
165 float expected = std::min(d_x, d_y);
166 float err = measurements[s] - expected;
167
168 if (err > dynamic_margin) {
169 w *= 0.001f;
170 } else if (err < -dynamic_margin) {
171 w *= 0.4f;
172 } else {
173 w *= std::exp(-0.5f * err * err / (dynamic_sensor_sig * dynamic_sensor_sig));
174 }
175 }
176
177 particle_weights[i] = w;
178 sum_w += w;
179 }
180
181 const float w_avg = sum_w / NUM_PARTICLES;
182 if (w_slow < 1e-10f) w_slow = w_avg;
183 if (w_fast < 1e-10f) w_fast = w_avg;
184 w_slow += ALPHA_SLOW * (w_avg - w_slow);
185 w_fast += ALPHA_FAST * (w_avg - w_fast);
186
187 if (sum_w > 1e-10f) {
188 for (int i = 0; i < NUM_PARTICLES; ++i)
189 particle_weights[i] /= sum_w;
190 } else {
191 const float u = 1.0f / NUM_PARTICLES;
192 for (int i = 0; i < NUM_PARTICLES; ++i)
193 particle_weights[i] = u;
194 }
195
196 particle_mutex.give();
197 }
198
199 // ─────────────────────────── ESS & Resampling ────────────────────────
200 float computeESS() {
201 particle_mutex.take();
202 float sq = 0.0f;
203 for (int i = 0; i < NUM_PARTICLES; ++i)
205 particle_mutex.give();
206 return (sq > 1e-20f) ? 1.0f / sq : 0.0f;
207 }
208
209 void Resample() {
210 particle_mutex.take();
211
212 const float ratio = (w_slow > 1e-10f) ? (w_fast / w_slow) : 1.0f;
213 const float inject_rate = std::clamp(1.0f - ratio, 0.0f, 0.20f);
214 const int num_inject = (int)(NUM_PARTICLES * inject_rate);
215 const int num_keep = NUM_PARTICLES - num_inject;
216
217 static float new_x[NUM_PARTICLES];
218 static float new_y[NUM_PARTICLES];
219
220 float step = 1.0f / (num_keep > 0 ? num_keep : 1);
221 float r = rng.next_f32() * step;
222 float cum = particle_weights[0];
223 int j = 0;
224
225 // Keep the high-performing particles
226 for (int m = 0; m < num_keep; ++m) {
227 float u = r + (float)m * step;
228 while (u > cum && j < NUM_PARTICLES - 1) {
229 cum += particle_weights[++j];
230 }
231 new_x[m] = particle_x[j];
232 new_y[m] = particle_y[j];
233 }
234
235 // Inject new particles locally around our best guess in a tight cluster (1.5" std dev)
236 for (int m = num_keep; m < NUM_PARTICLES; ++m) {
237 new_x[m] = std::clamp<float>(global_X + rng.gaussian(1.5f), FIELD_MIN + 1.0f, FIELD_MAX - 1.0f);
238 new_y[m] = std::clamp<float>(global_Y + rng.gaussian(1.5f), FIELD_MIN + 1.0f, FIELD_MAX - 1.0f);
239 }
240
241 const float uniform_w = 1.0f / NUM_PARTICLES;
242 for (int i = 0; i < NUM_PARTICLES; ++i) {
243 particle_x[i] = new_x[i];
244 particle_y[i] = new_y[i];
245 particle_weights[i] = uniform_w;
246 }
247
248 particle_mutex.give();
249 }
250
251 // ─────────────────────────── Main Loop ──────────────────────────────
252 void MonteCarlo() {
253 lemlib::Pose prevOdom = chassis.getPose();
254 uint32_t now = pros::millis();
255
256 int print_counter = 0;
257 bool first_run = true; // Added for the EMA filter
258
259 while (true) {
260 lemlib::Pose currOdom = chassis.getPose();
261
262 const double dX_global = currOdom.x - prevOdom.x;
263 const double dY_global = currOdom.y - prevOdom.y;
264 const double dTheta = wrapAngle((float)(currOdom.theta - prevOdom.theta));
265
266 if (std::abs(dX_global) > 0.001 || std::abs(dY_global) > 0.001 || std::abs(dTheta) > 0.1) {
267
268 MotionUpdate(dX_global, dY_global, dTheta, currOdom.theta);
269
270 std::vector<float> measurements(4, -1.0f);
271 bool has_valid_reading = false;
272
273 auto try_read_sensor = [&](auto& sensor, int index) {
274 float val = sensor.get() / 25.4f;
275 if (val > 2.0f && val < MAX_SENSOR_READING) {
276 measurements[index] = val;
277 has_valid_reading = true;
278 }
279 };
280
281 try_read_sensor(frontDistance, 0);
282 try_read_sensor(rightDistance, 1);
283 try_read_sensor(backDistance, 2);
284 try_read_sensor(leftDistance, 3);
285
286 if (has_valid_reading) {
287 SensorUpdate(measurements, currOdom.theta, global_Confidence);
288 }
289
290 particle_mutex.take();
291
292 float max_w = -1.0f;
293 int best_idx = 0;
294 for (int i = 0; i < NUM_PARTICLES; ++i) {
295 if (particle_weights[i] > max_w) {
296 max_w = particle_weights[i];
297 best_idx = i;
298 }
299 }
300
301 float best_x = particle_x[best_idx];
302 float best_y = particle_y[best_idx];
303
304 float sumX = 0.0f, sumY = 0.0f, sumW = 0.0f;
305 float sumX2 = 0.0f, sumY2 = 0.0f;
306 const float CLUSTER_RADIUS = 15.0f;
307
308 for (int i = 0; i < NUM_PARTICLES; ++i) {
309 float dx = particle_x[i] - best_x;
310 float dy = particle_y[i] - best_y;
311
312 if ((dx * dx + dy * dy) <= (CLUSTER_RADIUS * CLUSTER_RADIUS)) {
313 float w = particle_weights[i];
314 sumX += w * particle_x[i];
315 sumY += w * particle_y[i];
316 sumX2 += w * particle_x[i] * particle_x[i];
317 sumY2 += w * particle_y[i] * particle_y[i];
318 sumW += w;
319 }
320 }
321
322 float mcl_std_dev = 999.0f;
323 float cluster_weight_ratio = 0.0f;
324
325 // --- RAW CLUSTER CALCULATION ---
326 float raw_X = best_x;
327 float raw_Y = best_y;
328
329 if (sumW > 1e-6f) {
330 raw_X = sumX / sumW;
331 raw_Y = sumY / sumW;
332
333 float meanX = raw_X;
334 float meanY = raw_Y;
335 float varX = (sumX2 / sumW) - (meanX * meanX);
336 float varY = (sumY2 / sumW) - (meanY * meanY);
337 mcl_std_dev = std::sqrt(std::max(0.0f, varX + varY));
338
339 cluster_weight_ratio = sumW;
340 }
341
342 // --- EXPONENTIAL MOVING AVERAGE (EMA) FILTER ---
343 const float EMA_ALPHA = 0.20f;
344
345 if (first_run) {
346 global_X = raw_X;
347 global_Y = raw_Y;
348 first_run = false;
349 } else {
350 global_X = global_X + EMA_ALPHA * (raw_X - global_X);
351 global_Y = global_Y + EMA_ALPHA * (raw_Y - global_Y);
352 }
353 // -----------------------------------------------
354
355 global_Theta = currOdom.theta;
356 global_Confidence = (w_slow > 1e-10f) ? std::min(w_fast / w_slow, 1.0f) : 0.0f;
357
358 particle_mutex.give();
359
360 if (++print_counter >= 3) {
361 printf("MCL: %.2f %.2f | ODOM: %.2f %.2f | CONF: %.2f | STD: %.2f | RATIO: %.2f\n",
362 global_X, global_Y, currOdom.x, currOdom.y, global_Confidence, mcl_std_dev, cluster_weight_ratio);
363 print_counter = 0;
364 }
365
366 if (global_Confidence > 0.65 && cluster_weight_ratio > 0.50f &&
367 std::abs(dX_global) < 5 && std::abs(dY_global) < 5) {
368
369 double diff_x = global_X - currOdom.x;
370 double diff_y = global_Y - currOdom.y;
371 double correction_mag = std::hypot(diff_x, diff_y);
372
373 if (correction_mag > 1.0 && correction_mag < 12.0) {
374 lemlib::Pose fusedPose(global_X, global_Y, currOdom.theta);
375 chassis.setPose(fusedPose);
376 currOdom = chassis.getPose();
377 }
378 }
379
380 const float ess = computeESS();
381 const float ratio = (w_slow > 1e-10f) ? (w_fast / w_slow) : 1.0f;
382 if (ess < NUM_PARTICLES * 0.5f || ratio < 0.9f) {
383 Resample();
384 }
385 }
386
387 prevOdom = currOdom;
388 pros::Task::delay_until(&now, 10);
389 }
390 }
391
392} // namespace MCL
393
pros::Distance backDistance(7)
Chassis chassis(drivebase, lateral_controller, angular_controller, sensors, &throttle_curve, &steer_curve)
pros::Distance leftDistance(5)
pros::Distance rightDistance(6)
pros::Distance frontDistance(8)
Definition MCL.cpp:9
constexpr float HALF_SIZE
Definition MCL.cpp:22
double PARAMS_TRANS_BASE
Definition MCL.cpp:11
double global_Theta
Definition MCL.cpp:14
void StartMCL(double x, double y)
Definition MCL.cpp:79
void MotionUpdate(double dX_global, double dY_global, double dTheta, double robot_theta_deg)
Definition MCL.cpp:94
double global_Y
Definition MCL.cpp:14
double global_Confidence
Definition MCL.cpp:14
float particle_x[NUM_PARTICLES]
Definition MCL.cpp:30
void MonteCarlo()
Definition MCL.cpp:252
constexpr float ALPHA_FAST
Definition MCL.cpp:18
float particle_y[NUM_PARTICLES]
Definition MCL.cpp:31
float degToRad(float d)
Definition MCL.cpp:71
double PARAMS_TRANS_GAIN
Definition MCL.cpp:12
float wrapAngle(float a)
Definition MCL.cpp:72
float particle_weights[NUM_PARTICLES]
Definition MCL.cpp:32
constexpr float FIELD_MIN
Definition MCL.cpp:23
constexpr float MAX_SENSOR_READING
Definition MCL.cpp:27
pros::Mutex particle_mutex
Definition MCL.cpp:34
const std::vector< SensorConfig > SENSOR_CONFIGS
Definition MCL.cpp:44
float w_slow
Definition MCL.cpp:16
double global_X
Definition MCL.cpp:14
float w_fast
Definition MCL.cpp:16
struct MCL::XorShift32 rng
void SensorUpdate(const std::vector< float > &measurements, float robot_theta_deg, float current_confidence)
Definition MCL.cpp:127
float computeESS()
Definition MCL.cpp:200
constexpr float FIELD_SIZE
Definition MCL.cpp:21
void Resample()
Definition MCL.cpp:209
constexpr float ALPHA_SLOW
Definition MCL.cpp:17
constexpr float FIELD_MAX
Definition MCL.cpp:24
uint32_t state
Definition MCL.cpp:52
XorShift32(uint32_t seed=pros::micros())
Definition MCL.cpp:53
uint32_t next_u32()
Definition MCL.cpp:55
float gaussian(float std_dev)
Definition MCL.cpp:62
float next_f32()
Definition MCL.cpp:60
float uniform(float lo, float hi)
Definition MCL.cpp:61