#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SparseDelta {
pub vertex_index: u32,
pub delta: [f32; 3],
}
#[derive(Debug, Clone)]
pub struct SparseBlendShape {
pub name: String,
pub deltas: Vec<SparseDelta>,
pub total_vertex_count: usize,
pub weight: f32,
pub enabled: bool,
}
impl SparseBlendShape {
pub fn new(name: impl Into<String>, total_vertex_count: usize) -> Self {
SparseBlendShape {
name: name.into(),
deltas: Vec::new(),
total_vertex_count,
weight: 0.0,
enabled: true,
}
}
}
pub fn new_sparse_blend_shape(
name: impl Into<String>,
total_vertex_count: usize,
) -> SparseBlendShape {
SparseBlendShape::new(name, total_vertex_count)
}
pub fn sbs_add_delta(shape: &mut SparseBlendShape, delta: SparseDelta) {
shape.deltas.push(delta);
}
pub fn sbs_apply(shape: &SparseBlendShape, positions: &mut [[f32; 3]]) {
for d in &shape.deltas {
let idx = d.vertex_index as usize;
if idx < positions.len() {
positions[idx][0] += d.delta[0] * shape.weight;
positions[idx][1] += d.delta[1] * shape.weight;
positions[idx][2] += d.delta[2] * shape.weight;
}
}
}
pub fn sbs_delta_count(shape: &SparseBlendShape) -> usize {
shape.deltas.len()
}
pub fn sbs_set_weight(shape: &mut SparseBlendShape, weight: f32) {
shape.weight = weight.clamp(0.0, 1.0);
}
pub fn sbs_set_enabled(shape: &mut SparseBlendShape, enabled: bool) {
shape.enabled = enabled;
}
pub fn sbs_to_json(shape: &SparseBlendShape) -> String {
format!(
r#"{{"name":"{}","delta_count":{},"weight":{},"enabled":{}}}"#,
shape.name,
shape.deltas.len(),
shape.weight,
shape.enabled
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_no_deltas() {
let s = new_sparse_blend_shape("brow_up", 500);
assert_eq!(sbs_delta_count(&s), 0 ,);
}
#[test]
fn test_add_delta() {
let mut s = new_sparse_blend_shape("brow_up", 500);
sbs_add_delta(
&mut s,
SparseDelta {
vertex_index: 10,
delta: [0.0, 0.1, 0.0],
},
);
assert_eq!(sbs_delta_count(&s), 1 ,);
}
#[test]
fn test_apply_with_weight() {
let mut s = new_sparse_blend_shape("lift", 4);
sbs_add_delta(
&mut s,
SparseDelta {
vertex_index: 0,
delta: [0.0, 1.0, 0.0],
},
);
sbs_set_weight(&mut s, 0.5);
let mut positions = vec![[0.0; 3]; 4];
sbs_apply(&s, &mut positions);
assert!((positions[0][1] - 0.5).abs() < 1e-5, );
}
#[test]
fn test_weight_clamped() {
let mut s = new_sparse_blend_shape("x", 2);
sbs_set_weight(&mut s, 2.0);
assert!((s.weight - 1.0).abs() < 1e-6, );
}
#[test]
fn test_weight_clamped_negative() {
let mut s = new_sparse_blend_shape("x", 2);
sbs_set_weight(&mut s, -0.5);
assert!((s.weight).abs() < 1e-6, );
}
#[test]
fn test_set_enabled() {
let mut s = new_sparse_blend_shape("x", 2);
sbs_set_enabled(&mut s, false);
assert!(!s.enabled ,);
}
#[test]
fn test_to_json_contains_name() {
let s = new_sparse_blend_shape("smile", 10);
let j = sbs_to_json(&s);
assert!(j.contains("smile") ,);
}
#[test]
fn test_apply_out_of_bounds_ignored() {
let mut s = new_sparse_blend_shape("x", 2);
sbs_add_delta(
&mut s,
SparseDelta {
vertex_index: 999,
delta: [1.0, 1.0, 1.0],
},
);
sbs_set_weight(&mut s, 1.0);
let mut positions = vec![[0.0; 3]; 2];
sbs_apply(&s, &mut positions);
assert!((positions[0][0]).abs() < 1e-6, );
}
#[test]
fn test_total_vertex_count() {
let s = new_sparse_blend_shape("x", 128);
assert_eq!(
s.total_vertex_count,
128,
);
}
#[test]
fn test_enabled_default() {
let s = new_sparse_blend_shape("x", 2);
assert!(s.enabled ,);
}
}