use crate::motion::{magnitude, Twist};
use libm::{atanf, tanf};
#[derive(Clone, Copy, Debug)]
pub struct Ackermann {
wheelbase: f32,
}
impl Ackermann {
pub fn new(wheelbase: f32) -> Self {
Self {
wheelbase: magnitude(wheelbase),
}
}
pub fn steering_angle(&self, linear: f32, angular: f32) -> f32 {
if linear == 0.0 {
return 0.0;
}
atanf(self.wheelbase * angular / linear)
}
pub fn yaw_rate(&self, linear: f32, steering: f32) -> f32 {
if self.wheelbase == 0.0 {
return 0.0;
}
linear * tanf(steering) / self.wheelbase
}
pub fn turn_radius(&self, steering: f32) -> f32 {
let t = tanf(steering);
if t == 0.0 {
return f32::INFINITY;
}
self.wheelbase / t
}
pub fn curvature(&self, steering: f32) -> f32 {
if self.wheelbase == 0.0 {
return 0.0;
}
tanf(steering) / self.wheelbase
}
}
#[derive(Clone, Copy, Debug)]
pub struct SkidSteer {
track: f32,
slip: f32,
}
impl SkidSteer {
pub fn new(track: f32, slip: f32) -> Self {
let slip = magnitude(slip);
Self {
track: magnitude(track),
slip: if slip == 0.0 { 1.0 } else { slip },
}
}
fn effective_track(&self) -> f32 {
self.track * self.slip
}
pub fn wheel_speeds(&self, linear: f32, angular: f32) -> (f32, f32) {
let half = angular * self.effective_track() / 2.0;
(linear - half, linear + half)
}
pub fn body_motion(&self, left: f32, right: f32) -> (f32, f32) {
let linear = (right + left) / 2.0;
let track = self.effective_track();
let angular = if track == 0.0 {
0.0
} else {
(right - left) / track
};
(linear, angular)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct WheelSpeeds {
pub front_left: f32,
pub front_right: f32,
pub rear_left: f32,
pub rear_right: f32,
}
#[derive(Clone, Copy, Debug)]
pub struct Mecanum {
half_length: f32,
half_width: f32,
}
impl Mecanum {
pub fn new(wheelbase: f32, track: f32) -> Self {
Self {
half_length: magnitude(wheelbase) / 2.0,
half_width: magnitude(track) / 2.0,
}
}
fn lever(&self) -> f32 {
self.half_length + self.half_width
}
pub fn wheel_speeds(&self, twist: Twist) -> WheelSpeeds {
let r = self.lever() * twist.omega;
WheelSpeeds {
front_left: twist.vx - twist.vy - r,
front_right: twist.vx + twist.vy + r,
rear_left: twist.vx + twist.vy - r,
rear_right: twist.vx - twist.vy + r,
}
}
pub fn body_motion(&self, wheels: WheelSpeeds) -> Twist {
let WheelSpeeds {
front_left,
front_right,
rear_left,
rear_right,
} = wheels;
let vx = (front_left + front_right + rear_left + rear_right) / 4.0;
let vy = (-front_left + front_right + rear_left - rear_right) / 4.0;
let lever = self.lever();
let omega = if lever == 0.0 {
0.0
} else {
(-front_left + front_right - rear_left + rear_right) / (4.0 * lever)
};
Twist::new(vx, vy, omega)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ackermann_round_trips_steering_and_yaw() {
let car = Ackermann::new(2.5);
let steering = 0.4;
let omega = car.yaw_rate(5.0, steering);
assert!((car.steering_angle(5.0, omega) - steering).abs() < 1e-5);
}
#[test]
fn ackermann_drives_straight_with_no_steering() {
let car = Ackermann::new(2.0);
assert_eq!(car.yaw_rate(3.0, 0.0), 0.0);
assert_eq!(car.turn_radius(0.0), f32::INFINITY);
assert_eq!(car.steering_angle(0.0, 5.0), 0.0); }
#[test]
fn skid_steer_with_unit_slip_is_differential_drive() {
let drive = SkidSteer::new(0.5, 1.0);
assert_eq!(drive.wheel_speeds(1.0, 0.0), (1.0, 1.0)); assert_eq!(drive.wheel_speeds(0.0, 2.0), (-0.5, 0.5)); }
#[test]
fn skid_steer_widens_the_track_by_the_slip_factor() {
let drive = SkidSteer::new(0.5, 1.2); let (left, right) = drive.wheel_speeds(0.0, 2.0);
assert!((left + 0.6).abs() < 1e-6 && (right - 0.6).abs() < 1e-6);
let (linear, angular) = drive.body_motion(left, right);
assert!(linear.abs() < 1e-6 && (angular - 2.0).abs() < 1e-6);
}
#[test]
fn mecanum_handles_each_pure_motion() {
let base = Mecanum::new(0.4, 0.3); assert_eq!(
base.wheel_speeds(Twist::new(1.0, 0.0, 0.0)),
WheelSpeeds {
front_left: 1.0,
front_right: 1.0,
rear_left: 1.0,
rear_right: 1.0,
}
);
let spin = base.wheel_speeds(Twist::new(0.0, 0.0, 1.0));
assert!((spin.front_left + 0.35).abs() < 1e-6);
assert!((spin.front_right - 0.35).abs() < 1e-6);
assert!((spin.rear_left + 0.35).abs() < 1e-6);
assert!((spin.rear_right - 0.35).abs() < 1e-6);
}
#[test]
fn mecanum_round_trips_an_arbitrary_twist() {
let base = Mecanum::new(0.5, 0.4);
let twist = Twist::new(0.8, -0.3, 0.6);
let back = base.body_motion(base.wheel_speeds(twist));
assert!((back.vx - twist.vx).abs() < 1e-6);
assert!((back.vy - twist.vy).abs() < 1e-6);
assert!((back.omega - twist.omega).abs() < 1e-6);
}
}