#![allow(dead_code)]
use std::f32::consts::FRAC_PI_8;
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct JawProtrusionState {
pub protrusion: f32,
pub mandibular_plane_deg: f32,
pub lateral_shift: f32,
}
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct JawProtrusionConfig {
pub max_protrusion: f32,
pub max_plane_deg: f32,
}
impl Default for JawProtrusionConfig {
fn default() -> Self {
Self {
max_protrusion: 1.0,
max_plane_deg: 15.0,
}
}
}
impl Default for JawProtrusionState {
fn default() -> Self {
Self {
protrusion: 0.0,
mandibular_plane_deg: 0.0,
lateral_shift: 0.0,
}
}
}
#[allow(dead_code)]
pub fn new_jaw_protrusion_state() -> JawProtrusionState {
JawProtrusionState::default()
}
#[allow(dead_code)]
pub fn default_jaw_protrusion_config() -> JawProtrusionConfig {
JawProtrusionConfig::default()
}
#[allow(dead_code)]
pub fn jp_set_protrusion(state: &mut JawProtrusionState, cfg: &JawProtrusionConfig, v: f32) {
state.protrusion = v.clamp(-cfg.max_protrusion, cfg.max_protrusion);
}
#[allow(dead_code)]
pub fn jp_set_plane(state: &mut JawProtrusionState, cfg: &JawProtrusionConfig, deg: f32) {
state.mandibular_plane_deg = deg.clamp(-cfg.max_plane_deg, cfg.max_plane_deg);
}
#[allow(dead_code)]
pub fn jp_set_lateral(state: &mut JawProtrusionState, v: f32) {
state.lateral_shift = v.clamp(-1.0, 1.0);
}
#[allow(dead_code)]
pub fn jp_reset(state: &mut JawProtrusionState) {
*state = JawProtrusionState::default();
}
#[allow(dead_code)]
pub fn jp_is_neutral(state: &JawProtrusionState) -> bool {
state.protrusion.abs() < 1e-4
&& state.mandibular_plane_deg.abs() < 1e-4
&& state.lateral_shift.abs() < 1e-4
}
#[allow(dead_code)]
pub fn jp_blend(a: &JawProtrusionState, b: &JawProtrusionState, t: f32) -> JawProtrusionState {
let t = t.clamp(0.0, 1.0);
JawProtrusionState {
protrusion: a.protrusion + (b.protrusion - a.protrusion) * t,
mandibular_plane_deg: a.mandibular_plane_deg
+ (b.mandibular_plane_deg - a.mandibular_plane_deg) * t,
lateral_shift: a.lateral_shift + (b.lateral_shift - a.lateral_shift) * t,
}
}
#[allow(dead_code)]
pub fn jp_horizontal_offset(state: &JawProtrusionState) -> f32 {
state.protrusion * (state.mandibular_plane_deg.to_radians() + FRAC_PI_8).cos()
}
#[allow(dead_code)]
pub fn jp_to_weights(state: &JawProtrusionState) -> [f32; 3] {
[
state.protrusion,
state.mandibular_plane_deg / 15.0,
state.lateral_shift,
]
}
#[allow(dead_code)]
pub fn jp_to_json(state: &JawProtrusionState) -> String {
format!(
"{{\"protrusion\":{:.4},\"plane_deg\":{:.4},\"lateral\":{:.4}}}",
state.protrusion, state.mandibular_plane_deg, state.lateral_shift
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_neutral() {
assert!(jp_is_neutral(&new_jaw_protrusion_state()));
}
#[test]
fn protrusion_clamp_max() {
let mut s = new_jaw_protrusion_state();
let cfg = default_jaw_protrusion_config();
jp_set_protrusion(&mut s, &cfg, 5.0);
assert!(s.protrusion <= cfg.max_protrusion);
}
#[test]
fn protrusion_clamp_min() {
let mut s = new_jaw_protrusion_state();
let cfg = default_jaw_protrusion_config();
jp_set_protrusion(&mut s, &cfg, -5.0);
assert!(s.protrusion >= -cfg.max_protrusion);
}
#[test]
fn plane_clamp() {
let mut s = new_jaw_protrusion_state();
let cfg = default_jaw_protrusion_config();
jp_set_plane(&mut s, &cfg, 999.0);
assert!(s.mandibular_plane_deg <= cfg.max_plane_deg);
}
#[test]
fn lateral_clamp() {
let mut s = new_jaw_protrusion_state();
jp_set_lateral(&mut s, 5.0);
assert!(s.lateral_shift <= 1.0);
}
#[test]
fn reset_neutral() {
let mut s = new_jaw_protrusion_state();
let cfg = default_jaw_protrusion_config();
jp_set_protrusion(&mut s, &cfg, 0.5);
jp_reset(&mut s);
assert!(jp_is_neutral(&s));
}
#[test]
fn blend_half() {
let cfg = default_jaw_protrusion_config();
let mut a = new_jaw_protrusion_state();
let mut b = new_jaw_protrusion_state();
jp_set_protrusion(&mut a, &cfg, 0.0);
jp_set_protrusion(&mut b, &cfg, 1.0);
let m = jp_blend(&a, &b, 0.5);
assert!((m.protrusion - 0.5).abs() < 1e-4);
}
#[test]
fn horizontal_offset_finite() {
let s = new_jaw_protrusion_state();
assert!(jp_horizontal_offset(&s).is_finite());
}
#[test]
fn weights_len() {
assert_eq!(jp_to_weights(&new_jaw_protrusion_state()).len(), 3);
}
#[test]
fn json_has_protrusion() {
assert!(jp_to_json(&new_jaw_protrusion_state()).contains("protrusion"));
}
}