#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct AnimContribution {
pub name: String,
pub weight: f32,
pub positions: Vec<[f32; 3]>,
}
#[derive(Debug, Default, Clone)]
pub struct BlendAnimBuffer {
pub contributions: Vec<AnimContribution>,
pub vertex_count: usize,
}
impl BlendAnimBuffer {
pub fn new(vertex_count: usize) -> Self {
Self {
vertex_count,
contributions: Vec::new(),
}
}
pub fn add_contribution(&mut self, contrib: AnimContribution) {
self.contributions.push(contrib);
}
pub fn total_weight(&self) -> f32 {
self.contributions.iter().map(|c| c.weight).sum()
}
pub fn contribution_count(&self) -> usize {
self.contributions.len()
}
pub fn clear(&mut self) {
self.contributions.clear();
}
}
#[allow(clippy::needless_range_loop)]
pub fn evaluate_blend(buf: &BlendAnimBuffer) -> Vec<[f32; 3]> {
let n = buf.vertex_count;
let total = buf.total_weight();
if total < f32::EPSILON || n == 0 {
return vec![[0.0; 3]; n];
}
let mut result = vec![[0.0f32; 3]; n];
for contrib in &buf.contributions {
let w = contrib.weight / total;
for i in 0..n.min(contrib.positions.len()) {
result[i][0] += contrib.positions[i][0] * w;
result[i][1] += contrib.positions[i][1] * w;
result[i][2] += contrib.positions[i][2] * w;
}
}
result
}
pub fn normalize_weights(buf: &mut BlendAnimBuffer) {
let total = buf.total_weight();
if total < f32::EPSILON {
return;
}
for c in buf.contributions.iter_mut() {
c.weight /= total;
}
}
pub fn prune_zero_weights(buf: &mut BlendAnimBuffer) {
buf.contributions.retain(|c| c.weight > 0.0);
}
pub fn dominant_contribution(buf: &BlendAnimBuffer) -> Option<&AnimContribution> {
buf.contributions.iter().max_by(|a, b| {
a.weight
.partial_cmp(&b.weight)
.unwrap_or(std::cmp::Ordering::Equal)
})
}
#[cfg(test)]
mod tests {
use super::*;
fn make_contrib(name: &str, weight: f32, n: usize) -> AnimContribution {
AnimContribution {
name: name.to_string(),
weight,
positions: vec![[1.0, 0.0, 0.0]; n],
}
}
#[test]
fn test_new_blend_buffer_empty() {
assert_eq!(BlendAnimBuffer::new(10).contribution_count(), 0);
}
#[test]
fn test_total_weight_empty() {
assert_eq!(BlendAnimBuffer::new(10).total_weight(), 0.0);
}
#[test]
fn test_add_contribution() {
let mut buf = BlendAnimBuffer::new(5);
buf.add_contribution(make_contrib("walk", 1.0, 5));
assert_eq!(buf.contribution_count(), 1);
}
#[test]
fn test_total_weight_accumulates() {
let mut buf = BlendAnimBuffer::new(5);
buf.add_contribution(make_contrib("a", 0.3, 5));
buf.add_contribution(make_contrib("b", 0.7, 5));
assert!((buf.total_weight() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_evaluate_blend_single() {
let mut buf = BlendAnimBuffer::new(1);
buf.add_contribution(make_contrib("a", 1.0, 1));
let r = evaluate_blend(&buf);
assert!((r[0][0] - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_normalize_weights() {
let mut buf = BlendAnimBuffer::new(2);
buf.add_contribution(make_contrib("a", 2.0, 2));
buf.add_contribution(make_contrib("b", 2.0, 2));
normalize_weights(&mut buf);
assert!((buf.total_weight() - 1.0).abs() < f32::EPSILON);
}
#[test]
fn test_prune_zero_weights() {
let mut buf = BlendAnimBuffer::new(2);
buf.add_contribution(make_contrib("zero", 0.0, 2));
buf.add_contribution(make_contrib("positive", 0.5, 2));
prune_zero_weights(&mut buf);
assert_eq!(buf.contribution_count(), 1);
}
#[test]
fn test_dominant_contribution_none_empty() {
assert!(dominant_contribution(&BlendAnimBuffer::new(5)).is_none());
}
#[test]
fn test_dominant_contribution_found() {
let mut buf = BlendAnimBuffer::new(2);
buf.add_contribution(make_contrib("a", 0.2, 2));
buf.add_contribution(make_contrib("b", 0.8, 2));
assert_eq!(dominant_contribution(&buf).expect("should succeed").name, "b");
}
#[test]
fn test_clear() {
let mut buf = BlendAnimBuffer::new(2);
buf.add_contribution(make_contrib("a", 1.0, 2));
buf.clear();
assert_eq!(buf.contribution_count(), 0);
}
}