1#[allow(dead_code)]
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum TransferInterp {
11 Nearest,
13 Barycentric,
15}
16
17#[allow(dead_code)]
19#[derive(Debug, Clone)]
20pub struct ExpressionTransferConfig {
21 pub max_search_radius: f32,
23 pub normalize_by_scale: bool,
25 pub interpolation: TransferInterp,
27}
28
29impl Default for ExpressionTransferConfig {
30 fn default() -> Self {
31 Self {
32 max_search_radius: 0.1,
33 normalize_by_scale: false,
34 interpolation: TransferInterp::Nearest,
35 }
36 }
37}
38
39#[allow(dead_code)]
41#[derive(Debug, Clone)]
42pub struct TransferredExpression {
43 pub name: String,
45 pub deltas: Vec<[f32; 3]>,
47 pub coverage: f32,
49 pub max_delta_magnitude: f32,
51}
52
53#[allow(dead_code)]
55#[derive(Debug, Clone)]
56pub struct ExpressionTransferBatch {
57 pub expressions: Vec<TransferredExpression>,
59 pub source_vertex_count: usize,
61 pub target_vertex_count: usize,
63}
64
65#[allow(dead_code)]
72pub fn transfer_expression(
73 name: &str,
74 source_verts: &[[f32; 3]],
75 source_deltas: &[[f32; 3]],
76 target_verts: &[[f32; 3]],
77 cfg: &ExpressionTransferConfig,
78) -> TransferredExpression {
79 let scale = if cfg.normalize_by_scale {
80 mesh_scale_ratio(source_verts, target_verts)
81 } else {
82 1.0
83 };
84
85 let mut valid = 0usize;
86 let mut deltas: Vec<[f32; 3]> = Vec::with_capacity(target_verts.len());
87
88 for &tv in target_verts {
89 let d = match cfg.interpolation {
90 TransferInterp::Nearest => {
91 transfer_nearest(tv, source_verts, source_deltas, cfg.max_search_radius)
92 }
93 TransferInterp::Barycentric => {
94 transfer_barycentric_or_nearest(
98 tv,
99 source_verts,
100 source_deltas,
101 cfg.max_search_radius,
102 )
103 }
104 };
105
106 if let Some(delta) = d {
107 deltas.push(scale3(delta, scale));
108 valid += 1;
109 } else {
110 deltas.push([0.0, 0.0, 0.0]);
111 }
112 }
113
114 let coverage = if target_verts.is_empty() {
115 1.0
116 } else {
117 valid as f32 / target_verts.len() as f32
118 };
119 let max_delta_magnitude = delta_magnitude(&deltas);
120
121 TransferredExpression {
122 name: name.to_string(),
123 deltas,
124 coverage,
125 max_delta_magnitude,
126 }
127}
128
129#[allow(dead_code)]
131pub fn transfer_expression_batch(
132 expressions: &[(&str, Vec<[f32; 3]>)],
133 source_verts: &[[f32; 3]],
134 target_verts: &[[f32; 3]],
135 cfg: &ExpressionTransferConfig,
136) -> ExpressionTransferBatch {
137 let transferred = expressions
138 .iter()
139 .map(|(name, deltas)| transfer_expression(name, source_verts, deltas, target_verts, cfg))
140 .collect();
141
142 ExpressionTransferBatch {
143 expressions: transferred,
144 source_vertex_count: source_verts.len(),
145 target_vertex_count: target_verts.len(),
146 }
147}
148
149#[allow(dead_code)]
152pub fn barycentric_coords(p: [f32; 3], a: [f32; 3], b: [f32; 3], c: [f32; 3]) -> Option<[f32; 3]> {
153 let v0 = sub3(b, a);
154 let v1 = sub3(c, a);
155 let v2 = sub3(p, a);
156
157 let d00 = dot3(v0, v0);
158 let d01 = dot3(v0, v1);
159 let d11 = dot3(v1, v1);
160 let d20 = dot3(v2, v0);
161 let d21 = dot3(v2, v1);
162
163 let denom = d00 * d11 - d01 * d01;
164 if denom.abs() < 1e-10 {
165 return None; }
167
168 let v = (d11 * d20 - d01 * d21) / denom;
169 let w = (d00 * d21 - d01 * d20) / denom;
170 let u = 1.0 - v - w;
171 Some([u, v, w])
172}
173
174#[allow(dead_code)]
176pub fn interpolate_delta_barycentric(
177 d0: [f32; 3],
178 d1: [f32; 3],
179 d2: [f32; 3],
180 uvw: [f32; 3],
181) -> [f32; 3] {
182 [
183 d0[0] * uvw[0] + d1[0] * uvw[1] + d2[0] * uvw[2],
184 d0[1] * uvw[0] + d1[1] * uvw[1] + d2[1] * uvw[2],
185 d0[2] * uvw[0] + d1[2] * uvw[1] + d2[2] * uvw[2],
186 ]
187}
188
189#[allow(dead_code)]
192pub fn mesh_scale_ratio(source_verts: &[[f32; 3]], target_verts: &[[f32; 3]]) -> f32 {
193 let sd = bbox_diagonal(source_verts);
194 let td = bbox_diagonal(target_verts);
195 if td < 1e-10 || sd < 1e-10 {
196 return 1.0;
197 }
198 sd / td
199}
200
201#[allow(dead_code)]
203pub fn delta_magnitude(deltas: &[[f32; 3]]) -> f32 {
204 deltas
205 .iter()
206 .map(|&d| (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt())
207 .fold(0.0f32, f32::max)
208}
209
210fn transfer_nearest(
213 tv: [f32; 3],
214 source_verts: &[[f32; 3]],
215 source_deltas: &[[f32; 3]],
216 max_dist: f32,
217) -> Option<[f32; 3]> {
218 let mut best_idx = None;
219 let mut best_dist = max_dist;
220 for (i, &sv) in source_verts.iter().enumerate() {
221 let d = dist3(tv, sv);
222 if d < best_dist {
223 best_dist = d;
224 best_idx = Some(i);
225 }
226 }
227 best_idx.map(|i| source_deltas[i])
228}
229
230fn transfer_barycentric_or_nearest(
231 tv: [f32; 3],
232 source_verts: &[[f32; 3]],
233 source_deltas: &[[f32; 3]],
234 max_dist: f32,
235) -> Option<[f32; 3]> {
236 let n_tris = source_verts.len() / 3;
238 let mut best: Option<[f32; 3]> = None;
239 let mut best_dist = max_dist;
240
241 for t in 0..n_tris {
242 let i0 = t * 3;
243 let i1 = i0 + 1;
244 let i2 = i0 + 2;
245 if let Some(uvw) =
246 barycentric_coords(tv, source_verts[i0], source_verts[i1], source_verts[i2])
247 {
248 let proj = interpolate_delta_barycentric(
250 source_verts[i0],
251 source_verts[i1],
252 source_verts[i2],
253 uvw,
254 );
255 let d = dist3(tv, proj);
256 if d < best_dist {
257 best_dist = d;
258 best = Some(interpolate_delta_barycentric(
259 source_deltas[i0],
260 source_deltas[i1],
261 source_deltas[i2],
262 uvw,
263 ));
264 }
265 }
266 }
267
268 if best.is_some() {
269 return best;
270 }
271 transfer_nearest(tv, source_verts, source_deltas, max_dist)
273}
274
275fn bbox_diagonal(verts: &[[f32; 3]]) -> f32 {
276 if verts.is_empty() {
277 return 0.0;
278 }
279 let mut mn = verts[0];
280 let mut mx = verts[0];
281 for &v in verts {
282 if v[0] < mn[0] {
283 mn[0] = v[0];
284 }
285 if v[1] < mn[1] {
286 mn[1] = v[1];
287 }
288 if v[2] < mn[2] {
289 mn[2] = v[2];
290 }
291 if v[0] > mx[0] {
292 mx[0] = v[0];
293 }
294 if v[1] > mx[1] {
295 mx[1] = v[1];
296 }
297 if v[2] > mx[2] {
298 mx[2] = v[2];
299 }
300 }
301 dist3(mn, mx)
302}
303
304#[inline]
305fn dist3(a: [f32; 3], b: [f32; 3]) -> f32 {
306 let dx = a[0] - b[0];
307 let dy = a[1] - b[1];
308 let dz = a[2] - b[2];
309 (dx * dx + dy * dy + dz * dz).sqrt()
310}
311
312#[inline]
313fn sub3(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
314 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
315}
316
317#[inline]
318fn dot3(a: [f32; 3], b: [f32; 3]) -> f32 {
319 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
320}
321
322#[inline]
323fn scale3(v: [f32; 3], s: f32) -> [f32; 3] {
324 [v[0] * s, v[1] * s, v[2] * s]
325}
326
327#[cfg(test)]
330mod tests {
331 use super::*;
332
333 fn cfg_nearest() -> ExpressionTransferConfig {
334 ExpressionTransferConfig {
335 max_search_radius: 10.0,
336 normalize_by_scale: false,
337 interpolation: TransferInterp::Nearest,
338 }
339 }
340
341 #[test]
343 fn test_barycentric_centroid() {
344 let a = [0.0f32, 0.0, 0.0];
345 let b = [1.0, 0.0, 0.0];
346 let c = [0.0, 1.0, 0.0];
347 let centroid = [1.0 / 3.0, 1.0 / 3.0, 0.0];
348 let uvw = barycentric_coords(centroid, a, b, c).expect("should succeed");
349 assert!((uvw[0] - 1.0 / 3.0).abs() < 1e-5, "uvw[0]={}", uvw[0]);
350 assert!((uvw[1] - 1.0 / 3.0).abs() < 1e-5, "uvw[1]={}", uvw[1]);
351 assert!((uvw[2] - 1.0 / 3.0).abs() < 1e-5, "uvw[2]={}", uvw[2]);
352 }
353
354 #[test]
356 fn test_barycentric_corner_a() {
357 let a = [0.0f32, 0.0, 0.0];
358 let b = [1.0, 0.0, 0.0];
359 let c = [0.0, 1.0, 0.0];
360 let uvw = barycentric_coords(a, a, b, c).expect("should succeed");
361 assert!((uvw[0] - 1.0).abs() < 1e-5);
362 assert!(uvw[1].abs() < 1e-5);
363 assert!(uvw[2].abs() < 1e-5);
364 }
365
366 #[test]
368 fn test_barycentric_degenerate() {
369 let a = [0.0f32, 0.0, 0.0];
370 let b = [1.0, 0.0, 0.0];
371 let c = [2.0, 0.0, 0.0]; assert!(barycentric_coords([0.5, 0.0, 0.0], a, b, c).is_none());
375 }
376
377 #[test]
379 fn test_interpolate_delta_at_corner() {
380 let d0 = [1.0f32, 0.0, 0.0];
381 let d1 = [0.0, 1.0, 0.0];
382 let d2 = [0.0, 0.0, 1.0];
383 let uvw = [1.0, 0.0, 0.0];
384 let r = interpolate_delta_barycentric(d0, d1, d2, uvw);
385 assert!((r[0] - 1.0).abs() < 1e-6);
386 assert!(r[1].abs() < 1e-6);
387 assert!(r[2].abs() < 1e-6);
388 }
389
390 #[test]
392 fn test_interpolate_delta_at_centroid() {
393 let d0 = [3.0f32, 0.0, 0.0];
394 let d1 = [0.0, 3.0, 0.0];
395 let d2 = [0.0, 0.0, 3.0];
396 let uvw = [1.0 / 3.0, 1.0 / 3.0, 1.0 / 3.0];
397 let r = interpolate_delta_barycentric(d0, d1, d2, uvw);
398 assert!((r[0] - 1.0).abs() < 1e-5);
399 assert!((r[1] - 1.0).abs() < 1e-5);
400 assert!((r[2] - 1.0).abs() < 1e-5);
401 }
402
403 #[test]
405 fn test_mesh_scale_ratio_identical() {
406 let verts = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
407 let r = mesh_scale_ratio(&verts, &verts);
408 assert!((r - 1.0).abs() < 1e-5);
409 }
410
411 #[test]
413 fn test_mesh_scale_ratio_double() {
414 let src = vec![[0.0f32, 0.0, 0.0], [2.0, 0.0, 0.0]];
415 let tgt = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0]];
416 let r = mesh_scale_ratio(&src, &tgt);
417 assert!((r - 2.0).abs() < 1e-5);
418 }
419
420 #[test]
422 fn test_transfer_expression_identity_topology() {
423 let verts = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0]];
424 let deltas = vec![[0.1f32, 0.2, 0.3], [0.4, 0.5, 0.6]];
425 let cfg = cfg_nearest();
426 let result = transfer_expression("test", &verts, &deltas, &verts, &cfg);
427 for (a, b) in deltas.iter().zip(result.deltas.iter()) {
428 assert!((a[0] - b[0]).abs() < 1e-5);
429 assert!((a[1] - b[1]).abs() < 1e-5);
430 assert!((a[2] - b[2]).abs() < 1e-5);
431 }
432 }
433
434 #[test]
436 fn test_transfer_expression_full_coverage() {
437 let verts = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0]];
438 let deltas = vec![[0.1f32, 0.0, 0.0], [0.2, 0.0, 0.0]];
439 let cfg = cfg_nearest();
440 let result = transfer_expression("test", &verts, &deltas, &verts, &cfg);
441 assert!((result.coverage - 1.0).abs() < 1e-6);
442 }
443
444 #[test]
446 fn test_transfer_expression_partial_coverage() {
447 let source_verts = vec![[0.0f32, 0.0, 0.0]];
448 let source_deltas = vec![[0.1f32, 0.0, 0.0]];
449 let target_verts = vec![[0.0f32, 0.0, 0.0], [100.0, 0.0, 0.0]];
450 let cfg = ExpressionTransferConfig {
451 max_search_radius: 0.5,
452 ..Default::default()
453 };
454 let result =
455 transfer_expression("test", &source_verts, &source_deltas, &target_verts, &cfg);
456 assert!(result.coverage < 1.0);
457 }
458
459 #[test]
461 fn test_transfer_batch_count_matches() {
462 let verts = vec![[0.0f32, 0.0, 0.0], [1.0, 0.0, 0.0]];
463 let exprs: Vec<(&str, Vec<[f32; 3]>)> = vec![
464 ("smile", vec![[0.1, 0.0, 0.0], [0.2, 0.0, 0.0]]),
465 ("frown", vec![[0.0, 0.1, 0.0], [0.0, 0.2, 0.0]]),
466 ("blink", vec![[0.0, 0.0, 0.1], [0.0, 0.0, 0.2]]),
467 ];
468 let cfg = cfg_nearest();
469 let batch = transfer_expression_batch(&exprs, &verts, &verts, &cfg);
470 assert_eq!(batch.expressions.len(), 3);
471 assert_eq!(batch.source_vertex_count, 2);
472 assert_eq!(batch.target_vertex_count, 2);
473 }
474
475 #[test]
477 fn test_delta_magnitude_empty() {
478 assert_eq!(delta_magnitude(&[]), 0.0);
479 }
480
481 #[test]
483 fn test_delta_magnitude_value() {
484 let d = vec![[3.0f32, 4.0, 0.0]];
485 assert!((delta_magnitude(&d) - 5.0).abs() < 1e-5);
486 }
487
488 #[test]
490 fn test_barycentric_corner_b() {
491 let a = [0.0f32, 0.0, 0.0];
492 let b = [1.0, 0.0, 0.0];
493 let c = [0.0, 1.0, 0.0];
494 let uvw = barycentric_coords(b, a, b, c).expect("should succeed");
495 assert!(uvw[0].abs() < 1e-5);
496 assert!((uvw[1] - 1.0).abs() < 1e-5);
497 assert!(uvw[2].abs() < 1e-5);
498 }
499}