1#[allow(dead_code)]
5pub struct QuantizedDelta {
6 pub vertex_id: u32,
7 pub dx: i16,
8 pub dy: i16,
9 pub dz: i16,
10}
11
12#[allow(dead_code)]
13pub struct MorphQuantizeConfig {
14 pub bits: u8,
15 pub scale: f32,
16 pub threshold: f32,
17}
18
19#[allow(dead_code)]
20pub struct QuantizedMorph {
21 pub name: String,
22 pub deltas: Vec<QuantizedDelta>,
23 pub scale: f32,
24 pub original_count: usize,
25}
26
27#[allow(dead_code)]
28pub fn default_morph_quantize_config() -> MorphQuantizeConfig {
29 MorphQuantizeConfig {
30 bits: 16,
31 scale: 0.01,
32 threshold: 0.0001,
33 }
34}
35
36#[allow(dead_code)]
37pub fn quantize_delta(dx: f32, dy: f32, dz: f32, scale: f32) -> (i16, i16, i16) {
38 let clamp = |v: f32| -> i16 {
39 let q = (v / scale).round();
40 q.clamp(i16::MIN as f32, i16::MAX as f32) as i16
41 };
42 (clamp(dx), clamp(dy), clamp(dz))
43}
44
45#[allow(dead_code)]
46pub fn dequantize_delta(qx: i16, qy: i16, qz: i16, scale: f32) -> [f32; 3] {
47 [qx as f32 * scale, qy as f32 * scale, qz as f32 * scale]
48}
49
50#[allow(dead_code)]
51pub fn quantize_morph(
52 name: &str,
53 deltas: &[(u32, [f32; 3])],
54 cfg: &MorphQuantizeConfig,
55) -> QuantizedMorph {
56 let original_count = deltas.len();
57 let mut quantized_deltas = Vec::new();
58
59 for (vertex_id, delta) in deltas {
60 let [dx, dy, dz] = *delta;
61 let magnitude = (dx * dx + dy * dy + dz * dz).sqrt();
62 if magnitude < cfg.threshold {
63 continue;
64 }
65 let (qx, qy, qz) = quantize_delta(dx, dy, dz, cfg.scale);
66 quantized_deltas.push(QuantizedDelta {
67 vertex_id: *vertex_id,
68 dx: qx,
69 dy: qy,
70 dz: qz,
71 });
72 }
73
74 QuantizedMorph {
75 name: name.to_string(),
76 deltas: quantized_deltas,
77 scale: cfg.scale,
78 original_count,
79 }
80}
81
82#[allow(dead_code)]
83pub fn dequantize_morph(qm: &QuantizedMorph) -> Vec<(u32, [f32; 3])> {
84 qm.deltas
85 .iter()
86 .map(|d| {
87 let xyz = dequantize_delta(d.dx, d.dy, d.dz, qm.scale);
88 (d.vertex_id, xyz)
89 })
90 .collect()
91}
92
93#[allow(dead_code)]
94pub fn morph_compression_ratio(original: &[(u32, [f32; 3])], quantized: &QuantizedMorph) -> f32 {
95 let orig_bytes = original.len() * 16;
98 let quant_bytes = quantized.deltas.len() * 10;
99 if quant_bytes == 0 {
100 return f32::INFINITY;
101 }
102 orig_bytes as f32 / quant_bytes as f32
103}
104
105#[allow(dead_code)]
106pub fn max_quantization_error(original: &[(u32, [f32; 3])], quantized: &QuantizedMorph) -> f32 {
107 let deq = dequantize_morph(quantized);
108 let mut max_err = 0.0f32;
109
110 for (orig_vid, orig_delta) in original {
111 let found = deq.iter().find(|(vid, _)| vid == orig_vid);
112 if let Some((_, deq_delta)) = found {
113 let err = (0..3)
114 .map(|i| (orig_delta[i] - deq_delta[i]).abs())
115 .fold(0.0f32, f32::max);
116 max_err = max_err.max(err);
117 } else {
118 let err = orig_delta.iter().map(|v| v.abs()).fold(0.0f32, f32::max);
120 max_err = max_err.max(err);
121 }
122 }
123 max_err
124}
125
126#[allow(dead_code)]
128pub fn pack_quantized_morph(qm: &QuantizedMorph) -> Vec<u8> {
129 let name_bytes = qm.name.as_bytes();
130 let name_len = name_bytes.len() as u32;
131 let delta_count = qm.deltas.len() as u32;
132 let original_count = qm.original_count as u32;
133
134 let mut out = Vec::new();
135 out.extend_from_slice(&name_len.to_le_bytes());
136 out.extend_from_slice(name_bytes);
137 out.extend_from_slice(&qm.scale.to_bits().to_le_bytes());
138 out.extend_from_slice(&original_count.to_le_bytes());
139 out.extend_from_slice(&delta_count.to_le_bytes());
140
141 for d in &qm.deltas {
142 out.extend_from_slice(&d.vertex_id.to_le_bytes());
143 out.extend_from_slice(&d.dx.to_le_bytes());
144 out.extend_from_slice(&d.dy.to_le_bytes());
145 out.extend_from_slice(&d.dz.to_le_bytes());
146 }
147 out
148}
149
150#[allow(dead_code)]
152pub fn unpack_quantized_morph(data: &[u8]) -> Option<QuantizedMorph> {
153 if data.len() < 4 {
154 return None;
155 }
156 let mut pos = 0usize;
157
158 let name_len = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
159 pos += 4;
160 if data.len() < pos + name_len {
161 return None;
162 }
163 let name = std::str::from_utf8(&data[pos..pos + name_len])
164 .ok()?
165 .to_string();
166 pos += name_len;
167
168 if data.len() < pos + 4 {
169 return None;
170 }
171 let scale_bits = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?);
172 let scale = f32::from_bits(scale_bits);
173 pos += 4;
174
175 if data.len() < pos + 8 {
176 return None;
177 }
178 let original_count = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
179 pos += 4;
180 let delta_count = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?) as usize;
181 pos += 4;
182
183 if data.len() < pos + delta_count * 10 {
185 return None;
186 }
187
188 let mut deltas = Vec::with_capacity(delta_count);
189 for _ in 0..delta_count {
190 let vertex_id = u32::from_le_bytes(data[pos..pos + 4].try_into().ok()?);
191 pos += 4;
192 let dx = i16::from_le_bytes(data[pos..pos + 2].try_into().ok()?);
193 pos += 2;
194 let dy = i16::from_le_bytes(data[pos..pos + 2].try_into().ok()?);
195 pos += 2;
196 let dz = i16::from_le_bytes(data[pos..pos + 2].try_into().ok()?);
197 pos += 2;
198 deltas.push(QuantizedDelta {
199 vertex_id,
200 dx,
201 dy,
202 dz,
203 });
204 }
205
206 Some(QuantizedMorph {
207 name,
208 deltas,
209 scale,
210 original_count,
211 })
212}
213
214#[allow(dead_code)]
216pub fn merge_quantized_morphs(a: &QuantizedMorph, b: &QuantizedMorph) -> QuantizedMorph {
217 use std::collections::HashMap;
218 let mut map: HashMap<u32, (i16, i16, i16)> = HashMap::new();
219
220 for d in &a.deltas {
221 map.insert(d.vertex_id, (d.dx, d.dy, d.dz));
222 }
223 for d in &b.deltas {
224 let entry = map.entry(d.vertex_id).or_insert((0, 0, 0));
225 entry.0 = entry.0.saturating_add(d.dx);
226 entry.1 = entry.1.saturating_add(d.dy);
227 entry.2 = entry.2.saturating_add(d.dz);
228 }
229
230 let mut deltas: Vec<QuantizedDelta> = map
231 .into_iter()
232 .map(|(vertex_id, (dx, dy, dz))| QuantizedDelta {
233 vertex_id,
234 dx,
235 dy,
236 dz,
237 })
238 .collect();
239 deltas.sort_by_key(|d| d.vertex_id);
240
241 QuantizedMorph {
242 name: format!("{}+{}", a.name, b.name),
243 deltas,
244 scale: a.scale,
245 original_count: a.original_count + b.original_count,
246 }
247}
248
249#[allow(dead_code)]
251pub fn filter_zero_deltas(qm: &mut QuantizedMorph) {
252 qm.deltas.retain(|d| d.dx != 0 || d.dy != 0 || d.dz != 0);
253}
254
255#[allow(dead_code)]
256pub fn quantized_delta_count(qm: &QuantizedMorph) -> usize {
257 qm.deltas.len()
258}
259
260#[allow(dead_code)]
261pub fn apply_quantized_morph(positions: &mut [[f32; 3]], qm: &QuantizedMorph, weight: f32) {
262 for d in &qm.deltas {
263 let vid = d.vertex_id as usize;
264 if vid < positions.len() {
265 let [dx, dy, dz] = dequantize_delta(d.dx, d.dy, d.dz, qm.scale);
266 positions[vid][0] += dx * weight;
267 positions[vid][1] += dy * weight;
268 positions[vid][2] += dz * weight;
269 }
270 }
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 fn sample_deltas() -> Vec<(u32, [f32; 3])> {
278 vec![
279 (0, [0.05, 0.0, 0.0]),
280 (1, [0.0, 0.1, 0.0]),
281 (2, [0.0, 0.0, 0.15]),
282 (3, [0.02, 0.03, 0.04]),
283 (4, [0.0, 0.0, 0.0]), ]
285 }
286
287 #[test]
288 fn test_default_config() {
289 let cfg = default_morph_quantize_config();
290 assert_eq!(cfg.bits, 16);
291 assert!(cfg.scale > 0.0);
292 assert!(cfg.threshold >= 0.0);
293 }
294
295 #[test]
296 fn test_quantize_dequantize_roundtrip() {
297 let scale = 0.001;
298 let (dx, dy, dz) = (0.123f32, -0.456f32, 0.789f32);
299 let (qx, qy, qz) = quantize_delta(dx, dy, dz, scale);
300 let [rx, ry, rz] = dequantize_delta(qx, qy, qz, scale);
301 assert!((rx - dx).abs() < scale);
302 assert!((ry - dy).abs() < scale);
303 assert!((rz - dz).abs() < scale);
304 }
305
306 #[test]
307 fn test_quantize_zero() {
308 let (qx, qy, qz) = quantize_delta(0.0, 0.0, 0.0, 0.01);
309 assert_eq!(qx, 0);
310 assert_eq!(qy, 0);
311 assert_eq!(qz, 0);
312 }
313
314 #[test]
315 fn test_dequantize_zero() {
316 let [rx, ry, rz] = dequantize_delta(0, 0, 0, 0.01);
317 assert_eq!(rx, 0.0);
318 assert_eq!(ry, 0.0);
319 assert_eq!(rz, 0.0);
320 }
321
322 #[test]
323 fn test_quantize_morph_basic() {
324 let cfg = default_morph_quantize_config();
325 let deltas = sample_deltas();
326 let qm = quantize_morph("test", &deltas, &cfg);
327 assert_eq!(qm.name, "test");
328 assert_eq!(qm.original_count, deltas.len());
329 assert!(qm.deltas.len() < deltas.len());
331 }
332
333 #[test]
334 fn test_quantize_morph_threshold() {
335 let cfg = MorphQuantizeConfig {
336 bits: 16,
337 scale: 0.01,
338 threshold: 0.5,
339 };
340 let deltas = vec![(0, [0.01f32, 0.0, 0.0]), (1, [1.0, 0.0, 0.0])];
341 let qm = quantize_morph("th", &deltas, &cfg);
342 assert_eq!(qm.deltas.len(), 1);
344 assert_eq!(qm.deltas[0].vertex_id, 1);
345 }
346
347 #[test]
348 fn test_dequantize_morph() {
349 let cfg = default_morph_quantize_config();
350 let deltas = vec![(0, [0.05f32, 0.1, 0.15])];
351 let qm = quantize_morph("deq", &deltas, &cfg);
352 let result = dequantize_morph(&qm);
353 assert_eq!(result.len(), qm.deltas.len());
354 if !result.is_empty() {
355 let [rx, ry, rz] = result[0].1;
356 assert!((rx - 0.05).abs() < cfg.scale * 2.0);
357 assert!((ry - 0.1).abs() < cfg.scale * 2.0);
358 assert!((rz - 0.15).abs() < cfg.scale * 2.0);
359 }
360 }
361
362 #[test]
363 fn test_pack_unpack_roundtrip() {
364 let cfg = default_morph_quantize_config();
365 let deltas = sample_deltas();
366 let qm = quantize_morph("roundtrip", &deltas, &cfg);
367 let packed = pack_quantized_morph(&qm);
368 let unpacked = unpack_quantized_morph(&packed).expect("unpack failed");
369 assert_eq!(unpacked.name, qm.name);
370 assert_eq!(unpacked.deltas.len(), qm.deltas.len());
371 assert_eq!(unpacked.original_count, qm.original_count);
372 assert!((unpacked.scale - qm.scale).abs() < 1e-9);
373 }
374
375 #[test]
376 fn test_unpack_empty_returns_none() {
377 assert!(unpack_quantized_morph(&[]).is_none());
378 }
379
380 #[test]
381 fn test_compression_ratio_geq_one() {
382 let cfg = default_morph_quantize_config();
383 let deltas = sample_deltas();
384 let qm = quantize_morph("cr", &deltas, &cfg);
385 let ratio = morph_compression_ratio(&deltas, &qm);
386 assert!(ratio >= 1.0);
387 }
388
389 #[test]
390 fn test_max_quantization_error() {
391 let cfg = MorphQuantizeConfig {
392 bits: 16,
393 scale: 0.001,
394 threshold: 0.0001,
395 };
396 let deltas = vec![(0, [0.123f32, 0.456, 0.789])];
397 let qm = quantize_morph("err", &deltas, &cfg);
398 let err = max_quantization_error(&deltas, &qm);
399 assert!(err < cfg.scale * 2.0);
400 }
401
402 #[test]
403 fn test_filter_zero_deltas() {
404 let mut qm = QuantizedMorph {
405 name: "fz".to_string(),
406 deltas: vec![
407 QuantizedDelta {
408 vertex_id: 0,
409 dx: 0,
410 dy: 0,
411 dz: 0,
412 },
413 QuantizedDelta {
414 vertex_id: 1,
415 dx: 1,
416 dy: 0,
417 dz: 0,
418 },
419 QuantizedDelta {
420 vertex_id: 2,
421 dx: 0,
422 dy: 0,
423 dz: 0,
424 },
425 ],
426 scale: 0.01,
427 original_count: 3,
428 };
429 filter_zero_deltas(&mut qm);
430 assert_eq!(qm.deltas.len(), 1);
431 assert_eq!(qm.deltas[0].vertex_id, 1);
432 }
433
434 #[test]
435 fn test_quantized_delta_count() {
436 let qm = QuantizedMorph {
437 name: "cnt".to_string(),
438 deltas: vec![
439 QuantizedDelta {
440 vertex_id: 0,
441 dx: 1,
442 dy: 0,
443 dz: 0,
444 },
445 QuantizedDelta {
446 vertex_id: 1,
447 dx: 0,
448 dy: 1,
449 dz: 0,
450 },
451 ],
452 scale: 0.01,
453 original_count: 2,
454 };
455 assert_eq!(quantized_delta_count(&qm), 2);
456 }
457
458 #[test]
459 fn test_apply_quantized_morph() {
460 let qm = QuantizedMorph {
461 name: "apply".to_string(),
462 deltas: vec![QuantizedDelta {
463 vertex_id: 0,
464 dx: 100,
465 dy: 0,
466 dz: 0,
467 }],
468 scale: 0.01,
469 original_count: 1,
470 };
471 let mut positions = [[0.0f32; 3]; 3];
472 apply_quantized_morph(&mut positions, &qm, 1.0);
473 assert!((positions[0][0] - 1.0).abs() < 1e-5);
474 assert_eq!(positions[0][1], 0.0);
475 assert_eq!(positions[1][0], 0.0);
476 }
477
478 #[test]
479 fn test_apply_quantized_morph_weight() {
480 let qm = QuantizedMorph {
481 name: "w".to_string(),
482 deltas: vec![QuantizedDelta {
483 vertex_id: 0,
484 dx: 100,
485 dy: 0,
486 dz: 0,
487 }],
488 scale: 0.01,
489 original_count: 1,
490 };
491 let mut positions = [[0.0f32; 3]; 1];
492 apply_quantized_morph(&mut positions, &qm, 0.5);
493 assert!((positions[0][0] - 0.5).abs() < 1e-5);
494 }
495
496 #[test]
497 fn test_merge_quantized_morphs() {
498 let a = QuantizedMorph {
499 name: "a".to_string(),
500 deltas: vec![QuantizedDelta {
501 vertex_id: 0,
502 dx: 10,
503 dy: 0,
504 dz: 0,
505 }],
506 scale: 0.01,
507 original_count: 1,
508 };
509 let b = QuantizedMorph {
510 name: "b".to_string(),
511 deltas: vec![QuantizedDelta {
512 vertex_id: 0,
513 dx: 5,
514 dy: 0,
515 dz: 0,
516 }],
517 scale: 0.01,
518 original_count: 1,
519 };
520 let merged = merge_quantized_morphs(&a, &b);
521 assert_eq!(merged.deltas.len(), 1);
522 assert_eq!(merged.deltas[0].dx, 15);
523 }
524}