oxihuman_export/
blend_shape_inbetween_export.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
7pub struct InbetweenKey {
8 pub name: String,
9 pub target_weight: f32,
10 pub deltas: Vec<[f32; 3]>,
11}
12
13#[allow(dead_code)]
15pub struct BlendShapeInbetweenExport {
16 pub base_name: String,
17 pub keys: Vec<InbetweenKey>,
18}
19
20#[allow(dead_code)]
22pub fn new_bsi_export(base_name: &str) -> BlendShapeInbetweenExport {
23 BlendShapeInbetweenExport {
24 base_name: base_name.to_string(),
25 keys: Vec::new(),
26 }
27}
28
29#[allow(dead_code)]
31pub fn add_inbetween_key(
32 export: &mut BlendShapeInbetweenExport,
33 name: &str,
34 target_weight: f32,
35 deltas: Vec<[f32; 3]>,
36) {
37 export.keys.push(InbetweenKey {
38 name: name.to_string(),
39 target_weight,
40 deltas,
41 });
42}
43
44#[allow(dead_code)]
46pub fn inbetween_key_count(export: &BlendShapeInbetweenExport) -> usize {
47 export.keys.len()
48}
49
50#[allow(dead_code)]
52pub fn total_inbetween_deltas(export: &BlendShapeInbetweenExport) -> usize {
53 export.keys.iter().map(|k| k.deltas.len()).sum()
54}
55
56#[allow(dead_code)]
58pub fn find_inbetween<'a>(
59 export: &'a BlendShapeInbetweenExport,
60 name: &str,
61) -> Option<&'a InbetweenKey> {
62 export.keys.iter().find(|k| k.name == name)
63}
64
65#[allow(dead_code)]
67pub fn max_delta_magnitude_bsi(key: &InbetweenKey) -> f32 {
68 key.deltas
69 .iter()
70 .map(|d| (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt())
71 .fold(0.0_f32, f32::max)
72}
73
74#[allow(dead_code)]
76pub fn keys_sorted_by_weight(export: &BlendShapeInbetweenExport) -> bool {
77 export
78 .keys
79 .windows(2)
80 .all(|w| w[0].target_weight <= w[1].target_weight)
81}
82
83#[allow(dead_code)]
85pub fn interpolate_inbetween(k0: &InbetweenKey, k1: &InbetweenKey, t: f32) -> Vec<[f32; 3]> {
86 let n = k0.deltas.len().min(k1.deltas.len());
87 (0..n)
88 .map(|i| {
89 let d0 = k0.deltas[i];
90 let d1 = k1.deltas[i];
91 [
92 d0[0] + t * (d1[0] - d0[0]),
93 d0[1] + t * (d1[1] - d0[1]),
94 d0[2] + t * (d1[2] - d0[2]),
95 ]
96 })
97 .collect()
98}
99
100#[allow(dead_code)]
102pub fn bsi_to_json(export: &BlendShapeInbetweenExport) -> String {
103 format!(
104 r#"{{"base":"{}","keys":{}}}"#,
105 export.base_name,
106 export.keys.len()
107 )
108}
109
110#[cfg(test)]
111mod tests {
112 use super::*;
113
114 #[test]
115 fn create_and_add() {
116 let mut e = new_bsi_export("mouth_open");
117 add_inbetween_key(&mut e, "half", 0.5, vec![[0.0, 0.1, 0.0]; 4]);
118 assert_eq!(inbetween_key_count(&e), 1);
119 }
120
121 #[test]
122 fn total_deltas() {
123 let mut e = new_bsi_export("eye");
124 add_inbetween_key(&mut e, "k1", 0.5, vec![[0.0; 3]; 3]);
125 add_inbetween_key(&mut e, "k2", 1.0, vec![[0.0; 3]; 3]);
126 assert_eq!(total_inbetween_deltas(&e), 6);
127 }
128
129 #[test]
130 fn find_key() {
131 let mut e = new_bsi_export("brow");
132 add_inbetween_key(&mut e, "mid", 0.5, vec![]);
133 assert!(find_inbetween(&e, "mid").is_some());
134 }
135
136 #[test]
137 fn max_delta_mag() {
138 let k = InbetweenKey {
139 name: "k".to_string(),
140 target_weight: 1.0,
141 deltas: vec![[3.0, 4.0, 0.0]],
142 };
143 assert!((max_delta_magnitude_bsi(&k) - 5.0).abs() < 1e-5);
144 }
145
146 #[test]
147 fn sorted_by_weight() {
148 let mut e = new_bsi_export("test");
149 add_inbetween_key(&mut e, "a", 0.25, vec![]);
150 add_inbetween_key(&mut e, "b", 0.75, vec![]);
151 assert!(keys_sorted_by_weight(&e));
152 }
153
154 #[test]
155 fn not_sorted() {
156 let mut e = new_bsi_export("test");
157 add_inbetween_key(&mut e, "a", 0.75, vec![]);
158 add_inbetween_key(&mut e, "b", 0.25, vec![]);
159 assert!(!keys_sorted_by_weight(&e));
160 }
161
162 #[test]
163 fn interpolate_midpoint() {
164 let k0 = InbetweenKey {
165 name: "k0".to_string(),
166 target_weight: 0.0,
167 deltas: vec![[0.0; 3]],
168 };
169 let k1 = InbetweenKey {
170 name: "k1".to_string(),
171 target_weight: 1.0,
172 deltas: vec![[2.0, 0.0, 0.0]],
173 };
174 let mid = interpolate_inbetween(&k0, &k1, 0.5);
175 assert!((mid[0][0] - 1.0).abs() < 1e-5);
176 }
177
178 #[test]
179 fn json_has_base() {
180 let e = new_bsi_export("cheek");
181 let j = bsi_to_json(&e);
182 assert!(j.contains("\"base\":\"cheek\""));
183 }
184
185 #[test]
186 fn empty_keys() {
187 let e = new_bsi_export("x");
188 assert_eq!(inbetween_key_count(&e), 0);
189 }
190
191 #[test]
192 fn find_missing() {
193 let e = new_bsi_export("x");
194 assert!(find_inbetween(&e, "y").is_none());
195 }
196}