oxihuman_export/
blend_shape_driver_export.rs1#![allow(dead_code)]
4
5#[allow(dead_code)]
7pub struct BlendShapeDriver {
8 pub name: String,
9 pub driver_bone: String,
10 pub axis: DriverAxis,
11 pub input_range: [f32; 2],
12 pub driven_shapes: Vec<DrivenShape>,
13}
14
15#[allow(dead_code)]
16pub enum DriverAxis {
17 RotX,
18 RotY,
19 RotZ,
20 TransX,
21 TransY,
22 TransZ,
23}
24
25#[allow(dead_code)]
26pub struct DrivenShape {
27 pub shape_name: String,
28 pub weight_at_min: f32,
29 pub weight_at_max: f32,
30}
31
32#[allow(dead_code)]
33pub struct BlendShapeDriverExport {
34 pub drivers: Vec<BlendShapeDriver>,
35}
36
37#[allow(dead_code)]
38pub fn new_blend_shape_driver_export() -> BlendShapeDriverExport {
39 BlendShapeDriverExport { drivers: vec![] }
40}
41
42#[allow(dead_code)]
43pub fn add_driver(export: &mut BlendShapeDriverExport, driver: BlendShapeDriver) {
44 export.drivers.push(driver);
45}
46
47#[allow(dead_code)]
48pub fn driver_count(export: &BlendShapeDriverExport) -> usize {
49 export.drivers.len()
50}
51
52#[allow(dead_code)]
53pub fn total_driven_shapes(export: &BlendShapeDriverExport) -> usize {
54 export.drivers.iter().map(|d| d.driven_shapes.len()).sum()
55}
56
57#[allow(dead_code)]
58pub fn find_driver_by_name<'a>(
59 export: &'a BlendShapeDriverExport,
60 name: &str,
61) -> Option<&'a BlendShapeDriver> {
62 export.drivers.iter().find(|d| d.name == name)
63}
64
65#[allow(dead_code)]
66pub fn evaluate_driver(driver: &BlendShapeDriver, input: f32) -> Vec<(String, f32)> {
67 let [min_in, max_in] = driver.input_range;
68 let range = (max_in - min_in).abs();
69 let t = if range < 1e-10 {
70 0.0
71 } else {
72 ((input - min_in) / range).clamp(0.0, 1.0)
73 };
74 driver
75 .driven_shapes
76 .iter()
77 .map(|s| {
78 let w = s.weight_at_min + (s.weight_at_max - s.weight_at_min) * t;
79 (s.shape_name.clone(), w.clamp(0.0, 1.0))
80 })
81 .collect()
82}
83
84#[allow(dead_code)]
85pub fn driver_to_json(driver: &BlendShapeDriver) -> String {
86 format!(
87 "{{\"name\":\"{}\",\"driver_bone\":\"{}\",\"driven_shapes\":{}}}",
88 driver.name,
89 driver.driver_bone,
90 driver.driven_shapes.len()
91 )
92}
93
94#[allow(dead_code)]
95pub fn blend_shape_driver_export_to_json(export: &BlendShapeDriverExport) -> String {
96 format!(
97 "{{\"driver_count\":{},\"total_driven_shapes\":{}}}",
98 export.drivers.len(),
99 total_driven_shapes(export)
100 )
101}
102
103#[allow(dead_code)]
104pub fn validate_driver(driver: &BlendShapeDriver) -> bool {
105 driver.input_range[0] < driver.input_range[1]
106 && driver
107 .driven_shapes
108 .iter()
109 .all(|s| !s.shape_name.is_empty())
110}
111
112#[allow(dead_code)]
113pub fn axis_name(axis: &DriverAxis) -> &'static str {
114 match axis {
115 DriverAxis::RotX => "rot_x",
116 DriverAxis::RotY => "rot_y",
117 DriverAxis::RotZ => "rot_z",
118 DriverAxis::TransX => "trans_x",
119 DriverAxis::TransY => "trans_y",
120 DriverAxis::TransZ => "trans_z",
121 }
122}
123
124#[cfg(test)]
125mod tests {
126 use super::*;
127
128 fn sample_driver() -> BlendShapeDriver {
129 BlendShapeDriver {
130 name: "smile_driver".to_string(),
131 driver_bone: "jaw".to_string(),
132 axis: DriverAxis::RotX,
133 input_range: [0.0, 90.0],
134 driven_shapes: vec![
135 DrivenShape {
136 shape_name: "smile".to_string(),
137 weight_at_min: 0.0,
138 weight_at_max: 1.0,
139 },
140 DrivenShape {
141 shape_name: "cheek_puff".to_string(),
142 weight_at_min: 0.0,
143 weight_at_max: 0.5,
144 },
145 ],
146 }
147 }
148
149 #[test]
150 fn test_add_driver() {
151 let mut e = new_blend_shape_driver_export();
152 add_driver(&mut e, sample_driver());
153 assert_eq!(driver_count(&e), 1);
154 }
155
156 #[test]
157 fn test_total_driven_shapes() {
158 let mut e = new_blend_shape_driver_export();
159 add_driver(&mut e, sample_driver());
160 assert_eq!(total_driven_shapes(&e), 2);
161 }
162
163 #[test]
164 fn test_find_driver_found() {
165 let mut e = new_blend_shape_driver_export();
166 add_driver(&mut e, sample_driver());
167 assert!(find_driver_by_name(&e, "smile_driver").is_some());
168 }
169
170 #[test]
171 fn test_find_driver_not_found() {
172 let e = new_blend_shape_driver_export();
173 assert!(find_driver_by_name(&e, "missing").is_none());
174 }
175
176 #[test]
177 fn test_evaluate_driver_at_max() {
178 let d = sample_driver();
179 let result = evaluate_driver(&d, 90.0);
180 assert_eq!(result.len(), 2);
181 assert!((result[0].1 - 1.0).abs() < 1e-4);
182 }
183
184 #[test]
185 fn test_evaluate_driver_at_min() {
186 let d = sample_driver();
187 let result = evaluate_driver(&d, 0.0);
188 assert!((result[0].1).abs() < 1e-4);
189 }
190
191 #[test]
192 fn test_evaluate_driver_midpoint() {
193 let d = sample_driver();
194 let result = evaluate_driver(&d, 45.0);
195 assert!((result[0].1 - 0.5).abs() < 1e-3);
196 }
197
198 #[test]
199 fn test_validate_driver_valid() {
200 assert!(validate_driver(&sample_driver()));
201 }
202
203 #[test]
204 fn test_to_json() {
205 let d = sample_driver();
206 let j = driver_to_json(&d);
207 assert!(j.contains("smile_driver"));
208 }
209
210 #[test]
211 fn test_axis_name() {
212 assert_eq!(axis_name(&DriverAxis::RotX), "rot_x");
213 assert_eq!(axis_name(&DriverAxis::TransZ), "trans_z");
214 }
215}