Skip to main content

oxihuman_morph/
compressed_shape_key.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Compressed/quantized shape key stub.
6
7/// Quantization precision.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum QuantBits {
10    Bits8,
11    Bits16,
12}
13
14/// A compressed shape key storing quantized vertex deltas.
15#[derive(Debug, Clone)]
16pub struct CompressedShapeKey {
17    pub name: String,
18    pub vertex_count: usize,
19    pub quant_bits: QuantBits,
20    pub data: Vec<u16>,
21    pub scale: f32,
22    pub enabled: bool,
23}
24
25impl CompressedShapeKey {
26    pub fn new(name: impl Into<String>, vertex_count: usize) -> Self {
27        CompressedShapeKey {
28            name: name.into(),
29            vertex_count,
30            quant_bits: QuantBits::Bits16,
31            data: vec![0u16; vertex_count * 3],
32            scale: 1.0,
33            enabled: true,
34        }
35    }
36}
37
38/// Create a new compressed shape key.
39pub fn new_compressed_shape_key(
40    name: impl Into<String>,
41    vertex_count: usize,
42) -> CompressedShapeKey {
43    CompressedShapeKey::new(name, vertex_count)
44}
45
46/// Decode a compressed delta for a vertex component (stub).
47pub fn csk_decode_delta(key: &CompressedShapeKey, vertex: usize, component: usize) -> f32 {
48    /* Stub: dequantizes stored u16 value */
49    let idx = vertex * 3 + component;
50    if idx < key.data.len() {
51        (key.data[idx] as f32 - 32768.0) * key.scale / 32768.0
52    } else {
53        0.0
54    }
55}
56
57/// Set scale factor.
58pub fn csk_set_scale(key: &mut CompressedShapeKey, scale: f32) {
59    key.scale = scale;
60}
61
62/// Set quantization bits.
63pub fn csk_set_quant_bits(key: &mut CompressedShapeKey, bits: QuantBits) {
64    key.quant_bits = bits;
65}
66
67/// Return byte size estimate.
68pub fn csk_byte_size(key: &CompressedShapeKey) -> usize {
69    match key.quant_bits {
70        QuantBits::Bits8 => key.vertex_count * 3,
71        QuantBits::Bits16 => key.vertex_count * 3 * 2,
72    }
73}
74
75/// Enable or disable the shape key.
76pub fn csk_set_enabled(key: &mut CompressedShapeKey, enabled: bool) {
77    key.enabled = enabled;
78}
79
80/// Serialize to JSON-like string.
81pub fn csk_to_json(key: &CompressedShapeKey) -> String {
82    format!(
83        r#"{{"name":"{}","vertex_count":{},"scale":{},"enabled":{}}}"#,
84        key.name, key.vertex_count, key.scale, key.enabled
85    )
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91
92    #[test]
93    fn test_new_vertex_count() {
94        let k = new_compressed_shape_key("test", 100);
95        assert_eq!(k.vertex_count, 100 /* vertex count must match */,);
96    }
97
98    #[test]
99    fn test_data_length() {
100        let k = new_compressed_shape_key("k", 10);
101        assert_eq!(
102            k.data.len(),
103            30, /* data length must be vertex_count * 3 */
104        );
105    }
106
107    #[test]
108    fn test_decode_zero_delta() {
109        let k = new_compressed_shape_key("k", 4);
110        /* all data is 0 initially, decode should be near -scale */
111        let _ = csk_decode_delta(&k, 0, 0);
112    }
113
114    #[test]
115    fn test_set_scale() {
116        let mut k = new_compressed_shape_key("k", 2);
117        csk_set_scale(&mut k, 0.5);
118        assert!((k.scale - 0.5).abs() < 1e-6 /* scale must be set */,);
119    }
120
121    #[test]
122    fn test_set_quant_bits() {
123        let mut k = new_compressed_shape_key("k", 2);
124        csk_set_quant_bits(&mut k, QuantBits::Bits8);
125        assert_eq!(
126            k.quant_bits,
127            QuantBits::Bits8, /* quant bits must be set */
128        );
129    }
130
131    #[test]
132    fn test_byte_size_16bit() {
133        let k = new_compressed_shape_key("k", 10);
134        assert_eq!(csk_byte_size(&k), 60 /* 10 * 3 * 2 bytes for 16-bit */,);
135    }
136
137    #[test]
138    fn test_byte_size_8bit() {
139        let mut k = new_compressed_shape_key("k", 10);
140        csk_set_quant_bits(&mut k, QuantBits::Bits8);
141        assert_eq!(csk_byte_size(&k), 30 /* 10 * 3 bytes for 8-bit */,);
142    }
143
144    #[test]
145    fn test_set_enabled() {
146        let mut k = new_compressed_shape_key("k", 2);
147        csk_set_enabled(&mut k, false);
148        assert!(!k.enabled /* enabled must be false */,);
149    }
150
151    #[test]
152    fn test_to_json() {
153        let k = new_compressed_shape_key("smile", 5);
154        let j = csk_to_json(&k);
155        assert!(j.contains("smile"), /* json must contain shape key name */);
156    }
157
158    #[test]
159    fn test_enabled_default() {
160        let k = new_compressed_shape_key("k", 1);
161        assert!(k.enabled /* must be enabled by default */,);
162    }
163}