oxihuman_morph/
ear_lobe_size.rs1#![allow(dead_code)]
3
4#[allow(dead_code)]
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum EarSide {
10 Left,
11 Right,
12}
13
14#[allow(dead_code)]
16#[derive(Debug, Clone, PartialEq)]
17pub struct EarLobeSizeConfig {
18 pub max_size_m: f32,
19 pub max_droop_m: f32,
20}
21
22impl Default for EarLobeSizeConfig {
23 fn default() -> Self {
24 Self {
25 max_size_m: 0.010,
26 max_droop_m: 0.008,
27 }
28 }
29}
30
31#[allow(dead_code)]
33#[derive(Debug, Clone, Default)]
34pub struct EarLobeSizeState {
35 pub left_size: f32,
36 pub right_size: f32,
37 pub left_droop: f32,
38 pub right_droop: f32,
39}
40
41#[allow(dead_code)]
42pub fn new_ear_lobe_size_state() -> EarLobeSizeState {
43 EarLobeSizeState::default()
44}
45
46#[allow(dead_code)]
47pub fn default_ear_lobe_size_config() -> EarLobeSizeConfig {
48 EarLobeSizeConfig::default()
49}
50
51#[allow(dead_code)]
52pub fn els_set_size(state: &mut EarLobeSizeState, side: EarSide, v: f32) {
53 let v = v.clamp(0.0, 1.0);
54 match side {
55 EarSide::Left => state.left_size = v,
56 EarSide::Right => state.right_size = v,
57 }
58}
59
60#[allow(dead_code)]
61pub fn els_set_droop(state: &mut EarLobeSizeState, side: EarSide, v: f32) {
62 let v = v.clamp(0.0, 1.0);
63 match side {
64 EarSide::Left => state.left_droop = v,
65 EarSide::Right => state.right_droop = v,
66 }
67}
68
69#[allow(dead_code)]
70pub fn els_set_both_size(state: &mut EarLobeSizeState, v: f32) {
71 let v = v.clamp(0.0, 1.0);
72 state.left_size = v;
73 state.right_size = v;
74}
75
76#[allow(dead_code)]
77pub fn els_reset(state: &mut EarLobeSizeState) {
78 *state = EarLobeSizeState::default();
79}
80
81#[allow(dead_code)]
82pub fn els_is_neutral(state: &EarLobeSizeState) -> bool {
83 state.left_size < 1e-4
84 && state.right_size < 1e-4
85 && state.left_droop < 1e-4
86 && state.right_droop < 1e-4
87}
88
89#[allow(dead_code)]
90pub fn els_symmetry(state: &EarLobeSizeState) -> f32 {
91 1.0 - (state.left_size - state.right_size).abs()
92}
93
94#[allow(dead_code)]
95pub fn els_to_weights(state: &EarLobeSizeState, cfg: &EarLobeSizeConfig) -> [f32; 4] {
96 [
97 state.left_size * cfg.max_size_m,
98 state.right_size * cfg.max_size_m,
99 state.left_droop * cfg.max_droop_m,
100 state.right_droop * cfg.max_droop_m,
101 ]
102}
103
104#[allow(dead_code)]
105pub fn els_blend(a: &EarLobeSizeState, b: &EarLobeSizeState, t: f32) -> EarLobeSizeState {
106 let t = t.clamp(0.0, 1.0);
107 let inv = 1.0 - t;
108 EarLobeSizeState {
109 left_size: a.left_size * inv + b.left_size * t,
110 right_size: a.right_size * inv + b.right_size * t,
111 left_droop: a.left_droop * inv + b.left_droop * t,
112 right_droop: a.right_droop * inv + b.right_droop * t,
113 }
114}
115
116#[allow(dead_code)]
117pub fn els_to_json(state: &EarLobeSizeState) -> String {
118 format!(
119 "{{\"left_size\":{:.4},\"right_size\":{:.4},\"left_droop\":{:.4},\"right_droop\":{:.4}}}",
120 state.left_size, state.right_size, state.left_droop, state.right_droop
121 )
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 #[test]
129 fn default_is_neutral() {
130 assert!(els_is_neutral(&new_ear_lobe_size_state()));
131 }
132
133 #[test]
134 fn size_clamps() {
135 let mut s = new_ear_lobe_size_state();
136 els_set_size(&mut s, EarSide::Left, 5.0);
137 assert!((s.left_size - 1.0).abs() < 1e-6);
138 }
139
140 #[test]
141 fn droop_clamps() {
142 let mut s = new_ear_lobe_size_state();
143 els_set_droop(&mut s, EarSide::Right, -2.0);
144 assert!(s.right_droop < 1e-6);
145 }
146
147 #[test]
148 fn reset_clears() {
149 let mut s = new_ear_lobe_size_state();
150 els_set_both_size(&mut s, 0.8);
151 els_reset(&mut s);
152 assert!(els_is_neutral(&s));
153 }
154
155 #[test]
156 fn symmetry_one_when_equal() {
157 let mut s = new_ear_lobe_size_state();
158 els_set_both_size(&mut s, 0.5);
159 assert!((els_symmetry(&s) - 1.0).abs() < 1e-6);
160 }
161
162 #[test]
163 fn symmetry_less_when_asymmetric() {
164 let mut s = new_ear_lobe_size_state();
165 els_set_size(&mut s, EarSide::Left, 1.0);
166 assert!(els_symmetry(&s) < 1.0);
167 }
168
169 #[test]
170 fn weights_four_values() {
171 let w = els_to_weights(&new_ear_lobe_size_state(), &default_ear_lobe_size_config());
172 assert_eq!(w.len(), 4);
173 }
174
175 #[test]
176 fn blend_midpoint() {
177 let mut b = new_ear_lobe_size_state();
178 els_set_both_size(&mut b, 1.0);
179 let r = els_blend(&new_ear_lobe_size_state(), &b, 0.5);
180 assert!((r.left_size - 0.5).abs() < 1e-5);
181 }
182
183 #[test]
184 fn json_has_keys() {
185 let j = els_to_json(&new_ear_lobe_size_state());
186 assert!(j.contains("left_size"));
187 }
188
189 #[test]
190 fn set_both_size_equal() {
191 let mut s = new_ear_lobe_size_state();
192 els_set_both_size(&mut s, 0.6);
193 assert!((s.left_size - s.right_size).abs() < 1e-6);
194 }
195}