oxihuman_morph/
forehead_crease_control.rs1#![allow(dead_code)]
3
4#[allow(dead_code)]
8#[derive(Debug, Clone, PartialEq)]
9pub struct ForeheadCreaseConfig {
10 pub max_depth_m: f32,
11 pub max_lines: u32,
12}
13
14impl Default for ForeheadCreaseConfig {
15 fn default() -> Self {
16 Self {
17 max_depth_m: 0.002,
18 max_lines: 5,
19 }
20 }
21}
22
23#[allow(dead_code)]
25#[derive(Debug, Clone, Default)]
26pub struct ForeheadCreaseState {
27 pub depth: f32,
29 pub spread: f32,
31 pub line_count: u32,
33}
34
35#[allow(dead_code)]
36pub fn new_forehead_crease_state() -> ForeheadCreaseState {
37 ForeheadCreaseState::default()
38}
39
40#[allow(dead_code)]
41pub fn default_forehead_crease_config() -> ForeheadCreaseConfig {
42 ForeheadCreaseConfig::default()
43}
44
45#[allow(dead_code)]
46pub fn fhc_set_depth(state: &mut ForeheadCreaseState, v: f32) {
47 state.depth = v.clamp(0.0, 1.0);
48}
49
50#[allow(dead_code)]
51pub fn fhc_set_spread(state: &mut ForeheadCreaseState, v: f32) {
52 state.spread = v.clamp(0.0, 1.0);
53}
54
55#[allow(dead_code)]
56pub fn fhc_set_lines(state: &mut ForeheadCreaseState, n: u32, cfg: &ForeheadCreaseConfig) {
57 state.line_count = n.min(cfg.max_lines);
58}
59
60#[allow(dead_code)]
61pub fn fhc_reset(state: &mut ForeheadCreaseState) {
62 *state = ForeheadCreaseState::default();
63}
64
65#[allow(dead_code)]
66pub fn fhc_is_neutral(state: &ForeheadCreaseState) -> bool {
67 state.depth < 1e-4 && state.spread < 1e-4 && state.line_count == 0
68}
69
70#[allow(dead_code)]
72pub fn fhc_effective_depth(state: &ForeheadCreaseState, cfg: &ForeheadCreaseConfig) -> f32 {
73 state.depth * cfg.max_depth_m
74}
75
76#[allow(dead_code)]
78pub fn fhc_intensity(state: &ForeheadCreaseState) -> f32 {
79 state.depth * state.spread
80}
81
82#[allow(dead_code)]
83pub fn fhc_blend(a: &ForeheadCreaseState, b: &ForeheadCreaseState, t: f32) -> ForeheadCreaseState {
84 let t = t.clamp(0.0, 1.0);
85 let inv = 1.0 - t;
86 let lines = if t >= 0.5 { b.line_count } else { a.line_count };
87 ForeheadCreaseState {
88 depth: a.depth * inv + b.depth * t,
89 spread: a.spread * inv + b.spread * t,
90 line_count: lines,
91 }
92}
93
94#[allow(dead_code)]
95pub fn fhc_to_json(state: &ForeheadCreaseState) -> String {
96 format!(
97 "{{\"depth\":{:.4},\"spread\":{:.4},\"line_count\":{}}}",
98 state.depth, state.spread, state.line_count
99 )
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn default_neutral() {
108 assert!(fhc_is_neutral(&new_forehead_crease_state()));
109 }
110
111 #[test]
112 fn depth_clamps_high() {
113 let mut s = new_forehead_crease_state();
114 fhc_set_depth(&mut s, 5.0);
115 assert!((s.depth - 1.0).abs() < 1e-6);
116 }
117
118 #[test]
119 fn depth_clamps_low() {
120 let mut s = new_forehead_crease_state();
121 fhc_set_depth(&mut s, -1.0);
122 assert!(s.depth < 1e-6);
123 }
124
125 #[test]
126 fn spread_clamps() {
127 let mut s = new_forehead_crease_state();
128 fhc_set_spread(&mut s, 3.0);
129 assert!((s.spread - 1.0).abs() < 1e-6);
130 }
131
132 #[test]
133 fn line_count_clamps_to_max() {
134 let cfg = default_forehead_crease_config();
135 let mut s = new_forehead_crease_state();
136 fhc_set_lines(&mut s, 100, &cfg);
137 assert!(s.line_count <= cfg.max_lines);
138 }
139
140 #[test]
141 fn reset_clears() {
142 let cfg = default_forehead_crease_config();
143 let mut s = new_forehead_crease_state();
144 fhc_set_depth(&mut s, 0.8);
145 fhc_set_lines(&mut s, 3, &cfg);
146 fhc_reset(&mut s);
147 assert!(fhc_is_neutral(&s));
148 }
149
150 #[test]
151 fn effective_depth_zero_at_neutral() {
152 let cfg = default_forehead_crease_config();
153 let s = new_forehead_crease_state();
154 assert!(fhc_effective_depth(&s, &cfg) < 1e-8);
155 }
156
157 #[test]
158 fn intensity_product_of_depth_and_spread() {
159 let mut s = new_forehead_crease_state();
160 fhc_set_depth(&mut s, 0.5);
161 fhc_set_spread(&mut s, 0.8);
162 assert!((fhc_intensity(&s) - 0.4).abs() < 1e-5);
163 }
164
165 #[test]
166 fn blend_midpoint() {
167 let b = ForeheadCreaseState {
168 depth: 1.0,
169 spread: 1.0,
170 line_count: 4,
171 };
172 let r = fhc_blend(&new_forehead_crease_state(), &b, 0.5);
173 assert!((r.depth - 0.5).abs() < 1e-5);
174 }
175
176 #[test]
177 fn json_has_depth() {
178 let j = fhc_to_json(&new_forehead_crease_state());
179 assert!(j.contains("depth") && j.contains("line_count"));
180 }
181}