use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct JerkLimitedProfile<S: ControlScalar> {
pub j_max: S,
pub a_max: S,
pub v_max: S,
pos: S,
vel: S,
acc: S,
elapsed: S,
t1: S, t2: S, t3: S, t4: S, t5: S, t6: S, t7: S,
target: S,
pos0: S,
direction: S,
total_duration: S,
done: bool,
}
impl<S: ControlScalar> JerkLimitedProfile<S> {
pub fn new(j_max: S, a_max: S, v_max: S) -> Self {
Self {
j_max,
a_max,
v_max,
pos: S::ZERO,
vel: S::ZERO,
acc: S::ZERO,
elapsed: S::ZERO,
t1: S::ZERO,
t2: S::ZERO,
t3: S::ZERO,
t4: S::ZERO,
t5: S::ZERO,
t6: S::ZERO,
t7: S::ZERO,
target: S::ZERO,
pos0: S::ZERO,
direction: S::ONE,
total_duration: S::ZERO,
done: true,
}
}
pub fn plan(&mut self, target_pos: S) {
let distance = target_pos - self.pos;
if distance.abs() < S::EPSILON {
self.done = true;
return;
}
self.direction = if distance > S::ZERO { S::ONE } else { -S::ONE };
let dist = distance.abs();
self.pos0 = self.pos;
self.target = target_pos;
self.vel = S::ZERO;
self.acc = S::ZERO;
self.elapsed = S::ZERO;
self.done = false;
let t_jerk = self.a_max / self.j_max;
let v_jerk = S::HALF * self.j_max * t_jerk * t_jerk;
let two = S::TWO;
let four = S::from_f64(4.0);
let v_peak = {
let d_needed = v_jerk * t_jerk * two
+ self.v_max / self.a_max * (self.v_max - v_jerk * two)
+ (self.v_max - v_jerk * two).clamp_val(S::ZERO, S::from_f64(1e9)) * S::ZERO;
let _ = d_needed;
let t_ramp = two * t_jerk;
let d_min_for_vmax = self.v_max * (t_ramp + self.v_max / self.a_max) * S::HALF;
if dist >= d_min_for_vmax * two {
self.v_max
} else {
let v_try = (self.j_max * dist / four).sqrt();
v_try.clamp_val(S::ZERO, self.v_max)
}
};
let a_used = self.a_max.clamp_val(S::ZERO, self.a_max);
self.t1 = if v_peak > S::ZERO {
(v_peak / self.j_max).sqrt().min(a_used / self.j_max)
} else {
S::ZERO
};
self.t1 = (a_used / self.j_max).min((v_peak / self.j_max).sqrt());
let v_after_t1 = S::HALF * self.j_max * self.t1 * self.t1;
self.t2 = if v_peak > two * v_after_t1 {
(v_peak - two * v_after_t1) / a_used
} else {
S::ZERO
};
self.t3 = self.t1;
let d_accel = v_after_t1 * self.t1 * S::from_f64(2.0 / 3.0)
+ v_after_t1 * self.t2
+ v_peak * self.t2 / two
+ v_peak * self.t1 * S::from_f64(2.0 / 3.0);
let d_accel_decel = v_peak * (two * self.t1 + self.t2);
let d_const = dist - d_accel_decel;
self.t4 = if d_const > S::ZERO {
d_const / v_peak
} else {
S::ZERO
};
let _ = d_accel;
self.t5 = self.t1;
self.t6 = self.t2;
self.t7 = self.t1;
self.total_duration = self.t1 + self.t2 + self.t3 + self.t4 + self.t5 + self.t6 + self.t7;
}
pub fn update(&mut self, dt: S) -> (S, S, S) {
if self.done {
return (self.target, S::ZERO, S::ZERO);
}
self.elapsed += dt;
let jerk = self.current_jerk();
self.acc += jerk * dt;
self.vel += self.acc * dt;
self.pos += self.vel * dt;
let v_max_dir = self.v_max * self.direction;
if self.direction > S::ZERO {
self.vel = self.vel.clamp_val(-self.v_max, v_max_dir);
} else {
self.vel = self.vel.clamp_val(v_max_dir, self.v_max);
}
if self.elapsed >= self.total_duration {
self.pos = self.target;
self.vel = S::ZERO;
self.acc = S::ZERO;
self.done = true;
}
(self.pos, self.vel, self.acc)
}
fn current_jerk(&self) -> S {
let t = self.elapsed;
let j = self.j_max;
let dir = self.direction;
let seg_end = [
self.t1,
self.t1 + self.t2,
self.t1 + self.t2 + self.t3,
self.t1 + self.t2 + self.t3 + self.t4,
self.t1 + self.t2 + self.t3 + self.t4 + self.t5,
self.t1 + self.t2 + self.t3 + self.t4 + self.t5 + self.t6,
self.total_duration,
];
if t < seg_end[0] {
j * dir } else if t < seg_end[1] {
S::ZERO } else if t < seg_end[2] {
-j * dir } else if t < seg_end[3] {
S::ZERO } else if t < seg_end[4] {
-j * dir } else if t < seg_end[5] {
S::ZERO } else {
j * dir }
}
pub fn position(&self) -> S {
self.pos
}
pub fn velocity(&self) -> S {
self.vel
}
pub fn is_done(&self) -> bool {
self.done
}
pub fn set_position(&mut self, pos: S) {
self.pos = pos;
self.vel = S::ZERO;
self.acc = S::ZERO;
self.done = true;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reaches_target_position() {
let mut prof = JerkLimitedProfile::new(100.0_f64, 10.0, 1.0);
prof.set_position(0.0);
prof.plan(2.0);
let dt = 0.001;
for _ in 0..10000 {
if prof.is_done() {
break;
}
prof.update(dt);
}
assert!(prof.is_done());
assert!(
(prof.position() - 2.0).abs() < 0.1,
"pos={:.4}",
prof.position()
);
}
#[test]
fn velocity_stays_within_limit() {
let mut prof = JerkLimitedProfile::new(50.0_f64, 5.0, 2.0);
prof.set_position(0.0);
prof.plan(10.0);
let dt = 0.001;
let mut max_vel = 0.0_f64;
for _ in 0..20000 {
if prof.is_done() {
break;
}
let (_, v, _) = prof.update(dt);
max_vel = max_vel.max(v.abs());
}
assert!(max_vel <= 2.1, "max_vel={:.3}", max_vel); }
#[test]
fn zero_distance_immediately_done() {
let mut prof = JerkLimitedProfile::new(10.0_f64, 5.0, 1.0);
prof.set_position(3.0);
prof.plan(3.0);
assert!(prof.is_done());
}
}