oxihuman_morph/
hair_curl_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum CurlPattern {
11 Straight,
12 Wavy,
13 Curly,
14 Coily,
15 Kinky,
16}
17
18impl CurlPattern {
19 #[allow(dead_code)]
20 pub fn name(self) -> &'static str {
21 match self {
22 CurlPattern::Straight => "straight",
23 CurlPattern::Wavy => "wavy",
24 CurlPattern::Curly => "curly",
25 CurlPattern::Coily => "coily",
26 CurlPattern::Kinky => "kinky",
27 }
28 }
29
30 #[allow(dead_code)]
31 pub fn curl_index(self) -> f32 {
32 match self {
33 CurlPattern::Straight => 0.0,
34 CurlPattern::Wavy => 0.25,
35 CurlPattern::Curly => 0.5,
36 CurlPattern::Coily => 0.75,
37 CurlPattern::Kinky => 1.0,
38 }
39 }
40}
41
42#[allow(dead_code)]
44#[derive(Debug, Clone)]
45pub struct HairCurlParams {
46 pub pattern: CurlPattern,
47 pub strength: f32,
48 pub frequency: f32,
49 pub random_seed: u32,
50}
51
52impl Default for HairCurlParams {
53 fn default() -> Self {
54 HairCurlParams {
55 pattern: CurlPattern::Straight,
56 strength: 0.0,
57 frequency: 1.0,
58 random_seed: 42,
59 }
60 }
61}
62
63#[allow(dead_code)]
64pub fn default_hair_curl_params() -> HairCurlParams {
65 HairCurlParams::default()
66}
67
68#[allow(dead_code)]
69pub fn hc_set_pattern(p: &mut HairCurlParams, pat: CurlPattern) {
70 p.pattern = pat;
71}
72
73#[allow(dead_code)]
74pub fn hc_set_strength(p: &mut HairCurlParams, v: f32) {
75 p.strength = v.clamp(0.0, 1.0);
76}
77
78#[allow(dead_code)]
79pub fn hc_set_frequency(p: &mut HairCurlParams, v: f32) {
80 p.frequency = v.clamp(0.1, 10.0);
81}
82
83#[allow(dead_code)]
84pub fn hc_reset(p: &mut HairCurlParams) {
85 *p = HairCurlParams::default();
86}
87
88#[allow(dead_code)]
89pub fn hc_is_straight(p: &HairCurlParams) -> bool {
90 p.strength < 1e-6 || p.pattern == CurlPattern::Straight
91}
92
93#[allow(dead_code)]
94pub fn hc_effective_curl(p: &HairCurlParams) -> f32 {
95 p.pattern.curl_index() * p.strength
96}
97
98#[allow(dead_code)]
99pub fn hc_blend(a: &HairCurlParams, b: &HairCurlParams, t: f32) -> HairCurlParams {
100 let t = t.clamp(0.0, 1.0);
101 let pattern = if t < 0.5 { a.pattern } else { b.pattern };
102 HairCurlParams {
103 pattern,
104 strength: a.strength + (b.strength - a.strength) * t,
105 frequency: a.frequency + (b.frequency - a.frequency) * t,
106 random_seed: a.random_seed,
107 }
108}
109
110#[allow(dead_code)]
111pub fn hc_to_json(p: &HairCurlParams) -> String {
112 format!(
113 r#"{{"pattern":"{}","strength":{:.4},"frequency":{:.4}}}"#,
114 p.pattern.name(),
115 p.strength,
116 p.frequency
117 )
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn default_is_straight() {
126 assert!(hc_is_straight(&default_hair_curl_params()));
127 }
128
129 #[test]
130 fn curl_index_range() {
131 assert!((CurlPattern::Straight.curl_index()).abs() < 1e-6);
132 assert!((CurlPattern::Kinky.curl_index() - 1.0).abs() < 1e-6);
133 }
134
135 #[test]
136 fn set_strength_clamps() {
137 let mut p = default_hair_curl_params();
138 hc_set_strength(&mut p, 5.0);
139 assert!((p.strength - 1.0).abs() < 1e-6);
140 }
141
142 #[test]
143 fn set_frequency_clamps_low() {
144 let mut p = default_hair_curl_params();
145 hc_set_frequency(&mut p, 0.0);
146 assert!((p.frequency - 0.1).abs() < 1e-6);
147 }
148
149 #[test]
150 fn reset_to_straight() {
151 let mut p = default_hair_curl_params();
152 hc_set_pattern(&mut p, CurlPattern::Kinky);
153 hc_set_strength(&mut p, 1.0);
154 hc_reset(&mut p);
155 assert!(hc_is_straight(&p));
156 }
157
158 #[test]
159 fn effective_curl_zero_when_straight() {
160 let p = default_hair_curl_params();
161 assert!(hc_effective_curl(&p).abs() < 1e-6);
162 }
163
164 #[test]
165 fn effective_curl_with_kinky_full() {
166 let mut p = default_hair_curl_params();
167 hc_set_pattern(&mut p, CurlPattern::Kinky);
168 hc_set_strength(&mut p, 1.0);
169 assert!((hc_effective_curl(&p) - 1.0).abs() < 1e-5);
170 }
171
172 #[test]
173 fn blend_strength_midpoint() {
174 let a = default_hair_curl_params();
175 let mut b = default_hair_curl_params();
176 hc_set_strength(&mut b, 1.0);
177 let m = hc_blend(&a, &b, 0.5);
178 assert!((m.strength - 0.5).abs() < 1e-5);
179 }
180
181 #[test]
182 fn to_json_contains_pattern() {
183 assert!(hc_to_json(&default_hair_curl_params()).contains("pattern"));
184 }
185}