#![cfg(feature = "std")]
use crate::core::scalar::ControlScalar;
#[derive(Debug, Clone, Copy)]
pub struct TireModel<S: ControlScalar> {
pub cornering_stiffness: S,
pub mu: S,
pub fz: S,
}
impl<S: ControlScalar> TireModel<S> {
pub fn new(cornering_stiffness: S, mu: S, fz: S) -> Self {
Self {
cornering_stiffness,
mu,
fz,
}
}
pub fn lateral_force(&self, slip_angle: S) -> S {
let f_linear = self.cornering_stiffness * slip_angle;
let f_max = self.mu * self.fz;
f_linear.clamp_val(-f_max, f_max)
}
pub fn longitudinal_force(&self, slip_ratio: S) -> S {
let f_raw = self.mu * self.fz * slip_ratio;
let f_max = self.mu * self.fz;
f_raw.clamp_val(-f_max, f_max)
}
}
#[derive(Debug, Clone, Copy)]
pub struct VehicleState<S: ControlScalar> {
pub x: S,
pub y: S,
pub yaw: S,
pub vx: S,
pub vy: S,
pub yaw_rate: S,
}
impl<S: ControlScalar> VehicleState<S> {
pub fn zero() -> Self {
Self {
x: S::ZERO,
y: S::ZERO,
yaw: S::ZERO,
vx: S::ZERO,
vy: S::ZERO,
yaw_rate: S::ZERO,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct VehicleParams<S: ControlScalar> {
pub mass: S,
pub inertia_z: S,
pub lf: S,
pub lr: S,
pub cd: S,
pub frontal_area: S,
pub air_density: S,
}
pub struct VehicleDynamics<S: ControlScalar> {
pub state: VehicleState<S>,
pub params: VehicleParams<S>,
pub front_tire: TireModel<S>,
pub rear_tire: TireModel<S>,
}
impl<S: ControlScalar> VehicleDynamics<S> {
pub fn new(
params: VehicleParams<S>,
front_tire: TireModel<S>,
rear_tire: TireModel<S>,
) -> Self {
Self {
state: VehicleState::zero(),
params,
front_tire,
rear_tire,
}
}
fn aero_drag(&self) -> S {
let half = S::HALF;
let vx2 = self.state.vx * self.state.vx;
half * self.params.air_density * self.params.cd * self.params.frontal_area * vx2
}
pub fn speed(&self) -> S {
let vx2 = self.state.vx * self.state.vx;
let vy2 = self.state.vy * self.state.vy;
(vx2 + vy2).sqrt()
}
pub fn step(&mut self, throttle: S, brake: S, steer: S, dt: S) {
let s = &self.state;
let p = &self.params;
let vx_safe = if s.vx.abs() < S::from_f64(0.1) {
S::from_f64(0.1)
} else {
s.vx
};
let alpha_f = steer - (s.vy + p.lf * s.yaw_rate) / vx_safe;
let alpha_r = -(s.vy - p.lr * s.yaw_rate) / vx_safe;
let fy_f = self.front_tire.lateral_force(alpha_f);
let fy_r = self.rear_tire.lateral_force(alpha_r);
let f_drive = throttle * self.rear_tire.mu * self.rear_tire.fz;
let f_brake_mag = brake
* (self.front_tire.mu * self.front_tire.fz + self.rear_tire.mu * self.rear_tire.fz);
let fx_net = f_drive - f_brake_mag;
let f_drag = self.aero_drag();
let ax = (fx_net - f_drag) / p.mass + s.vy * s.yaw_rate;
let ay = (fy_f + fy_r) / p.mass - s.vx * s.yaw_rate;
let yaw_ddot = (p.lf * fy_f - p.lr * fy_r) / p.inertia_z;
let new_vx = s.vx + ax * dt;
let new_vy = s.vy + ay * dt;
let new_yaw_rate = s.yaw_rate + yaw_ddot * dt;
let new_yaw = s.yaw + s.yaw_rate * dt;
let cos_yaw = s.yaw.cos();
let sin_yaw = s.yaw.sin();
let new_x = s.x + (s.vx * cos_yaw - s.vy * sin_yaw) * dt;
let new_y = s.y + (s.vx * sin_yaw + s.vy * cos_yaw) * dt;
self.state = VehicleState {
x: new_x,
y: new_y,
yaw: new_yaw,
vx: new_vx.clamp_val(S::ZERO, S::from_f64(100.0)),
vy: new_vy,
yaw_rate: new_yaw_rate,
};
}
}
#[cfg(test)]
mod tests {
use super::*;
fn make_vehicle() -> VehicleDynamics<f64> {
let params = VehicleParams {
mass: 1500.0,
inertia_z: 2500.0,
lf: 1.2,
lr: 1.4,
cd: 0.3,
frontal_area: 2.2,
air_density: 1.225,
};
let front_tire = TireModel::new(80_000.0, 0.9, 7357.5);
let rear_tire = TireModel::new(80_000.0, 0.9, 7357.5);
VehicleDynamics::new(params, front_tire, rear_tire)
}
#[test]
fn test_vehicle_accelerates_from_rest() {
let mut veh = make_vehicle();
for _ in 0..100 {
veh.step(0.3, 0.0, 0.0, 0.01);
}
assert!(veh.state.vx > 0.5, "vx={}", veh.state.vx);
assert!(veh.state.x > 0.1, "x={}", veh.state.x);
}
#[test]
fn test_tire_lateral_force_linear() {
let tire = TireModel::<f64>::new(80_000.0, 1.0, 5000.0);
let f = tire.lateral_force(0.05);
let expected = 80_000.0 * 0.05;
assert!((f - expected).abs() < 1.0, "f={f}");
}
#[test]
fn test_tire_lateral_force_saturates() {
let tire = TireModel::<f64>::new(80_000.0, 1.0, 5000.0);
let f = tire.lateral_force(1.0);
assert!((f - 5000.0).abs() < 1.0, "f={f}");
}
#[test]
fn test_aero_drag_increases_with_speed() {
let mut veh = make_vehicle();
veh.state.vx = 10.0;
let drag_low = veh.aero_drag();
veh.state.vx = 30.0;
let drag_high = veh.aero_drag();
assert!(
drag_high > drag_low * 8.0,
"drag_low={drag_low} drag_high={drag_high}"
);
}
#[test]
fn test_yaw_rate_from_steering() {
let mut veh = make_vehicle();
veh.state.vx = 10.0;
let steer = 0.05_f64;
for _ in 0..50 {
veh.step(0.0, 0.0, steer, 0.01);
}
assert!(
veh.state.yaw_rate.abs() > 1e-3,
"yaw_rate={}",
veh.state.yaw_rate
);
}
}