oxihuman_morph/
beard_density_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum BeardZone {
11 Mustache,
12 ChinBeard,
13 Cheeks,
14 Sideburns,
15 Neck,
16}
17
18impl BeardZone {
19 #[allow(dead_code)]
20 pub fn name(self) -> &'static str {
21 match self {
22 BeardZone::Mustache => "mustache",
23 BeardZone::ChinBeard => "chin_beard",
24 BeardZone::Cheeks => "cheeks",
25 BeardZone::Sideburns => "sideburns",
26 BeardZone::Neck => "neck",
27 }
28 }
29}
30
31#[allow(dead_code)]
33#[derive(Debug, Clone)]
34pub struct BeardZoneEntry {
35 pub zone: BeardZone,
36 pub density: f32,
37 pub length: f32,
38 pub thickness: f32,
39}
40
41impl BeardZoneEntry {
42 #[allow(dead_code)]
43 pub fn new(zone: BeardZone) -> Self {
44 BeardZoneEntry {
45 zone,
46 density: 0.0,
47 length: 0.0,
48 thickness: 0.0,
49 }
50 }
51}
52
53#[allow(dead_code)]
55#[derive(Debug, Clone)]
56pub struct BeardDensityState {
57 pub zones: Vec<BeardZoneEntry>,
58 pub global_density: f32,
59}
60
61#[allow(dead_code)]
62pub fn default_beard_density_state() -> BeardDensityState {
63 let zones = vec![
64 BeardZoneEntry::new(BeardZone::Mustache),
65 BeardZoneEntry::new(BeardZone::ChinBeard),
66 BeardZoneEntry::new(BeardZone::Cheeks),
67 BeardZoneEntry::new(BeardZone::Sideburns),
68 BeardZoneEntry::new(BeardZone::Neck),
69 ];
70 BeardDensityState {
71 zones,
72 global_density: 0.0,
73 }
74}
75
76#[allow(dead_code)]
77pub fn bd_set_global(state: &mut BeardDensityState, v: f32) {
78 state.global_density = v.clamp(0.0, 1.0);
79 for z in &mut state.zones {
80 z.density = v.clamp(0.0, 1.0);
81 }
82}
83
84#[allow(dead_code)]
85pub fn bd_set_zone_density(state: &mut BeardDensityState, zone: BeardZone, v: f32) {
86 for z in &mut state.zones {
87 if z.zone == zone {
88 z.density = v.clamp(0.0, 1.0);
89 return;
90 }
91 }
92}
93
94#[allow(dead_code)]
95pub fn bd_set_zone_length(state: &mut BeardDensityState, zone: BeardZone, v: f32) {
96 for z in &mut state.zones {
97 if z.zone == zone {
98 z.length = v.clamp(0.0, 1.0);
99 return;
100 }
101 }
102}
103
104#[allow(dead_code)]
105pub fn bd_reset(state: &mut BeardDensityState) {
106 for z in &mut state.zones {
107 z.density = 0.0;
108 z.length = 0.0;
109 z.thickness = 0.0;
110 }
111 state.global_density = 0.0;
112}
113
114#[allow(dead_code)]
115pub fn bd_is_clean_shaven(state: &BeardDensityState) -> bool {
116 state.zones.iter().all(|z| z.density < 1e-6)
117}
118
119#[allow(dead_code)]
120pub fn bd_average_density(state: &BeardDensityState) -> f32 {
121 if state.zones.is_empty() {
122 return 0.0;
123 }
124 let sum: f32 = state.zones.iter().map(|z| z.density).sum();
125 sum / state.zones.len() as f32
126}
127
128#[allow(dead_code)]
129pub fn bd_to_json(state: &BeardDensityState) -> String {
130 format!(
131 r#"{{"global_density":{:.4},"zone_count":{}}}"#,
132 state.global_density,
133 state.zones.len()
134 )
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn default_is_clean_shaven() {
143 assert!(bd_is_clean_shaven(&default_beard_density_state()));
144 }
145
146 #[test]
147 fn set_global_density() {
148 let mut s = default_beard_density_state();
149 bd_set_global(&mut s, 0.8);
150 assert!((s.global_density - 0.8).abs() < 1e-5);
151 }
152
153 #[test]
154 fn set_global_sets_all_zones() {
155 let mut s = default_beard_density_state();
156 bd_set_global(&mut s, 0.5);
157 assert!(!bd_is_clean_shaven(&s));
158 }
159
160 #[test]
161 fn set_zone_density_clamps() {
162 let mut s = default_beard_density_state();
163 bd_set_zone_density(&mut s, BeardZone::Mustache, 2.0);
164 let z = s
165 .zones
166 .iter()
167 .find(|z| z.zone == BeardZone::Mustache)
168 .expect("should succeed");
169 assert!((z.density - 1.0).abs() < 1e-6);
170 }
171
172 #[test]
173 fn reset_clears_all() {
174 let mut s = default_beard_density_state();
175 bd_set_global(&mut s, 1.0);
176 bd_reset(&mut s);
177 assert!(bd_is_clean_shaven(&s));
178 }
179
180 #[test]
181 fn average_density_zero_default() {
182 let s = default_beard_density_state();
183 assert!(bd_average_density(&s).abs() < 1e-6);
184 }
185
186 #[test]
187 fn average_density_after_global_set() {
188 let mut s = default_beard_density_state();
189 bd_set_global(&mut s, 0.6);
190 assert!((bd_average_density(&s) - 0.6).abs() < 1e-5);
191 }
192
193 #[test]
194 fn zone_length_set() {
195 let mut s = default_beard_density_state();
196 bd_set_zone_length(&mut s, BeardZone::ChinBeard, 0.7);
197 let z = s
198 .zones
199 .iter()
200 .find(|z| z.zone == BeardZone::ChinBeard)
201 .expect("should succeed");
202 assert!((z.length - 0.7).abs() < 1e-5);
203 }
204
205 #[test]
206 fn to_json_has_global_density() {
207 assert!(bd_to_json(&default_beard_density_state()).contains("global_density"));
208 }
209}