oxihuman_morph/
nail_shape_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum NailShape {
11 Square,
12 Oval,
13 Pointed,
14 Squoval,
15 Almond,
16}
17
18impl NailShape {
19 #[allow(dead_code)]
20 pub fn name(self) -> &'static str {
21 match self {
22 NailShape::Square => "square",
23 NailShape::Oval => "oval",
24 NailShape::Pointed => "pointed",
25 NailShape::Squoval => "squoval",
26 NailShape::Almond => "almond",
27 }
28 }
29}
30
31#[allow(dead_code)]
33#[derive(Debug, Clone)]
34pub struct NailShapeParams {
35 pub shape: NailShape,
36 pub length: f32,
37 pub curvature: f32,
38 pub width_scale: f32,
39}
40
41impl Default for NailShapeParams {
42 fn default() -> Self {
43 NailShapeParams {
44 shape: NailShape::Oval,
45 length: 0.0,
46 curvature: 0.0,
47 width_scale: 0.0,
48 }
49 }
50}
51
52#[allow(dead_code)]
53pub fn default_nail_shape_params() -> NailShapeParams {
54 NailShapeParams::default()
55}
56
57#[allow(dead_code)]
58pub fn nail_set_shape(p: &mut NailShapeParams, s: NailShape) {
59 p.shape = s;
60}
61
62#[allow(dead_code)]
63pub fn nail_set_length(p: &mut NailShapeParams, v: f32) {
64 p.length = v.clamp(0.0, 1.0);
65}
66
67#[allow(dead_code)]
68pub fn nail_set_curvature(p: &mut NailShapeParams, v: f32) {
69 p.curvature = v.clamp(-1.0, 1.0);
70}
71
72#[allow(dead_code)]
73pub fn nail_set_width_scale(p: &mut NailShapeParams, v: f32) {
74 p.width_scale = v.clamp(-0.5, 0.5);
75}
76
77#[allow(dead_code)]
78pub fn nail_reset(p: &mut NailShapeParams) {
79 *p = NailShapeParams::default();
80}
81
82#[allow(dead_code)]
83pub fn nail_is_neutral(p: &NailShapeParams) -> bool {
84 p.length.abs() < 1e-6 && p.curvature.abs() < 1e-6 && p.width_scale.abs() < 1e-6
85}
86
87#[allow(dead_code)]
88pub fn nail_blend(a: &NailShapeParams, b: &NailShapeParams, t: f32) -> NailShapeParams {
89 let t = t.clamp(0.0, 1.0);
90 let shape = if t < 0.5 { a.shape } else { b.shape };
91 NailShapeParams {
92 shape,
93 length: a.length + (b.length - a.length) * t,
94 curvature: a.curvature + (b.curvature - a.curvature) * t,
95 width_scale: a.width_scale + (b.width_scale - a.width_scale) * t,
96 }
97}
98
99#[allow(dead_code)]
101pub fn nail_sharpness_index(p: &NailShapeParams) -> f32 {
102 match p.shape {
103 NailShape::Square => 0.0,
104 NailShape::Squoval => 0.25,
105 NailShape::Oval => 0.5,
106 NailShape::Almond => 0.75,
107 NailShape::Pointed => 1.0,
108 }
109}
110
111#[allow(dead_code)]
112pub fn nail_to_json(p: &NailShapeParams) -> String {
113 format!(
114 r#"{{"shape":"{}","length":{:.4},"curvature":{:.4},"width_scale":{:.4}}}"#,
115 p.shape.name(),
116 p.length,
117 p.curvature,
118 p.width_scale
119 )
120}
121
122#[cfg(test)]
123mod tests {
124 use super::*;
125
126 #[test]
127 fn default_shape_is_oval() {
128 assert_eq!(default_nail_shape_params().shape, NailShape::Oval);
129 }
130
131 #[test]
132 fn default_is_neutral() {
133 assert!(nail_is_neutral(&default_nail_shape_params()));
134 }
135
136 #[test]
137 fn set_length_clamps() {
138 let mut p = default_nail_shape_params();
139 nail_set_length(&mut p, 5.0);
140 assert!((p.length - 1.0).abs() < 1e-6);
141 }
142
143 #[test]
144 fn set_shape_square() {
145 let mut p = default_nail_shape_params();
146 nail_set_shape(&mut p, NailShape::Square);
147 assert_eq!(p.shape, NailShape::Square);
148 }
149
150 #[test]
151 fn reset_clears() {
152 let mut p = default_nail_shape_params();
153 nail_set_length(&mut p, 0.7);
154 nail_reset(&mut p);
155 assert!(nail_is_neutral(&p));
156 }
157
158 #[test]
159 fn sharpness_square_zero() {
160 let mut p = default_nail_shape_params();
161 nail_set_shape(&mut p, NailShape::Square);
162 assert!(nail_sharpness_index(&p).abs() < 1e-6);
163 }
164
165 #[test]
166 fn sharpness_pointed_one() {
167 let mut p = default_nail_shape_params();
168 nail_set_shape(&mut p, NailShape::Pointed);
169 assert!((nail_sharpness_index(&p) - 1.0).abs() < 1e-6);
170 }
171
172 #[test]
173 fn blend_midpoint_length() {
174 let a = default_nail_shape_params();
175 let mut b = default_nail_shape_params();
176 nail_set_length(&mut b, 1.0);
177 let m = nail_blend(&a, &b, 0.5);
178 assert!((m.length - 0.5).abs() < 1e-5);
179 }
180
181 #[test]
182 fn to_json_contains_shape() {
183 assert!(nail_to_json(&default_nail_shape_params()).contains("shape"));
184 }
185}