oxihuman_morph/
sparse_blend_shape.rs1#![allow(dead_code)]
4
5#[derive(Debug, Clone)]
9pub struct SparseDelta {
10 pub vertex_index: u32,
11 pub delta: [f32; 3],
12}
13
14#[derive(Debug, Clone)]
16pub struct SparseBlendShape {
17 pub name: String,
18 pub deltas: Vec<SparseDelta>,
19 pub total_vertex_count: usize,
20 pub weight: f32,
21 pub enabled: bool,
22}
23
24impl SparseBlendShape {
25 pub fn new(name: impl Into<String>, total_vertex_count: usize) -> Self {
26 SparseBlendShape {
27 name: name.into(),
28 deltas: Vec::new(),
29 total_vertex_count,
30 weight: 0.0,
31 enabled: true,
32 }
33 }
34}
35
36pub fn new_sparse_blend_shape(
38 name: impl Into<String>,
39 total_vertex_count: usize,
40) -> SparseBlendShape {
41 SparseBlendShape::new(name, total_vertex_count)
42}
43
44pub fn sbs_add_delta(shape: &mut SparseBlendShape, delta: SparseDelta) {
46 shape.deltas.push(delta);
47}
48
49pub fn sbs_apply(shape: &SparseBlendShape, positions: &mut [[f32; 3]]) {
51 for d in &shape.deltas {
53 let idx = d.vertex_index as usize;
54 if idx < positions.len() {
55 positions[idx][0] += d.delta[0] * shape.weight;
56 positions[idx][1] += d.delta[1] * shape.weight;
57 positions[idx][2] += d.delta[2] * shape.weight;
58 }
59 }
60}
61
62pub fn sbs_delta_count(shape: &SparseBlendShape) -> usize {
64 shape.deltas.len()
65}
66
67pub fn sbs_set_weight(shape: &mut SparseBlendShape, weight: f32) {
69 shape.weight = weight.clamp(0.0, 1.0);
70}
71
72pub fn sbs_set_enabled(shape: &mut SparseBlendShape, enabled: bool) {
74 shape.enabled = enabled;
75}
76
77pub fn sbs_to_json(shape: &SparseBlendShape) -> String {
79 format!(
80 r#"{{"name":"{}","delta_count":{},"weight":{},"enabled":{}}}"#,
81 shape.name,
82 shape.deltas.len(),
83 shape.weight,
84 shape.enabled
85 )
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 #[test]
93 fn test_new_no_deltas() {
94 let s = new_sparse_blend_shape("brow_up", 500);
95 assert_eq!(sbs_delta_count(&s), 0 ,);
96 }
97
98 #[test]
99 fn test_add_delta() {
100 let mut s = new_sparse_blend_shape("brow_up", 500);
101 sbs_add_delta(
102 &mut s,
103 SparseDelta {
104 vertex_index: 10,
105 delta: [0.0, 0.1, 0.0],
106 },
107 );
108 assert_eq!(sbs_delta_count(&s), 1 ,);
109 }
110
111 #[test]
112 fn test_apply_with_weight() {
113 let mut s = new_sparse_blend_shape("lift", 4);
114 sbs_add_delta(
115 &mut s,
116 SparseDelta {
117 vertex_index: 0,
118 delta: [0.0, 1.0, 0.0],
119 },
120 );
121 sbs_set_weight(&mut s, 0.5);
122 let mut positions = vec![[0.0; 3]; 4];
123 sbs_apply(&s, &mut positions);
124 assert!((positions[0][1] - 0.5).abs() < 1e-5, );
125 }
126
127 #[test]
128 fn test_weight_clamped() {
129 let mut s = new_sparse_blend_shape("x", 2);
130 sbs_set_weight(&mut s, 2.0);
131 assert!((s.weight - 1.0).abs() < 1e-6, );
132 }
133
134 #[test]
135 fn test_weight_clamped_negative() {
136 let mut s = new_sparse_blend_shape("x", 2);
137 sbs_set_weight(&mut s, -0.5);
138 assert!((s.weight).abs() < 1e-6, );
139 }
140
141 #[test]
142 fn test_set_enabled() {
143 let mut s = new_sparse_blend_shape("x", 2);
144 sbs_set_enabled(&mut s, false);
145 assert!(!s.enabled ,);
146 }
147
148 #[test]
149 fn test_to_json_contains_name() {
150 let s = new_sparse_blend_shape("smile", 10);
151 let j = sbs_to_json(&s);
152 assert!(j.contains("smile") ,);
153 }
154
155 #[test]
156 fn test_apply_out_of_bounds_ignored() {
157 let mut s = new_sparse_blend_shape("x", 2);
158 sbs_add_delta(
159 &mut s,
160 SparseDelta {
161 vertex_index: 999,
162 delta: [1.0, 1.0, 1.0],
163 },
164 );
165 sbs_set_weight(&mut s, 1.0);
166 let mut positions = vec![[0.0; 3]; 2];
167 sbs_apply(&s, &mut positions);
168 assert!((positions[0][0]).abs() < 1e-6, );
169 }
170
171 #[test]
172 fn test_total_vertex_count() {
173 let s = new_sparse_blend_shape("x", 128);
174 assert_eq!(
175 s.total_vertex_count,
176 128, );
178 }
179
180 #[test]
181 fn test_enabled_default() {
182 let s = new_sparse_blend_shape("x", 2);
183 assert!(s.enabled ,);
184 }
185}