1#![allow(dead_code)]
4
5#[derive(Debug, Clone)]
9pub struct ExamplePose {
10 pub name: String,
11 pub vertices: Vec<[f32; 3]>,
12 pub weight: f32,
13}
14
15#[derive(Debug, Clone)]
17pub struct ExampleBasedMorph {
18 pub rest_pose: Vec<[f32; 3]>,
19 pub examples: Vec<ExamplePose>,
20 pub enabled: bool,
21}
22
23impl ExampleBasedMorph {
24 pub fn new(rest_pose: Vec<[f32; 3]>) -> Self {
25 ExampleBasedMorph {
26 rest_pose,
27 examples: Vec::new(),
28 enabled: true,
29 }
30 }
31}
32
33pub fn new_example_based_morph(rest_pose: Vec<[f32; 3]>) -> ExampleBasedMorph {
35 ExampleBasedMorph::new(rest_pose)
36}
37
38pub fn ebm_add_example(morph: &mut ExampleBasedMorph, example: ExamplePose) {
40 morph.examples.push(example);
41}
42
43pub fn ebm_evaluate(morph: &ExampleBasedMorph, _weights: &[f32]) -> Vec<[f32; 3]> {
51 let nv = morph.rest_pose.len();
52 let mut out: Vec<[f32; 3]> = morph.rest_pose.clone();
53
54 let n_examples = morph.examples.len().min(_weights.len());
55 for (ex, &w) in morph.examples[..n_examples]
56 .iter()
57 .zip(&_weights[..n_examples])
58 {
59 if w.abs() < 1e-12 {
60 continue;
61 }
62 for (i, out_v) in out[..nv].iter_mut().enumerate() {
63 let ex_pos = ex.vertices.get(i).copied().unwrap_or([0.0f32; 3]);
64 let rest = morph.rest_pose[i];
65 out_v[0] += w * (ex_pos[0] - rest[0]);
66 out_v[1] += w * (ex_pos[1] - rest[1]);
67 out_v[2] += w * (ex_pos[2] - rest[2]);
68 }
69 }
70
71 out
72}
73
74pub fn ebm_example_count(morph: &ExampleBasedMorph) -> usize {
76 morph.examples.len()
77}
78
79pub fn ebm_vertex_count(morph: &ExampleBasedMorph) -> usize {
81 morph.rest_pose.len()
82}
83
84pub fn ebm_set_enabled(morph: &mut ExampleBasedMorph, enabled: bool) {
86 morph.enabled = enabled;
87}
88
89pub fn ebm_to_json(morph: &ExampleBasedMorph) -> String {
91 format!(
92 r#"{{"vertex_count":{},"example_count":{},"enabled":{}}}"#,
93 morph.rest_pose.len(),
94 morph.examples.len(),
95 morph.enabled
96 )
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102
103 #[test]
104 fn test_new_vertex_count() {
105 let m = new_example_based_morph(vec![[0.0; 3]; 10]);
106 assert_eq!(
107 ebm_vertex_count(&m),
108 10, );
110 }
111
112 #[test]
113 fn test_no_examples_initially() {
114 let m = new_example_based_morph(vec![[0.0; 3]; 5]);
115 assert_eq!(ebm_example_count(&m), 0 ,);
116 }
117
118 #[test]
119 fn test_add_example() {
120 let mut m = new_example_based_morph(vec![[0.0; 3]; 4]);
121 ebm_add_example(
122 &mut m,
123 ExamplePose {
124 name: "smile".into(),
125 vertices: vec![[0.1, 0.0, 0.0]; 4],
126 weight: 1.0,
127 },
128 );
129 assert_eq!(ebm_example_count(&m), 1 ,);
130 }
131
132 #[test]
133 fn test_evaluate_length() {
134 let m = new_example_based_morph(vec![[0.0; 3]; 6]);
135 let out = ebm_evaluate(&m, &[]);
136 assert_eq!(
137 out.len(),
138 6, );
140 }
141
142 #[test]
143 fn test_evaluate_returns_rest() {
144 let rest = vec![[1.0, 2.0, 3.0]; 3];
145 let m = new_example_based_morph(rest.clone());
146 let out = ebm_evaluate(&m, &[]);
147 assert!((out[0][0] - rest[0][0]).abs() < 1e-6, );
148 }
149
150 #[test]
151 fn test_set_enabled() {
152 let mut m = new_example_based_morph(vec![]);
153 ebm_set_enabled(&mut m, false);
154 assert!(!m.enabled ,);
155 }
156
157 #[test]
158 fn test_to_json() {
159 let m = new_example_based_morph(vec![[0.0; 3]; 3]);
160 let j = ebm_to_json(&m);
161 assert!(j.contains("\"vertex_count\""), );
162 }
163
164 #[test]
165 fn test_enabled_default() {
166 let m = new_example_based_morph(vec![]);
167 assert!(m.enabled ,);
168 }
169
170 #[test]
171 fn test_multiple_examples() {
172 let mut m = new_example_based_morph(vec![[0.0; 3]; 2]);
173 for i in 0..4 {
174 ebm_add_example(
175 &mut m,
176 ExamplePose {
177 name: format!("pose_{i}"),
178 vertices: vec![[0.0; 3]; 2],
179 weight: 0.5,
180 },
181 );
182 }
183 assert_eq!(
184 ebm_example_count(&m),
185 4, );
187 }
188
189 #[test]
190 fn test_json_contains_example_count() {
191 let m = new_example_based_morph(vec![]);
192 let j = ebm_to_json(&m);
193 assert!(j.contains("\"example_count\""), );
194 }
195
196 #[test]
197 fn test_blend_shape_half_weight() {
198 let rest = vec![[0.0f32; 3]; 3];
201 let mut m = new_example_based_morph(rest);
202 ebm_add_example(
203 &mut m,
204 ExamplePose {
205 name: "target".into(),
206 vertices: vec![[1.0f32; 3]; 3],
207 weight: 0.5,
208 },
209 );
210 let out = ebm_evaluate(&m, &[0.5]);
211 assert_eq!(out.len(), 3, "output vertex count must match rest pose");
212 for (i, v) in out.iter().enumerate() {
213 assert!(
214 (v[0] - 0.5).abs() < 1e-5,
215 "vertex[{i}][0] must be 0.5, got {}",
216 v[0]
217 );
218 assert!(
219 (v[1] - 0.5).abs() < 1e-5,
220 "vertex[{i}][1] must be 0.5, got {}",
221 v[1]
222 );
223 assert!(
224 (v[2] - 0.5).abs() < 1e-5,
225 "vertex[{i}][2] must be 0.5, got {}",
226 v[2]
227 );
228 }
229 }
230
231 #[test]
232 fn test_blend_shape_full_weight_reaches_example() {
233 let rest = vec![[0.0f32; 3]; 2];
235 let mut m = new_example_based_morph(rest);
236 ebm_add_example(
237 &mut m,
238 ExamplePose {
239 name: "full".into(),
240 vertices: vec![[3.0, 4.0, 5.0], [6.0, 7.0, 8.0]],
241 weight: 1.0,
242 },
243 );
244 let out = ebm_evaluate(&m, &[1.0]);
245 assert!(
246 (out[0][0] - 3.0).abs() < 1e-5,
247 "full-weight must reach example position"
248 );
249 assert!(
250 (out[1][2] - 8.0).abs() < 1e-5,
251 "full-weight must reach example position"
252 );
253 }
254
255 #[test]
256 fn test_blend_shape_no_weights_returns_rest() {
257 let rest = vec![[1.0, 2.0, 3.0]; 4];
259 let mut m = new_example_based_morph(rest.clone());
260 ebm_add_example(
261 &mut m,
262 ExamplePose {
263 name: "ignored".into(),
264 vertices: vec![[99.0f32; 3]; 4],
265 weight: 1.0,
266 },
267 );
268 let out = ebm_evaluate(&m, &[]);
269 for (i, v) in out.iter().enumerate() {
270 assert!(
271 (v[0] - 1.0).abs() < 1e-5,
272 "vertex[{i}] must equal rest when no weights provided"
273 );
274 }
275 }
276}