use std::time::Duration;
use crate::types::{Degrees, DegreesPerSecond, DegreesPerSecondPerSecond};
pub struct TrapezoidalAcceleration {
acceleration: f32,
deceleration: f32,
target_speed: f32,
total_distance: f32,
total_duration: f32,
acceleration_end: f32,
const_end: f32,
deceleration_end: f32,
solved: bool,
}
pub enum AccelerationPhase {
RampUp,
Constant,
RampDown,
Stopping,
Illegal,
}
pub const MIN_SPEED: f32 = 100.0;
pub const STOPPING_DURATION: f32 = 0.5;
pub const STOPPING_LENGTH: f32 = STOPPING_DURATION * MIN_SPEED;
impl TrapezoidalAcceleration {
pub fn new(
Degrees(total_distance): Degrees,
DegreesPerSecond(target_speed): DegreesPerSecond,
DegreesPerSecondPerSecond(acceleration): DegreesPerSecondPerSecond,
DegreesPerSecondPerSecond(deceleration): DegreesPerSecondPerSecond,
) -> Self {
assert!(target_speed >= 0.0, "target_speed must be greater than 0");
assert!(acceleration >= 0.0, "acceleration must be greater than 0");
assert!(deceleration >= 0.0, "deceleration must be greater than 0");
let total_distance = total_distance.abs();
let target_speed = {
let special_acceleration_distance =
(total_distance - STOPPING_LENGTH) * deceleration / (acceleration + deceleration);
let special_target_speed =
(MIN_SPEED * MIN_SPEED + 2.0 * acceleration * special_acceleration_distance).sqrt();
target_speed.min(special_target_speed)
};
let acceleration_duration = (target_speed - MIN_SPEED) / acceleration;
let deceleration_duration = (target_speed - MIN_SPEED) / deceleration;
let constant_duration = {
let average_acceleration_speed = acceleration_duration * acceleration / 2.0 + MIN_SPEED;
let average_deceleration_speed = deceleration_duration * deceleration / 2.0 + MIN_SPEED;
let acceleration_distance = acceleration_duration * average_acceleration_speed;
let deceleration_distance = deceleration_duration * average_deceleration_speed;
let constant_distance =
total_distance - (acceleration_distance + deceleration_distance + STOPPING_LENGTH);
constant_distance / target_speed
};
let total_duration =
acceleration_duration + constant_duration + deceleration_duration + STOPPING_DURATION;
let acceleration_end = acceleration_duration;
let const_end = acceleration_end + constant_duration;
let deceleration_end = const_end + deceleration_duration;
let solved =
target_speed > MIN_SPEED && total_duration.is_normal() && constant_duration > -0.0001;
if solved {
TrapezoidalAcceleration {
acceleration,
deceleration,
target_speed,
total_distance,
total_duration,
acceleration_end,
const_end,
deceleration_end,
solved,
}
} else {
let duration = total_distance / MIN_SPEED;
TrapezoidalAcceleration {
acceleration: 0.0,
deceleration: 0.0,
target_speed: MIN_SPEED,
total_distance,
total_duration: duration,
acceleration_end: 0.0,
const_end: duration,
deceleration_end: duration,
solved,
}
}
}
pub fn get_speed(&self, time: Duration) -> (f32, AccelerationPhase) {
let sec = time.as_secs_f32();
if self.target_speed <= MIN_SPEED {
return (MIN_SPEED, AccelerationPhase::Stopping);
}
if sec < self.acceleration_end {
(
self.acceleration * sec + MIN_SPEED,
AccelerationPhase::RampUp,
)
} else if sec < self.const_end {
(self.target_speed, AccelerationPhase::Constant)
} else if sec < self.deceleration_end {
(
self.target_speed - self.deceleration * (sec - self.const_end),
AccelerationPhase::RampDown,
)
} else if sec < self.total_duration {
(MIN_SPEED, AccelerationPhase::Stopping)
} else {
(MIN_SPEED, AccelerationPhase::Illegal)
}
}
pub fn get_distance(&self, time: Duration) -> f32 {
let sec = time.as_secs_f32();
if self.target_speed <= MIN_SPEED {
return MIN_SPEED * sec;
}
let mut total = 0.0;
{
let in_phase = sec.min(self.acceleration_end).max(0.0);
total += self.acceleration * in_phase * in_phase / 2.0 + MIN_SPEED * in_phase;
}
{
(self.target_speed, AccelerationPhase::Constant);
let in_phase = (sec.min(self.const_end) - self.acceleration_end).max(0.0);
total += self.target_speed * in_phase;
}
{
let in_phase = (sec.min(self.deceleration_end) - self.const_end).max(0.0);
total += self.target_speed * in_phase - self.deceleration * in_phase * in_phase / 2.0;
}
{
let in_phase = (sec.min(self.total_duration) - self.deceleration_end).max(0.0);
total += MIN_SPEED * in_phase;
}
return total;
}
pub fn distance(&self) -> f32 {
self.total_distance
}
pub fn duration(&self) -> f32 {
self.total_duration
}
pub fn target_speed(&self) -> f32 {
self.target_speed
}
pub fn solved(&self) -> bool {
self.solved
}
}