Skip to main content

oxihuman_morph/
cage_morph.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Cage (volumetric) morph target deformer stub.
6
7/// A cage vertex used for volumetric morph deformation.
8#[derive(Debug, Clone, Copy)]
9pub struct CageVertex {
10    pub position: [f32; 3],
11    pub delta: [f32; 3],
12}
13
14/// A cage morph target.
15#[derive(Debug, Clone)]
16pub struct CageMorph {
17    pub cage_vertices: Vec<CageVertex>,
18    pub weight: f32,
19}
20
21impl CageMorph {
22    pub fn new(vertex_count: usize) -> Self {
23        CageMorph {
24            cage_vertices: (0..vertex_count)
25                .map(|_| CageVertex {
26                    position: [0.0; 3],
27                    delta: [0.0; 3],
28                })
29                .collect(),
30            weight: 0.0,
31        }
32    }
33}
34
35/// Create a new cage morph with given vertex count.
36pub fn new_cage_morph(vertex_count: usize) -> CageMorph {
37    CageMorph::new(vertex_count)
38}
39
40/// Set the cage vertex position and delta.
41pub fn cage_set_vertex(morph: &mut CageMorph, index: usize, position: [f32; 3], delta: [f32; 3]) {
42    if index < morph.cage_vertices.len() {
43        morph.cage_vertices[index] = CageVertex { position, delta };
44    }
45}
46
47/// Set the blend weight.
48pub fn cage_set_weight(morph: &mut CageMorph, weight: f32) {
49    morph.weight = weight.clamp(0.0, 1.0);
50}
51
52/// Return a JSON-like string.
53pub fn cage_to_json(morph: &CageMorph) -> String {
54    format!(
55        r#"{{"cage_vertices":{},"weight":{:.4}}}"#,
56        morph.cage_vertices.len(),
57        morph.weight
58    )
59}
60
61/// Return the number of cage vertices.
62pub fn cage_vertex_count(morph: &CageMorph) -> usize {
63    morph.cage_vertices.len()
64}
65
66/// Return total delta magnitude (sum of delta lengths).
67pub fn cage_total_delta_magnitude(morph: &CageMorph) -> f32 {
68    morph
69        .cage_vertices
70        .iter()
71        .map(|v| {
72            (v.delta[0] * v.delta[0] + v.delta[1] * v.delta[1] + v.delta[2] * v.delta[2]).sqrt()
73        })
74        .sum()
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80
81    #[test]
82    fn test_new_cage_vertex_count() {
83        let c = new_cage_morph(12);
84        assert_eq!(cage_vertex_count(&c), 12 /* vertex count must match */,);
85    }
86
87    #[test]
88    fn test_initial_weight_zero() {
89        let c = new_cage_morph(5);
90        assert!((c.weight).abs() < 1e-6, /* initial weight should be 0 */);
91    }
92
93    #[test]
94    fn test_set_vertex_updates() {
95        let mut c = new_cage_morph(4);
96        cage_set_vertex(&mut c, 0, [1.0, 2.0, 3.0], [0.1, 0.2, 0.3]);
97        let v = c.cage_vertices[0];
98        assert!((v.position[0] - 1.0).abs() < 1e-5, /* position x must match */);
99    }
100
101    #[test]
102    fn test_set_weight_clamps_to_one() {
103        let mut c = new_cage_morph(2);
104        cage_set_weight(&mut c, 5.0);
105        assert!((c.weight - 1.0).abs() < 1e-5 /* weight clamped to 1 */,);
106    }
107
108    #[test]
109    fn test_set_weight_negative_clamps() {
110        let mut c = new_cage_morph(2);
111        cage_set_weight(&mut c, -2.0);
112        assert!((c.weight).abs() < 1e-6, /* negative weight clamped to 0 */);
113    }
114
115    #[test]
116    fn test_to_json_contains_weight() {
117        let c = new_cage_morph(3);
118        let j = cage_to_json(&c);
119        assert!(j.contains("weight") /* JSON must contain weight */,);
120    }
121
122    #[test]
123    fn test_total_delta_zero_initially() {
124        let c = new_cage_morph(6);
125        assert!((cage_total_delta_magnitude(&c)).abs() < 1e-6, /* total delta is 0 initially */);
126    }
127
128    #[test]
129    fn test_total_delta_after_set() {
130        let mut c = new_cage_morph(2);
131        cage_set_vertex(&mut c, 0, [0.0; 3], [1.0, 0.0, 0.0]);
132        assert!(cage_total_delta_magnitude(&c) > 0.0, /* delta should be positive */);
133    }
134
135    #[test]
136    fn test_set_out_of_bounds_ignored() {
137        let mut c = new_cage_morph(2);
138        cage_set_vertex(&mut c, 999, [1.0, 0.0, 0.0], [1.0, 0.0, 0.0]);
139        assert_eq!(
140            cage_vertex_count(&c),
141            2, /* vertex count should be unchanged */
142        );
143    }
144
145    #[test]
146    fn test_json_vertex_count() {
147        let c = new_cage_morph(7);
148        let j = cage_to_json(&c);
149        assert!(j.contains("7") /* JSON should contain vertex count */,);
150    }
151}