#![allow(dead_code)]
use std::f32::consts::FRAC_PI_4;
#[allow(dead_code)]
#[derive(Debug, Clone, PartialEq)]
pub struct BodyCenterConfig {
pub max_anterior: f32,
pub max_posterior: f32,
pub max_lateral: f32,
}
impl Default for BodyCenterConfig {
fn default() -> Self {
Self {
max_anterior: 0.08,
max_posterior: 0.06,
max_lateral: 0.05,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone, Default)]
pub struct BodyCenterState {
pub ap_shift: f32,
pub lateral_shift: f32,
}
#[allow(dead_code)]
pub fn new_body_center_state() -> BodyCenterState {
BodyCenterState::default()
}
#[allow(dead_code)]
pub fn default_body_center_config() -> BodyCenterConfig {
BodyCenterConfig::default()
}
#[allow(dead_code)]
pub fn bcc_set_ap(state: &mut BodyCenterState, v: f32) {
state.ap_shift = v.clamp(-1.0, 1.0);
}
#[allow(dead_code)]
pub fn bcc_set_lateral(state: &mut BodyCenterState, v: f32) {
state.lateral_shift = v.clamp(-1.0, 1.0);
}
#[allow(dead_code)]
pub fn bcc_reset(state: &mut BodyCenterState) {
*state = BodyCenterState::default();
}
#[allow(dead_code)]
pub fn bcc_is_neutral(state: &BodyCenterState) -> bool {
state.ap_shift.abs() < 1e-4 && state.lateral_shift.abs() < 1e-4
}
#[allow(dead_code)]
pub fn bcc_displacement(state: &BodyCenterState) -> f32 {
(state.ap_shift.powi(2) + state.lateral_shift.powi(2))
.sqrt()
.min(1.0)
}
#[allow(dead_code)]
pub fn bcc_to_weights(state: &BodyCenterState, cfg: &BodyCenterConfig) -> [f32; 2] {
let ap = if state.ap_shift >= 0.0 {
state.ap_shift * cfg.max_anterior
} else {
state.ap_shift * cfg.max_posterior
};
let lat = state.lateral_shift * cfg.max_lateral;
[ap, lat]
}
#[allow(dead_code)]
pub fn bcc_lean_angle_rad(state: &BodyCenterState) -> f32 {
state.ap_shift * FRAC_PI_4 * 0.25
}
#[allow(dead_code)]
pub fn bcc_blend(a: &BodyCenterState, b: &BodyCenterState, t: f32) -> BodyCenterState {
let t = t.clamp(0.0, 1.0);
let inv = 1.0 - t;
BodyCenterState {
ap_shift: a.ap_shift * inv + b.ap_shift * t,
lateral_shift: a.lateral_shift * inv + b.lateral_shift * t,
}
}
#[allow(dead_code)]
pub fn bcc_to_json(state: &BodyCenterState) -> String {
format!(
"{{\"ap_shift\":{:.4},\"lateral_shift\":{:.4}}}",
state.ap_shift, state.lateral_shift
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_is_neutral() {
let s = new_body_center_state();
assert!(bcc_is_neutral(&s));
}
#[test]
fn set_ap_clamps() {
let mut s = new_body_center_state();
bcc_set_ap(&mut s, 5.0);
assert!((s.ap_shift - 1.0).abs() < 1e-6);
bcc_set_ap(&mut s, -5.0);
assert!((s.ap_shift + 1.0).abs() < 1e-6);
}
#[test]
fn set_lateral_clamps() {
let mut s = new_body_center_state();
bcc_set_lateral(&mut s, -3.0);
assert!((s.lateral_shift + 1.0).abs() < 1e-6);
}
#[test]
fn reset_clears() {
let mut s = new_body_center_state();
bcc_set_ap(&mut s, 0.5);
bcc_set_lateral(&mut s, 0.3);
bcc_reset(&mut s);
assert!(bcc_is_neutral(&s));
}
#[test]
fn displacement_zero_at_neutral() {
let s = new_body_center_state();
assert!(bcc_displacement(&s) < 1e-6);
}
#[test]
fn displacement_positive_when_shifted() {
let mut s = new_body_center_state();
bcc_set_ap(&mut s, 0.6);
bcc_set_lateral(&mut s, 0.4);
assert!(bcc_displacement(&s) > 0.5);
}
#[test]
fn weights_sign_matches_direction() {
let cfg = default_body_center_config();
let mut s = new_body_center_state();
bcc_set_ap(&mut s, 1.0);
let w = bcc_to_weights(&s, &cfg);
assert!(w[0] > 0.0);
}
#[test]
fn lean_angle_sign() {
let mut s = new_body_center_state();
bcc_set_ap(&mut s, 1.0);
assert!(bcc_lean_angle_rad(&s) > 0.0);
bcc_set_ap(&mut s, -1.0);
assert!(bcc_lean_angle_rad(&s) < 0.0);
}
#[test]
fn blend_midpoint() {
let mut a = new_body_center_state();
let mut b = new_body_center_state();
bcc_set_ap(&mut a, 0.0);
bcc_set_ap(&mut b, 1.0);
let r = bcc_blend(&a, &b, 0.5);
assert!((r.ap_shift - 0.5).abs() < 1e-5);
}
#[test]
fn json_contains_ap_key() {
let s = new_body_center_state();
let j = bcc_to_json(&s);
assert!(j.contains("ap_shift"));
}
}