oxihuman_morph/
scar_morph.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq)]
10pub enum ScarType {
11 Raised,
12 Depressed,
13 Linear,
14 Keloid,
15 Atrophic,
16}
17
18impl ScarType {
19 #[allow(dead_code)]
20 pub fn name(self) -> &'static str {
21 match self {
22 ScarType::Raised => "raised",
23 ScarType::Depressed => "depressed",
24 ScarType::Linear => "linear",
25 ScarType::Keloid => "keloid",
26 ScarType::Atrophic => "atrophic",
27 }
28 }
29}
30
31#[allow(dead_code)]
33#[derive(Debug, Clone)]
34pub struct ScarMorphParams {
35 pub scar_type: ScarType,
36 pub prominence: f32,
37 pub width: f32,
38 pub length: f32,
39 pub roughness: f32,
40}
41
42impl ScarMorphParams {
43 #[allow(dead_code)]
44 pub fn new(scar_type: ScarType) -> Self {
45 ScarMorphParams {
46 scar_type,
47 prominence: 0.0,
48 width: 0.0,
49 length: 0.0,
50 roughness: 0.0,
51 }
52 }
53}
54
55#[allow(dead_code)]
56pub fn default_scar_morph_params() -> ScarMorphParams {
57 ScarMorphParams::new(ScarType::Linear)
58}
59
60#[allow(dead_code)]
61pub fn scar_set_prominence(p: &mut ScarMorphParams, v: f32) {
62 p.prominence = v.clamp(0.0, 1.0);
63}
64
65#[allow(dead_code)]
66pub fn scar_set_width(p: &mut ScarMorphParams, v: f32) {
67 p.width = v.clamp(0.0, 1.0);
68}
69
70#[allow(dead_code)]
71pub fn scar_set_length(p: &mut ScarMorphParams, v: f32) {
72 p.length = v.clamp(0.0, 1.0);
73}
74
75#[allow(dead_code)]
76pub fn scar_set_roughness(p: &mut ScarMorphParams, v: f32) {
77 p.roughness = v.clamp(0.0, 1.0);
78}
79
80#[allow(dead_code)]
81pub fn scar_reset(p: &mut ScarMorphParams) {
82 let t = p.scar_type;
83 *p = ScarMorphParams::new(t);
84}
85
86#[allow(dead_code)]
87pub fn scar_is_neutral(p: &ScarMorphParams) -> bool {
88 p.prominence.abs() < 1e-6 && p.width.abs() < 1e-6 && p.length.abs() < 1e-6
89}
90
91#[allow(dead_code)]
92pub fn scar_visibility(p: &ScarMorphParams) -> f32 {
93 p.prominence * 0.5 + p.roughness * 0.3 + (p.width * p.length).sqrt() * 0.2
94}
95
96#[allow(dead_code)]
97pub fn scar_blend(a: &ScarMorphParams, b: &ScarMorphParams, t: f32) -> ScarMorphParams {
98 let t = t.clamp(0.0, 1.0);
99 ScarMorphParams {
100 scar_type: if t < 0.5 { a.scar_type } else { b.scar_type },
101 prominence: a.prominence + (b.prominence - a.prominence) * t,
102 width: a.width + (b.width - a.width) * t,
103 length: a.length + (b.length - a.length) * t,
104 roughness: a.roughness + (b.roughness - a.roughness) * t,
105 }
106}
107
108#[allow(dead_code)]
109pub fn scar_to_json(p: &ScarMorphParams) -> String {
110 format!(
111 r#"{{"type":"{}","prominence":{:.4},"width":{:.4},"length":{:.4},"roughness":{:.4}}}"#,
112 p.scar_type.name(),
113 p.prominence,
114 p.width,
115 p.length,
116 p.roughness
117 )
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn default_is_neutral() {
126 assert!(scar_is_neutral(&default_scar_morph_params()));
127 }
128
129 #[test]
130 fn set_prominence_clamps() {
131 let mut p = default_scar_morph_params();
132 scar_set_prominence(&mut p, 5.0);
133 assert!((p.prominence - 1.0).abs() < 1e-6);
134 }
135
136 #[test]
137 fn scar_type_names() {
138 assert_eq!(ScarType::Raised.name(), "raised");
139 assert_eq!(ScarType::Keloid.name(), "keloid");
140 }
141
142 #[test]
143 fn reset_clears() {
144 let mut p = ScarMorphParams::new(ScarType::Raised);
145 scar_set_prominence(&mut p, 0.8);
146 scar_reset(&mut p);
147 assert!(scar_is_neutral(&p));
148 }
149
150 #[test]
151 fn reset_preserves_type() {
152 let mut p = ScarMorphParams::new(ScarType::Keloid);
153 scar_reset(&mut p);
154 assert_eq!(p.scar_type, ScarType::Keloid);
155 }
156
157 #[test]
158 fn visibility_zero_when_neutral() {
159 assert!(scar_visibility(&default_scar_morph_params()).abs() < 1e-6);
160 }
161
162 #[test]
163 fn visibility_positive_with_prominence() {
164 let mut p = default_scar_morph_params();
165 scar_set_prominence(&mut p, 1.0);
166 assert!(scar_visibility(&p) > 0.0);
167 }
168
169 #[test]
170 fn blend_midpoint() {
171 let a = default_scar_morph_params();
172 let mut b = default_scar_morph_params();
173 scar_set_prominence(&mut b, 1.0);
174 let m = scar_blend(&a, &b, 0.5);
175 assert!((m.prominence - 0.5).abs() < 1e-5);
176 }
177
178 #[test]
179 fn to_json_contains_type() {
180 assert!(scar_to_json(&default_scar_morph_params()).contains("type"));
181 }
182}