oxihuman_morph/
neck_crease_control.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum CreaseTier {
11 Top,
12 Middle,
13 Bottom,
14}
15
16#[allow(dead_code)]
18#[derive(Clone, Debug)]
19pub struct NeckCreaseState {
20 pub depth_top: f32,
21 pub depth_middle: f32,
22 pub depth_bottom: f32,
23 pub vertical_spread: f32,
25}
26
27#[allow(dead_code)]
29#[derive(Clone, Debug)]
30pub struct NeckCreaseConfig {
31 pub max_depth: f32,
32}
33
34impl Default for NeckCreaseConfig {
35 fn default() -> Self {
36 Self { max_depth: 1.0 }
37 }
38}
39impl Default for NeckCreaseState {
40 fn default() -> Self {
41 Self {
42 depth_top: 0.0,
43 depth_middle: 0.0,
44 depth_bottom: 0.0,
45 vertical_spread: 0.5,
46 }
47 }
48}
49
50#[allow(dead_code)]
51pub fn new_neck_crease_state() -> NeckCreaseState {
52 NeckCreaseState::default()
53}
54
55#[allow(dead_code)]
56pub fn default_neck_crease_config() -> NeckCreaseConfig {
57 NeckCreaseConfig::default()
58}
59
60#[allow(dead_code)]
61pub fn nc_set_depth(state: &mut NeckCreaseState, cfg: &NeckCreaseConfig, tier: CreaseTier, v: f32) {
62 let v = v.clamp(0.0, cfg.max_depth);
63 match tier {
64 CreaseTier::Top => state.depth_top = v,
65 CreaseTier::Middle => state.depth_middle = v,
66 CreaseTier::Bottom => state.depth_bottom = v,
67 }
68}
69
70#[allow(dead_code)]
71pub fn nc_set_spread(state: &mut NeckCreaseState, v: f32) {
72 state.vertical_spread = v.clamp(0.0, 1.0);
73}
74
75#[allow(dead_code)]
76pub fn nc_reset(state: &mut NeckCreaseState) {
77 *state = NeckCreaseState::default();
78}
79
80#[allow(dead_code)]
81pub fn nc_is_neutral(state: &NeckCreaseState) -> bool {
82 state.depth_top < 1e-4 && state.depth_middle < 1e-4 && state.depth_bottom < 1e-4
83}
84
85#[allow(dead_code)]
86pub fn nc_blend(a: &NeckCreaseState, b: &NeckCreaseState, t: f32) -> NeckCreaseState {
87 let t = t.clamp(0.0, 1.0);
88 NeckCreaseState {
89 depth_top: a.depth_top + (b.depth_top - a.depth_top) * t,
90 depth_middle: a.depth_middle + (b.depth_middle - a.depth_middle) * t,
91 depth_bottom: a.depth_bottom + (b.depth_bottom - a.depth_bottom) * t,
92 vertical_spread: a.vertical_spread + (b.vertical_spread - a.vertical_spread) * t,
93 }
94}
95
96#[allow(dead_code)]
97pub fn nc_average_depth(state: &NeckCreaseState) -> f32 {
98 (state.depth_top + state.depth_middle + state.depth_bottom) / 3.0
99}
100
101#[allow(dead_code)]
102pub fn nc_to_weights(state: &NeckCreaseState) -> [f32; 4] {
103 [
104 state.depth_top,
105 state.depth_middle,
106 state.depth_bottom,
107 state.vertical_spread,
108 ]
109}
110
111#[allow(dead_code)]
112pub fn nc_to_json(state: &NeckCreaseState) -> String {
113 format!(
114 "{{\"top\":{:.4},\"mid\":{:.4},\"bot\":{:.4},\"spread\":{:.4}}}",
115 state.depth_top, state.depth_middle, state.depth_bottom, state.vertical_spread
116 )
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 #[test]
124 fn default_neutral() {
125 assert!(nc_is_neutral(&new_neck_crease_state()));
126 }
127
128 #[test]
129 fn depth_clamps_max() {
130 let mut s = new_neck_crease_state();
131 let cfg = default_neck_crease_config();
132 nc_set_depth(&mut s, &cfg, CreaseTier::Top, 5.0);
133 assert!(s.depth_top <= cfg.max_depth);
134 }
135
136 #[test]
137 fn depth_not_negative() {
138 let mut s = new_neck_crease_state();
139 let cfg = default_neck_crease_config();
140 nc_set_depth(&mut s, &cfg, CreaseTier::Middle, -1.0);
141 assert!(s.depth_middle >= 0.0);
142 }
143
144 #[test]
145 fn bottom_tier() {
146 let mut s = new_neck_crease_state();
147 let cfg = default_neck_crease_config();
148 nc_set_depth(&mut s, &cfg, CreaseTier::Bottom, 0.7);
149 assert!((s.depth_bottom - 0.7).abs() < 1e-5);
150 }
151
152 #[test]
153 fn reset_neutral() {
154 let mut s = new_neck_crease_state();
155 let cfg = default_neck_crease_config();
156 nc_set_depth(&mut s, &cfg, CreaseTier::Top, 0.5);
157 nc_reset(&mut s);
158 assert!(nc_is_neutral(&s));
159 }
160
161 #[test]
162 fn blend_midpoint() {
163 let cfg = default_neck_crease_config();
164 let mut a = new_neck_crease_state();
165 let mut b = new_neck_crease_state();
166 nc_set_depth(&mut a, &cfg, CreaseTier::Top, 0.0);
167 nc_set_depth(&mut b, &cfg, CreaseTier::Top, 1.0);
168 let m = nc_blend(&a, &b, 0.5);
169 assert!((m.depth_top - 0.5).abs() < 1e-4);
170 }
171
172 #[test]
173 fn average_depth_zero() {
174 assert!((nc_average_depth(&new_neck_crease_state())).abs() < 1e-5);
175 }
176
177 #[test]
178 fn spread_clamp() {
179 let mut s = new_neck_crease_state();
180 nc_set_spread(&mut s, 5.0);
181 assert!(s.vertical_spread <= 1.0);
182 }
183
184 #[test]
185 fn weights_len() {
186 assert_eq!(nc_to_weights(&new_neck_crease_state()).len(), 4);
187 }
188
189 #[test]
190 fn json_has_spread() {
191 assert!(nc_to_json(&new_neck_crease_state()).contains("spread"));
192 }
193}