oxihuman_morph/
body_hair_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum BodyHairRegion {
11 Arms,
12 Legs,
13 Chest,
14 Abdomen,
15 Back,
16 Shoulders,
17}
18
19impl BodyHairRegion {
20 #[allow(dead_code)]
21 pub fn name(self) -> &'static str {
22 match self {
23 BodyHairRegion::Arms => "arms",
24 BodyHairRegion::Legs => "legs",
25 BodyHairRegion::Chest => "chest",
26 BodyHairRegion::Abdomen => "abdomen",
27 BodyHairRegion::Back => "back",
28 BodyHairRegion::Shoulders => "shoulders",
29 }
30 }
31}
32
33#[allow(dead_code)]
35#[derive(Debug, Clone)]
36pub struct BodyHairEntry {
37 pub region: BodyHairRegion,
38 pub density: f32,
39 pub length: f32,
40 pub color_darkness: f32,
41}
42
43impl BodyHairEntry {
44 #[allow(dead_code)]
45 pub fn new(region: BodyHairRegion) -> Self {
46 BodyHairEntry {
47 region,
48 density: 0.0,
49 length: 0.0,
50 color_darkness: 0.5,
51 }
52 }
53}
54
55#[allow(dead_code)]
57#[derive(Debug, Clone)]
58pub struct BodyHairState {
59 pub entries: Vec<BodyHairEntry>,
60 pub global_density: f32,
61}
62
63#[allow(dead_code)]
64pub fn default_body_hair_state() -> BodyHairState {
65 let entries = vec![
66 BodyHairEntry::new(BodyHairRegion::Arms),
67 BodyHairEntry::new(BodyHairRegion::Legs),
68 BodyHairEntry::new(BodyHairRegion::Chest),
69 BodyHairEntry::new(BodyHairRegion::Abdomen),
70 BodyHairEntry::new(BodyHairRegion::Back),
71 BodyHairEntry::new(BodyHairRegion::Shoulders),
72 ];
73 BodyHairState {
74 entries,
75 global_density: 0.0,
76 }
77}
78
79#[allow(dead_code)]
80pub fn bh_set_global(state: &mut BodyHairState, v: f32) {
81 let v = v.clamp(0.0, 1.0);
82 state.global_density = v;
83 for e in &mut state.entries {
84 e.density = v;
85 }
86}
87
88#[allow(dead_code)]
89pub fn bh_set_region_density(state: &mut BodyHairState, region: BodyHairRegion, v: f32) {
90 for e in &mut state.entries {
91 if e.region == region {
92 e.density = v.clamp(0.0, 1.0);
93 return;
94 }
95 }
96}
97
98#[allow(dead_code)]
99pub fn bh_set_region_length(state: &mut BodyHairState, region: BodyHairRegion, v: f32) {
100 for e in &mut state.entries {
101 if e.region == region {
102 e.length = v.clamp(0.0, 1.0);
103 return;
104 }
105 }
106}
107
108#[allow(dead_code)]
109pub fn bh_reset(state: &mut BodyHairState) {
110 for e in &mut state.entries {
111 e.density = 0.0;
112 e.length = 0.0;
113 e.color_darkness = 0.5;
114 }
115 state.global_density = 0.0;
116}
117
118#[allow(dead_code)]
119pub fn bh_is_smooth(state: &BodyHairState) -> bool {
120 state.entries.iter().all(|e| e.density < 1e-6)
121}
122
123#[allow(dead_code)]
124pub fn bh_average_density(state: &BodyHairState) -> f32 {
125 if state.entries.is_empty() {
126 return 0.0;
127 }
128 let sum: f32 = state.entries.iter().map(|e| e.density).sum();
129 sum / state.entries.len() as f32
130}
131
132#[allow(dead_code)]
133pub fn bh_to_json(state: &BodyHairState) -> String {
134 format!(
135 r#"{{"global_density":{:.4},"region_count":{}}}"#,
136 state.global_density,
137 state.entries.len()
138 )
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn default_is_smooth() {
147 assert!(bh_is_smooth(&default_body_hair_state()));
148 }
149
150 #[test]
151 fn set_global_changes_all() {
152 let mut s = default_body_hair_state();
153 bh_set_global(&mut s, 0.5);
154 assert!(!bh_is_smooth(&s));
155 }
156
157 #[test]
158 fn set_region_density_clamps() {
159 let mut s = default_body_hair_state();
160 bh_set_region_density(&mut s, BodyHairRegion::Chest, 5.0);
161 let e = s
162 .entries
163 .iter()
164 .find(|e| e.region == BodyHairRegion::Chest)
165 .expect("should succeed");
166 assert!((e.density - 1.0).abs() < 1e-6);
167 }
168
169 #[test]
170 fn set_region_length() {
171 let mut s = default_body_hair_state();
172 bh_set_region_length(&mut s, BodyHairRegion::Arms, 0.6);
173 let e = s
174 .entries
175 .iter()
176 .find(|e| e.region == BodyHairRegion::Arms)
177 .expect("should succeed");
178 assert!((e.length - 0.6).abs() < 1e-5);
179 }
180
181 #[test]
182 fn reset_clears() {
183 let mut s = default_body_hair_state();
184 bh_set_global(&mut s, 1.0);
185 bh_reset(&mut s);
186 assert!(bh_is_smooth(&s));
187 }
188
189 #[test]
190 fn average_density_zero_by_default() {
191 assert!(bh_average_density(&default_body_hair_state()).abs() < 1e-6);
192 }
193
194 #[test]
195 fn average_density_after_global() {
196 let mut s = default_body_hair_state();
197 bh_set_global(&mut s, 0.4);
198 assert!((bh_average_density(&s) - 0.4).abs() < 1e-5);
199 }
200
201 #[test]
202 fn region_names_valid() {
203 assert_eq!(BodyHairRegion::Arms.name(), "arms");
204 assert_eq!(BodyHairRegion::Back.name(), "back");
205 }
206
207 #[test]
208 fn to_json_has_global_density() {
209 assert!(bh_to_json(&default_body_hair_state()).contains("global_density"));
210 }
211}