#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct GazeConstraintConfig {
pub max_yaw_deg: f32,
pub max_pitch_deg: f32,
pub soft_limit_ratio: f32,
}
impl Default for GazeConstraintConfig {
fn default() -> Self {
Self { max_yaw_deg: 35.0, max_pitch_deg: 25.0, soft_limit_ratio: 0.8 }
}
}
#[derive(Debug, Clone, Default)]
pub struct GazeConstraintState {
pub yaw_deg: f32,
pub pitch_deg: f32,
pub config: GazeConstraintConfig,
}
impl GazeConstraintState {
pub fn new() -> Self {
Self { config: GazeConstraintConfig::default(), ..Default::default() }
}
}
pub fn gc_set_gaze(state: &mut GazeConstraintState, yaw: f32, pitch: f32) {
state.yaw_deg = yaw.clamp(-state.config.max_yaw_deg, state.config.max_yaw_deg);
state.pitch_deg = pitch.clamp(-state.config.max_pitch_deg, state.config.max_pitch_deg);
}
pub fn gc_is_comfortable(state: &GazeConstraintState) -> bool {
let r = state.config.soft_limit_ratio;
state.yaw_deg.abs() <= state.config.max_yaw_deg * r
&& state.pitch_deg.abs() <= state.config.max_pitch_deg * r
}
pub fn gc_normalised_yaw(state: &GazeConstraintState) -> f32 {
if state.config.max_yaw_deg < 1e-6 {
return 0.0;
}
state.yaw_deg / state.config.max_yaw_deg
}
pub fn gc_normalised_pitch(state: &GazeConstraintState) -> f32 {
if state.config.max_pitch_deg < 1e-6 {
return 0.0;
}
state.pitch_deg / state.config.max_pitch_deg
}
pub fn gc_reset(state: &mut GazeConstraintState) {
state.yaw_deg = 0.0;
state.pitch_deg = 0.0;
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_initial_neutral() {
let s = GazeConstraintState::new();
assert_eq!(s.yaw_deg, 0.0);
assert_eq!(s.pitch_deg, 0.0);
}
#[test]
fn test_set_gaze_clamps() {
let mut s = GazeConstraintState::new();
gc_set_gaze(&mut s, 999.0, -999.0);
assert!((s.yaw_deg - s.config.max_yaw_deg).abs() < 1e-6);
assert!((s.pitch_deg + s.config.max_pitch_deg).abs() < 1e-6);
}
#[test]
fn test_comfortable() {
let mut s = GazeConstraintState::new();
gc_set_gaze(&mut s, 5.0, 5.0);
assert!(gc_is_comfortable(&s));
}
#[test]
fn test_uncomfortable() {
let mut s = GazeConstraintState::new();
let max = s.config.max_yaw_deg;
gc_set_gaze(&mut s, max, 0.0);
assert!(!gc_is_comfortable(&s));
}
#[test]
fn test_normalised_yaw() {
let mut s = GazeConstraintState::new();
let half = s.config.max_yaw_deg / 2.0;
gc_set_gaze(&mut s, half, 0.0);
assert!((gc_normalised_yaw(&s) - 0.5).abs() < 1e-5);
}
#[test]
fn test_normalised_pitch() {
let mut s = GazeConstraintState::new();
let half = s.config.max_pitch_deg / 2.0;
gc_set_gaze(&mut s, 0.0, -half);
assert!((gc_normalised_pitch(&s) + 0.5).abs() < 1e-5);
}
#[test]
fn test_reset() {
let mut s = GazeConstraintState::new();
gc_set_gaze(&mut s, 20.0, 10.0);
gc_reset(&mut s);
assert_eq!(s.yaw_deg, 0.0);
assert_eq!(s.pitch_deg, 0.0);
}
#[test]
fn test_default_config_limits() {
let cfg = GazeConstraintConfig::default();
assert!(cfg.max_yaw_deg > 0.0);
assert!(cfg.max_pitch_deg > 0.0);
}
#[test]
fn test_soft_limit_ratio_range() {
let cfg = GazeConstraintConfig::default();
assert!((0.0..=1.0).contains(&cfg.soft_limit_ratio));
}
}