1#![allow(dead_code)]
3
4#[allow(dead_code)]
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum BrowShapePreset {
10 Flat,
11 Arched,
12 Peaked,
13 Rounded,
14 Straight,
15 Angled,
16 SoftArch,
17 Bushy,
18}
19
20#[allow(dead_code)]
22#[derive(Debug, Clone, PartialEq)]
23pub struct BrowShapeParams {
24 pub arch_height: f32,
26 pub peak_position: f32,
28 pub thickness: f32,
30 pub tail_angle: f32,
32 pub inner_angle: f32,
34 pub width: f32,
36}
37
38impl Default for BrowShapeParams {
39 fn default() -> Self {
40 Self {
41 arch_height: 0.4,
42 peak_position: 0.6,
43 thickness: 0.5,
44 tail_angle: 0.0,
45 inner_angle: 0.0,
46 width: 1.0,
47 }
48 }
49}
50
51#[allow(dead_code)]
53pub fn preset_params(preset: BrowShapePreset) -> BrowShapeParams {
54 match preset {
55 BrowShapePreset::Flat => BrowShapeParams {
56 arch_height: 0.1,
57 peak_position: 0.5,
58 thickness: 0.5,
59 tail_angle: 0.0,
60 inner_angle: 0.0,
61 width: 1.0,
62 },
63 BrowShapePreset::Arched => BrowShapeParams {
64 arch_height: 0.7,
65 peak_position: 0.6,
66 thickness: 0.4,
67 tail_angle: -0.2,
68 inner_angle: 0.1,
69 width: 1.0,
70 },
71 BrowShapePreset::Peaked => BrowShapeParams {
72 arch_height: 0.9,
73 peak_position: 0.65,
74 thickness: 0.35,
75 tail_angle: -0.3,
76 inner_angle: 0.0,
77 width: 1.0,
78 },
79 BrowShapePreset::Rounded => BrowShapeParams {
80 arch_height: 0.5,
81 peak_position: 0.5,
82 thickness: 0.6,
83 tail_angle: -0.1,
84 inner_angle: -0.1,
85 width: 1.05,
86 },
87 BrowShapePreset::Straight => BrowShapeParams {
88 arch_height: 0.05,
89 peak_position: 0.5,
90 thickness: 0.55,
91 tail_angle: 0.0,
92 inner_angle: 0.0,
93 width: 1.0,
94 },
95 BrowShapePreset::Angled => BrowShapeParams {
96 arch_height: 0.6,
97 peak_position: 0.7,
98 thickness: 0.4,
99 tail_angle: -0.4,
100 inner_angle: 0.2,
101 width: 1.0,
102 },
103 BrowShapePreset::SoftArch => BrowShapeParams {
104 arch_height: 0.45,
105 peak_position: 0.58,
106 thickness: 0.5,
107 tail_angle: -0.1,
108 inner_angle: 0.0,
109 width: 1.0,
110 },
111 BrowShapePreset::Bushy => BrowShapeParams {
112 arch_height: 0.35,
113 peak_position: 0.5,
114 thickness: 0.85,
115 tail_angle: 0.0,
116 inner_angle: 0.0,
117 width: 1.1,
118 },
119 }
120}
121
122#[allow(dead_code)]
124pub fn all_presets() -> [BrowShapePreset; 8] {
125 [
126 BrowShapePreset::Flat,
127 BrowShapePreset::Arched,
128 BrowShapePreset::Peaked,
129 BrowShapePreset::Rounded,
130 BrowShapePreset::Straight,
131 BrowShapePreset::Angled,
132 BrowShapePreset::SoftArch,
133 BrowShapePreset::Bushy,
134 ]
135}
136
137#[allow(dead_code)]
139pub fn blend_brow_shape(a: &BrowShapeParams, b: &BrowShapeParams, t: f32) -> BrowShapeParams {
140 let t = t.clamp(0.0, 1.0);
141 let inv = 1.0 - t;
142 BrowShapeParams {
143 arch_height: a.arch_height * inv + b.arch_height * t,
144 peak_position: a.peak_position * inv + b.peak_position * t,
145 thickness: a.thickness * inv + b.thickness * t,
146 tail_angle: a.tail_angle * inv + b.tail_angle * t,
147 inner_angle: a.inner_angle * inv + b.inner_angle * t,
148 width: a.width * inv + b.width * t,
149 }
150}
151
152#[allow(dead_code)]
154pub fn brow_height_at(x: f32, params: &BrowShapeParams) -> f32 {
155 let x = x.clamp(0.0, 1.0);
156 let peak = params.peak_position.clamp(0.0, 1.0);
157 let arch = params.arch_height.clamp(0.0, 1.0);
158 let sigma = 0.25f32;
159 let gaussian = (-(x - peak).powi(2) / (2.0 * sigma * sigma)).exp();
160 arch * gaussian + params.tail_angle * (x - 0.5) + params.inner_angle * (0.5 - x).max(0.0)
161}
162
163#[allow(dead_code)]
165pub fn reset_brow_shape(params: &mut BrowShapeParams) {
166 *params = BrowShapeParams::default();
167}
168
169#[allow(dead_code)]
171pub fn brow_shape_to_json(params: &BrowShapeParams) -> String {
172 format!(
173 r#"{{"arch_height":{:.4},"peak_position":{:.4},"thickness":{:.4},"tail_angle":{:.4},"inner_angle":{:.4},"width":{:.4}}}"#,
174 params.arch_height,
175 params.peak_position,
176 params.thickness,
177 params.tail_angle,
178 params.inner_angle,
179 params.width
180 )
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186
187 #[test]
188 fn test_default() {
189 let p = BrowShapeParams::default();
190 assert!((0.0..=1.0).contains(&p.arch_height));
191 }
192
193 #[test]
194 fn test_preset_flat_low_arch() {
195 let p = preset_params(BrowShapePreset::Flat);
196 assert!(p.arch_height < 0.2);
197 }
198
199 #[test]
200 fn test_preset_peaked_high_arch() {
201 let p = preset_params(BrowShapePreset::Peaked);
202 assert!(p.arch_height > 0.8);
203 }
204
205 #[test]
206 fn test_all_presets_count() {
207 assert_eq!(all_presets().len(), 8);
208 }
209
210 #[test]
211 fn test_blend_midpoint() {
212 let a = BrowShapeParams {
213 arch_height: 0.0,
214 ..Default::default()
215 };
216 let b = BrowShapeParams {
217 arch_height: 1.0,
218 ..Default::default()
219 };
220 let r = blend_brow_shape(&a, &b, 0.5);
221 assert!((r.arch_height - 0.5).abs() < 1e-6);
222 }
223
224 #[test]
225 fn test_brow_height_at_peak() {
226 let p = BrowShapeParams {
227 arch_height: 1.0,
228 peak_position: 0.5,
229 tail_angle: 0.0,
230 inner_angle: 0.0,
231 ..Default::default()
232 };
233 let h_peak = brow_height_at(0.5, &p);
234 let h_edge = brow_height_at(0.0, &p);
235 assert!(h_peak > h_edge);
236 }
237
238 #[test]
239 fn test_brow_height_clamped_x() {
240 let p = BrowShapeParams::default();
241 let h1 = brow_height_at(-1.0, &p);
242 let h2 = brow_height_at(0.0, &p);
243 assert!((h1 - h2).abs() < 1e-6);
244 }
245
246 #[test]
247 fn test_reset() {
248 let mut p = BrowShapeParams {
249 arch_height: 0.99,
250 ..Default::default()
251 };
252 reset_brow_shape(&mut p);
253 assert!((p.arch_height - 0.4).abs() < 1e-6);
254 }
255
256 #[test]
257 fn test_to_json() {
258 let j = brow_shape_to_json(&BrowShapeParams::default());
259 assert!(j.contains("arch_height"));
260 assert!(j.contains("thickness"));
261 }
262
263 #[test]
264 fn test_preset_bushy_thick() {
265 let p = preset_params(BrowShapePreset::Bushy);
266 assert!(p.thickness > 0.8);
267 }
268}