oxihuman_export/
bone_bind_pose_export.rs1#![allow(dead_code)]
4
5pub type Mat4 = [f32; 16];
9
10#[allow(dead_code)]
12#[derive(Debug, Clone)]
13pub struct BoneBindPose {
14 pub name: String,
15 pub local_matrix: Mat4,
16 pub world_matrix: Mat4,
17 pub inverse_bind: Mat4,
18}
19
20#[allow(dead_code)]
22#[derive(Debug, Clone)]
23pub struct BindPoseExport {
24 pub bones: Vec<BoneBindPose>,
25}
26
27fn identity_mat4() -> Mat4 {
28 [
29 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
30 ]
31}
32
33#[allow(dead_code)]
35pub fn new_bind_pose_export() -> BindPoseExport {
36 BindPoseExport { bones: Vec::new() }
37}
38
39#[allow(dead_code)]
41pub fn add_bind_pose_bone(exp: &mut BindPoseExport, name: &str) {
42 exp.bones.push(BoneBindPose {
43 name: name.to_string(),
44 local_matrix: identity_mat4(),
45 world_matrix: identity_mat4(),
46 inverse_bind: identity_mat4(),
47 });
48}
49
50#[allow(dead_code)]
52pub fn set_local_matrix(exp: &mut BindPoseExport, idx: usize, mat: Mat4) {
53 if idx < exp.bones.len() {
54 exp.bones[idx].local_matrix = mat;
55 }
56}
57
58#[allow(dead_code)]
60pub fn bind_bone_count(exp: &BindPoseExport) -> usize {
61 exp.bones.len()
62}
63
64#[allow(dead_code)]
66pub fn find_bind_bone(exp: &BindPoseExport, name: &str) -> Option<usize> {
67 exp.bones.iter().position(|b| b.name == name)
68}
69
70#[allow(dead_code)]
72pub fn all_inverse_binds_set(exp: &BindPoseExport) -> bool {
73 exp.bones
74 .iter()
75 .all(|b| b.inverse_bind != identity_mat4() || b.world_matrix == identity_mat4())
76}
77
78#[allow(dead_code)]
80pub fn bind_pose_to_json(exp: &BindPoseExport) -> String {
81 format!("{{\"bone_count\":{}}}", bind_bone_count(exp))
82}
83
84#[allow(dead_code)]
86pub fn bind_pose_to_flat(exp: &BindPoseExport) -> Vec<f32> {
87 exp.bones.iter().flat_map(|b| b.world_matrix).collect()
88}
89
90#[allow(dead_code)]
92pub fn validate_bind_pose(exp: &BindPoseExport) -> bool {
93 exp.bones.iter().all(|b| !b.name.is_empty())
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 #[test]
101 fn new_export_empty() {
102 let exp = new_bind_pose_export();
103 assert_eq!(bind_bone_count(&exp), 0);
104 }
105
106 #[test]
107 fn add_bone_increments() {
108 let mut exp = new_bind_pose_export();
109 add_bind_pose_bone(&mut exp, "root");
110 assert_eq!(bind_bone_count(&exp), 1);
111 }
112
113 #[test]
114 fn find_existing() {
115 let mut exp = new_bind_pose_export();
116 add_bind_pose_bone(&mut exp, "spine");
117 assert!(find_bind_bone(&exp, "spine").is_some_and(|i| i == 0));
118 }
119
120 #[test]
121 fn find_missing_none() {
122 let exp = new_bind_pose_export();
123 assert!(find_bind_bone(&exp, "missing").is_none());
124 }
125
126 #[test]
127 fn set_local_matrix_updates() {
128 let mut exp = new_bind_pose_export();
129 add_bind_pose_bone(&mut exp, "a");
130 let mut mat = identity_mat4();
131 mat[12] = 5.0;
132 set_local_matrix(&mut exp, 0, mat);
133 assert!((exp.bones[0].local_matrix[12] - 5.0).abs() < 1e-5);
134 }
135
136 #[test]
137 fn flat_output_length() {
138 let mut exp = new_bind_pose_export();
139 add_bind_pose_bone(&mut exp, "a");
140 add_bind_pose_bone(&mut exp, "b");
141 assert_eq!(bind_pose_to_flat(&exp).len(), 32);
142 }
143
144 #[test]
145 fn json_contains_bone_count() {
146 let exp = new_bind_pose_export();
147 let j = bind_pose_to_json(&exp);
148 assert!(j.contains("bone_count"));
149 }
150
151 #[test]
152 fn validate_valid() {
153 let mut exp = new_bind_pose_export();
154 add_bind_pose_bone(&mut exp, "hip");
155 assert!(validate_bind_pose(&exp));
156 }
157
158 #[test]
159 fn identity_mat4_diagonal_one() {
160 let m = identity_mat4();
161 assert!((m[0] - 1.0).abs() < 1e-6);
162 assert!((m[5] - 1.0).abs() < 1e-6);
163 }
164
165 #[test]
166 fn contains_range() {
167 let v = 0.5_f32;
168 assert!((0.0..=1.0).contains(&v));
169 }
170}