Skip to main content

oxihuman_morph/
morph_quantize.rs

1//! Morph delta compression via quantization.
2//! Compresses morph delta data to fewer bits for efficient storage and transfer.
3
4#[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    // original: u32 + 3xf32 = 4 + 12 = 16 bytes per entry
96    // quantized: u32 + 3xi16 = 4 + 6 = 10 bytes per entry
97    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            // dropped delta — error is the magnitude of original
119            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/// Pack to bytes: `[name_len: u32][name bytes][scale: f32][original_count: u32][delta_count: u32][deltas...]`
127#[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/// Parse from bytes: returns None if malformed
151#[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    // each delta: u32 + i16 + i16 + i16 = 10 bytes
184    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/// Merge two quantized morphs (same scale assumed). If vertex appears in both, sum the deltas.
215#[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/// Remove entries where dx=dy=dz=0
250#[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]), // zero — should be filtered
284        ]
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        // zero delta should be filtered
330        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        // only vertex 1 should pass threshold
343        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}