Skip to main content

oxihuman_morph/
omega_skin.rs

1// Copyright (C) 2026 COOLJAPAN OU (Team KitaSan)
2// SPDX-License-Identifier: Apache-2.0
3#![allow(dead_code)]
4
5//! Omega skinning deformer stub.
6
7/// Omega skin blending mode.
8#[derive(Debug, Clone, Copy, PartialEq)]
9pub enum OmegaMode {
10    Linear,
11    DualQuat,
12    Blended,
13}
14
15/// Omega skin vertex.
16#[derive(Debug, Clone)]
17pub struct OmegaVertex {
18    pub bone_index: usize,
19    pub omega_weight: f32,
20    pub lbs_weight: f32,
21}
22
23/// Omega skinning deformer.
24#[derive(Debug, Clone)]
25pub struct OmegaSkin {
26    pub vertices: Vec<OmegaVertex>,
27    pub mode: OmegaMode,
28    pub blend_factor: f32,
29}
30
31impl OmegaSkin {
32    pub fn new(vertex_count: usize) -> Self {
33        OmegaSkin {
34            vertices: (0..vertex_count)
35                .map(|_| OmegaVertex {
36                    bone_index: 0,
37                    omega_weight: 1.0,
38                    lbs_weight: 1.0,
39                })
40                .collect(),
41            mode: OmegaMode::Blended,
42            blend_factor: 0.5,
43        }
44    }
45}
46
47/// Create a new Omega skin.
48pub fn new_omega_skin(vertex_count: usize) -> OmegaSkin {
49    OmegaSkin::new(vertex_count)
50}
51
52/// Set the blend factor between LBS and DQS.
53pub fn omega_set_blend(skin: &mut OmegaSkin, factor: f32) {
54    skin.blend_factor = factor.clamp(0.0, 1.0);
55}
56
57/// Set the mode.
58pub fn omega_set_mode(skin: &mut OmegaSkin, mode: OmegaMode) {
59    skin.mode = mode;
60}
61
62/// Return vertex count.
63pub fn omega_vertex_count(skin: &OmegaSkin) -> usize {
64    skin.vertices.len()
65}
66
67/// Compute effective weight for a vertex given the blend factor.
68pub fn omega_effective_weight(skin: &OmegaSkin, vertex: usize) -> f32 {
69    if vertex >= skin.vertices.len() {
70        return 0.0;
71    }
72    let v = &skin.vertices[vertex];
73    match skin.mode {
74        OmegaMode::Linear => v.lbs_weight,
75        OmegaMode::DualQuat => v.omega_weight,
76        OmegaMode::Blended => {
77            v.lbs_weight * (1.0 - skin.blend_factor) + v.omega_weight * skin.blend_factor
78        }
79    }
80}
81
82/// Return a JSON-like string.
83pub fn omega_to_json(skin: &OmegaSkin) -> String {
84    format!(
85        r#"{{"mode":"{}","blend":{:.4},"vertices":{}}}"#,
86        match skin.mode {
87            OmegaMode::Linear => "linear",
88            OmegaMode::DualQuat => "dual_quat",
89            OmegaMode::Blended => "blended",
90        },
91        skin.blend_factor,
92        skin.vertices.len()
93    )
94}
95
96#[cfg(test)]
97mod tests {
98    use super::*;
99
100    #[test]
101    fn test_new_omega_skin_vertex_count() {
102        let s = new_omega_skin(10);
103        assert_eq!(
104            omega_vertex_count(&s),
105            10, /* vertex count must match */
106        );
107    }
108
109    #[test]
110    fn test_default_mode_blended() {
111        let s = new_omega_skin(5);
112        assert_eq!(
113            s.mode,
114            OmegaMode::Blended, /* default mode should be Blended */
115        );
116    }
117
118    #[test]
119    fn test_set_blend_clamps() {
120        let mut s = new_omega_skin(3);
121        omega_set_blend(&mut s, 2.0);
122        assert!((s.blend_factor - 1.0).abs() < 1e-5, /* blend factor clamped to 1 */);
123    }
124
125    #[test]
126    fn test_set_mode_linear() {
127        let mut s = new_omega_skin(3);
128        omega_set_mode(&mut s, OmegaMode::Linear);
129        assert_eq!(s.mode, OmegaMode::Linear /* mode should be Linear */,);
130    }
131
132    #[test]
133    fn test_effective_weight_linear_mode() {
134        let mut s = new_omega_skin(2);
135        s.vertices[0].lbs_weight = 0.8;
136        omega_set_mode(&mut s, OmegaMode::Linear);
137        let w = omega_effective_weight(&s, 0);
138        assert!((w - 0.8).abs() < 1e-5, /* linear mode uses lbs_weight */);
139    }
140
141    #[test]
142    fn test_effective_weight_dq_mode() {
143        let mut s = new_omega_skin(2);
144        s.vertices[0].omega_weight = 0.9;
145        omega_set_mode(&mut s, OmegaMode::DualQuat);
146        let w = omega_effective_weight(&s, 0);
147        assert!((w - 0.9).abs() < 1e-5 /* DQ mode uses omega_weight */,);
148    }
149
150    #[test]
151    fn test_effective_weight_out_of_bounds() {
152        let s = new_omega_skin(2);
153        let w = omega_effective_weight(&s, 99);
154        assert!((w).abs() < 1e-6 /* out-of-bounds vertex returns 0 */,);
155    }
156
157    #[test]
158    fn test_to_json_contains_mode() {
159        let s = new_omega_skin(3);
160        let j = omega_to_json(&s);
161        assert!(j.contains("mode") /* JSON must contain mode */,);
162    }
163
164    #[test]
165    fn test_default_blend_factor_half() {
166        let s = new_omega_skin(2);
167        assert!((s.blend_factor - 0.5).abs() < 1e-5, /* default blend factor is 0.5 */);
168    }
169
170    #[test]
171    fn test_blended_mode_midpoint() {
172        let mut s = new_omega_skin(1);
173        s.vertices[0].lbs_weight = 0.0;
174        s.vertices[0].omega_weight = 1.0;
175        omega_set_blend(&mut s, 0.5);
176        let w = omega_effective_weight(&s, 0);
177        assert!((w - 0.5).abs() < 1e-5, /* blended mode midpoint should be 0.5 */);
178    }
179}