#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct SoftGoalVertex {
pub vertex_index: usize,
pub goal_strength: f32,
}
#[derive(Debug, Clone)]
pub struct SoftBodyGoal {
pub vertices: Vec<SoftGoalVertex>,
pub default_strength: f32,
pub label: String,
}
pub fn new_soft_body_goal(default_strength: f32, label: &str) -> SoftBodyGoal {
SoftBodyGoal {
vertices: Vec::new(),
default_strength: default_strength.clamp(0.0, 1.0),
label: label.to_owned(),
}
}
pub fn pin_vertex(goal: &mut SoftBodyGoal, vertex_index: usize, strength: f32) {
let s = strength.clamp(0.0, 1.0);
if let Some(v) = goal
.vertices
.iter_mut()
.find(|v| v.vertex_index == vertex_index)
{
v.goal_strength = s;
} else {
goal.vertices.push(SoftGoalVertex {
vertex_index,
goal_strength: s,
});
}
}
pub fn pinned_vertex_count(goal: &SoftBodyGoal) -> usize {
goal.vertices.len()
}
pub fn average_goal_strength(goal: &SoftBodyGoal) -> f32 {
if goal.vertices.is_empty() {
return goal.default_strength;
}
let sum: f32 = goal.vertices.iter().map(|v| v.goal_strength).sum();
sum / goal.vertices.len() as f32
}
pub fn is_fully_pinned(goal: &SoftBodyGoal, vertex_index: usize) -> bool {
goal.vertices
.iter()
.find(|v| v.vertex_index == vertex_index)
.map(|v| v.goal_strength >= 0.999)
.unwrap_or(false)
}
pub fn soft_body_goal_to_json(goal: &SoftBodyGoal) -> String {
format!(
r#"{{"label":"{}", "default_strength":{:.4}, "pinned_count":{}}}"#,
goal.label,
goal.default_strength,
goal.vertices.len()
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new_goal_has_no_pins() {
let g = new_soft_body_goal(0.5, "body");
assert_eq!(pinned_vertex_count(&g), 0);
}
#[test]
fn pin_vertex_increases_count() {
let mut g = new_soft_body_goal(0.5, "body");
pin_vertex(&mut g, 0, 1.0);
assert_eq!(pinned_vertex_count(&g), 1);
}
#[test]
fn pin_same_vertex_twice_does_not_duplicate() {
let mut g = new_soft_body_goal(0.5, "body");
pin_vertex(&mut g, 0, 0.5);
pin_vertex(&mut g, 0, 0.9);
assert_eq!(pinned_vertex_count(&g), 1);
}
#[test]
fn is_fully_pinned_strength_one() {
let mut g = new_soft_body_goal(0.5, "body");
pin_vertex(&mut g, 3, 1.0);
assert!(is_fully_pinned(&g, 3));
}
#[test]
fn is_fully_pinned_partial_strength_false() {
let mut g = new_soft_body_goal(0.5, "body");
pin_vertex(&mut g, 3, 0.5);
assert!(!is_fully_pinned(&g, 3));
}
#[test]
fn average_strength_correct() {
let mut g = new_soft_body_goal(0.5, "body");
pin_vertex(&mut g, 0, 0.4);
pin_vertex(&mut g, 1, 0.6);
assert!((average_goal_strength(&g) - 0.5).abs() < 1e-5);
}
#[test]
fn default_strength_clamped_to_one() {
let g = new_soft_body_goal(2.0, "body");
assert!((g.default_strength - 1.0).abs() < 1e-5);
}
#[test]
fn json_contains_label() {
let g = new_soft_body_goal(0.5, "softGoal");
assert!(soft_body_goal_to_json(&g).contains("softGoal"));
}
#[test]
fn average_empty_returns_default() {
let g = new_soft_body_goal(0.7, "body");
assert!((average_goal_strength(&g) - 0.7).abs() < 1e-5);
}
}