use crate::{RagdollError, RagdollRef};
#[derive(Clone, Debug, PartialEq)]
pub struct SettleDetector {
max_linear_speed: f32,
max_angular_speed: f32,
calm_ticks: u32,
calm_so_far: u32,
}
impl Default for SettleDetector {
fn default() -> Self {
Self {
max_linear_speed: 0.05,
max_angular_speed: 0.1,
calm_ticks: 30,
calm_so_far: 0,
}
}
}
impl SettleDetector {
pub fn new(
max_linear_speed: f32,
max_angular_speed: f32,
calm_ticks: u32,
) -> Result<Self, RagdollError> {
let positive = |value: f32| value.is_finite() && value > 0.0;
if !(positive(max_linear_speed) && positive(max_angular_speed)) {
return Err(RagdollError::InvalidValue(
"settle speed limits must be finite and positive",
));
}
if calm_ticks == 0 {
return Err(RagdollError::InvalidValue(
"settling needs at least one calm tick",
));
}
Ok(Self {
max_linear_speed,
max_angular_speed,
calm_ticks,
calm_so_far: 0,
})
}
pub fn update(&mut self, ragdoll: &RagdollRef<'_>) -> bool {
self.record(ragdoll.is_calm(self.max_linear_speed, self.max_angular_speed))
}
pub fn reset(&mut self) {
self.calm_so_far = 0;
}
pub fn calm_ticks(&self) -> u32 {
self.calm_ticks
}
fn record(&mut self, calm: bool) -> bool {
self.calm_so_far = if calm {
self.calm_so_far.saturating_add(1)
} else {
0
};
self.calm_so_far >= self.calm_ticks
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn calm_updates_in_a_row_settle() {
let mut detector = SettleDetector::new(0.05, 0.1, 3).unwrap();
assert_eq!(detector.calm_ticks(), 3);
assert!(!detector.record(true));
assert!(!detector.record(true));
assert!(!detector.record(false));
assert!(!detector.record(true));
assert!(!detector.record(true));
assert!(detector.record(true));
assert!(detector.record(true));
detector.reset();
assert!(!detector.record(true));
}
#[test]
fn default_is_the_rest_rule() {
let detector = SettleDetector::default();
assert_eq!(detector, SettleDetector::new(0.05, 0.1, 30).unwrap());
}
#[test]
fn invalid_limits_are_rejected() {
for (linear, angular, ticks) in [
(0.0, 0.1, 30),
(0.05, f32::NAN, 30),
(f32::INFINITY, 0.1, 30),
(0.05, 0.1, 0),
] {
assert!(matches!(
SettleDetector::new(linear, angular, ticks),
Err(RagdollError::InvalidValue(_))
));
}
}
}