Skip to main content

oxihuman_morph/
larynx_position_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Larynx height position morph — controls vertical larynx position and tilt.
6
7/// Larynx position morph configuration.
8#[derive(Debug, Clone)]
9pub struct LarynxPositionMorph {
10    pub height: f32,
11    pub tilt: f32,
12    pub anterior_posterior: f32,
13}
14
15impl LarynxPositionMorph {
16    pub fn new() -> Self {
17        Self {
18            height: 0.5,
19            tilt: 0.0,
20            anterior_posterior: 0.5,
21        }
22    }
23}
24
25impl Default for LarynxPositionMorph {
26    fn default() -> Self {
27        Self::new()
28    }
29}
30
31/// Create a new larynx position morph.
32pub fn new_larynx_position_morph() -> LarynxPositionMorph {
33    LarynxPositionMorph::new()
34}
35
36/// Set larynx height (0 = lowered, 1 = raised).
37pub fn larynx_set_height(m: &mut LarynxPositionMorph, v: f32) {
38    m.height = v.clamp(0.0, 1.0);
39}
40
41/// Set larynx tilt angle.
42pub fn larynx_set_tilt(m: &mut LarynxPositionMorph, v: f32) {
43    m.tilt = v.clamp(-1.0, 1.0);
44}
45
46/// Set anterior-posterior position.
47pub fn larynx_set_anterior_posterior(m: &mut LarynxPositionMorph, v: f32) {
48    m.anterior_posterior = v.clamp(0.0, 1.0);
49}
50
51/// Effective tract lengthening from lowered larynx.
52pub fn larynx_tract_lengthening(m: &LarynxPositionMorph) -> f32 {
53    /* lower larynx lengthens the tract */
54    (0.5 - m.height) * 0.4
55}
56
57/// Serialize to JSON-like string.
58pub fn larynx_position_morph_to_json(m: &LarynxPositionMorph) -> String {
59    format!(
60        r#"{{"height":{:.4},"tilt":{:.4},"anterior_posterior":{:.4}}}"#,
61        m.height, m.tilt, m.anterior_posterior
62    )
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn test_defaults() {
71        let m = new_larynx_position_morph();
72        assert!((m.height - 0.5).abs() < 1e-6);
73        assert_eq!(m.tilt, 0.0);
74    }
75
76    #[test]
77    fn test_set_height() {
78        let mut m = new_larynx_position_morph();
79        larynx_set_height(&mut m, 0.1);
80        assert!((m.height - 0.1).abs() < 1e-6);
81    }
82
83    #[test]
84    fn test_height_clamp() {
85        let mut m = new_larynx_position_morph();
86        larynx_set_height(&mut m, 2.0);
87        assert_eq!(m.height, 1.0);
88    }
89
90    #[test]
91    fn test_tilt_negative() {
92        let mut m = new_larynx_position_morph();
93        larynx_set_tilt(&mut m, -0.5);
94        assert!((m.tilt + 0.5).abs() < 1e-6);
95    }
96
97    #[test]
98    fn test_anterior_posterior() {
99        let mut m = new_larynx_position_morph();
100        larynx_set_anterior_posterior(&mut m, 0.2);
101        assert!((m.anterior_posterior - 0.2).abs() < 1e-6);
102    }
103
104    #[test]
105    fn test_tract_lengthening_default() {
106        let m = new_larynx_position_morph();
107        assert!((larynx_tract_lengthening(&m)).abs() < 1e-6); /* neutral height */
108    }
109
110    #[test]
111    fn test_tract_lengthening_lowered() {
112        let mut m = new_larynx_position_morph();
113        larynx_set_height(&mut m, 0.0);
114        assert!(larynx_tract_lengthening(&m) > 0.0); /* lengthened */
115    }
116
117    #[test]
118    fn test_json_keys() {
119        let m = new_larynx_position_morph();
120        let s = larynx_position_morph_to_json(&m);
121        assert!(s.contains("anterior_posterior"));
122    }
123
124    #[test]
125    fn test_clone() {
126        let m = new_larynx_position_morph();
127        let m2 = m.clone();
128        assert!((m2.height - m.height).abs() < 1e-6);
129    }
130}