oxihuman_morph/
tooth_shape_control.rs1#![allow(dead_code)]
3
4#[allow(dead_code)]
8#[derive(Debug, Clone, PartialEq)]
9pub struct ToothShapeParams {
10 pub width: f32,
12 pub height: f32,
14 pub rounding: f32,
16 pub overbite: f32,
18 pub crowding: f32,
20 pub whiteness: f32,
22 pub translucency: f32,
24}
25
26impl Default for ToothShapeParams {
27 fn default() -> Self {
28 Self {
29 width: 0.5,
30 height: 0.5,
31 rounding: 0.4,
32 overbite: 0.2,
33 crowding: 0.0,
34 whiteness: 0.8,
35 translucency: 0.2,
36 }
37 }
38}
39
40#[allow(dead_code)]
42pub fn default_tooth_shape_params() -> ToothShapeParams {
43 ToothShapeParams::default()
44}
45
46#[allow(dead_code)]
48pub fn set_tooth_width(params: &mut ToothShapeParams, value: f32) {
49 params.width = value.clamp(0.0, 1.0);
50}
51
52#[allow(dead_code)]
54pub fn set_tooth_height(params: &mut ToothShapeParams, value: f32) {
55 params.height = value.clamp(0.0, 1.0);
56}
57
58#[allow(dead_code)]
60pub fn set_tooth_rounding(params: &mut ToothShapeParams, value: f32) {
61 params.rounding = value.clamp(0.0, 1.0);
62}
63
64#[allow(dead_code)]
66pub fn set_tooth_overbite(params: &mut ToothShapeParams, value: f32) {
67 params.overbite = value.clamp(0.0, 1.0);
68}
69
70#[allow(dead_code)]
72pub fn set_tooth_crowding(params: &mut ToothShapeParams, value: f32) {
73 params.crowding = value.clamp(0.0, 1.0);
74}
75
76#[allow(dead_code)]
78pub fn set_tooth_whiteness(params: &mut ToothShapeParams, value: f32) {
79 params.whiteness = value.clamp(0.0, 1.0);
80}
81
82#[allow(dead_code)]
84pub fn tooth_color_rgb(params: &ToothShapeParams) -> [f32; 3] {
85 let w = params.whiteness.clamp(0.0, 1.0);
86 let t = params.translucency.clamp(0.0, 1.0);
87 let r = 0.85 + w * 0.12;
88 let g = 0.80 + w * 0.10 - t * 0.03;
89 let b = 0.70 + w * 0.08 - t * 0.05;
90 [r.clamp(0.0, 1.0), g.clamp(0.0, 1.0), b.clamp(0.0, 1.0)]
91}
92
93#[allow(dead_code)]
95pub fn blend_tooth_shape(a: &ToothShapeParams, b: &ToothShapeParams, t: f32) -> ToothShapeParams {
96 let t = t.clamp(0.0, 1.0);
97 let inv = 1.0 - t;
98 ToothShapeParams {
99 width: a.width * inv + b.width * t,
100 height: a.height * inv + b.height * t,
101 rounding: a.rounding * inv + b.rounding * t,
102 overbite: a.overbite * inv + b.overbite * t,
103 crowding: a.crowding * inv + b.crowding * t,
104 whiteness: a.whiteness * inv + b.whiteness * t,
105 translucency: a.translucency * inv + b.translucency * t,
106 }
107}
108
109#[allow(dead_code)]
111pub fn reset_tooth_shape(params: &mut ToothShapeParams) {
112 *params = ToothShapeParams::default();
113}
114
115#[allow(dead_code)]
117pub fn tooth_shape_to_json(params: &ToothShapeParams) -> String {
118 format!(
119 r#"{{"width":{:.4},"height":{:.4},"rounding":{:.4},"overbite":{:.4},"crowding":{:.4},"whiteness":{:.4}}}"#,
120 params.width,
121 params.height,
122 params.rounding,
123 params.overbite,
124 params.crowding,
125 params.whiteness
126 )
127}
128
129#[cfg(test)]
130mod tests {
131 use super::*;
132
133 #[test]
134 fn test_default() {
135 let p = ToothShapeParams::default();
136 assert!((0.0..=1.0).contains(&p.width));
137 }
138
139 #[test]
140 fn test_set_width_clamp() {
141 let mut p = ToothShapeParams::default();
142 set_tooth_width(&mut p, 5.0);
143 assert!((p.width - 1.0).abs() < 1e-6);
144 }
145
146 #[test]
147 fn test_set_height_clamp() {
148 let mut p = ToothShapeParams::default();
149 set_tooth_height(&mut p, -1.0);
150 assert!(p.height.abs() < 1e-6);
151 }
152
153 #[test]
154 fn test_set_rounding() {
155 let mut p = ToothShapeParams::default();
156 set_tooth_rounding(&mut p, 0.9);
157 assert!((p.rounding - 0.9).abs() < 1e-6);
158 }
159
160 #[test]
161 fn test_set_whiteness() {
162 let mut p = ToothShapeParams::default();
163 set_tooth_whiteness(&mut p, 0.3);
164 assert!((p.whiteness - 0.3).abs() < 1e-6);
165 }
166
167 #[test]
168 fn test_tooth_color_bright() {
169 let p = ToothShapeParams {
170 whiteness: 1.0,
171 translucency: 0.0,
172 ..Default::default()
173 };
174 let c = tooth_color_rgb(&p);
175 assert!(c[0] > 0.9);
176 }
177
178 #[test]
179 fn test_tooth_color_range() {
180 let p = ToothShapeParams::default();
181 let c = tooth_color_rgb(&p);
182 for ch in c {
183 assert!((0.0..=1.0).contains(&ch));
184 }
185 }
186
187 #[test]
188 fn test_blend_midpoint() {
189 let a = ToothShapeParams {
190 width: 0.0,
191 ..Default::default()
192 };
193 let b = ToothShapeParams {
194 width: 1.0,
195 ..Default::default()
196 };
197 let r = blend_tooth_shape(&a, &b, 0.5);
198 assert!((r.width - 0.5).abs() < 1e-6);
199 }
200
201 #[test]
202 fn test_reset() {
203 let mut p = ToothShapeParams {
204 whiteness: 0.1,
205 ..Default::default()
206 };
207 reset_tooth_shape(&mut p);
208 assert!((p.whiteness - 0.8).abs() < 1e-6);
209 }
210
211 #[test]
212 fn test_to_json() {
213 let j = tooth_shape_to_json(&ToothShapeParams::default());
214 assert!(j.contains("whiteness"));
215 assert!(j.contains("crowding"));
216 }
217}