use super::force::Force;
use super::sim_node::SimNode;
pub struct CenterForce {
target_x: f32,
target_y: f32,
strength: f32,
}
impl Default for CenterForce {
fn default() -> Self {
Self { target_x: 0.0, target_y: 0.0, strength: 1.0 }
}
}
impl CenterForce {
pub fn new() -> Self {
Self::default()
}
pub fn target(mut self, x: f32, y: f32) -> Self {
self.target_x = x;
self.target_y = y;
self
}
pub fn strength(mut self, strength: f32) -> Self {
self.strength = strength;
self
}
pub fn set_target(&mut self, x: f32, y: f32) {
self.target_x = x;
self.target_y = y;
}
pub fn set_strength(&mut self, strength: f32) {
self.strength = strength;
}
}
impl Force for CenterForce {
fn apply(&mut self, nodes: &mut [SimNode], _alpha: f32) {
let n = nodes.len();
if n == 0 {
return;
}
let (mut sx, mut sy) = (0.0_f32, 0.0_f32);
for node in nodes.iter() {
sx += node.x;
sy += node.y;
}
let inv_n = 1.0 / n as f32;
let shift_x = (self.target_x - sx * inv_n) * self.strength;
let shift_y = (self.target_y - sy * inv_n) * self.strength;
for node in nodes.iter_mut() {
node.x += shift_x;
node.y += shift_y;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn centroid_moves_toward_target_with_unit_strength() {
let mut nodes = vec![
SimNode::new(10.0, 10.0),
SimNode::new(20.0, 30.0),
SimNode::new(30.0, 50.0),
];
let mut f = CenterForce::new().target(0.0, 0.0).strength(1.0);
f.apply(&mut nodes, 1.0);
let cx: f32 = nodes.iter().map(|n| n.x).sum::<f32>() / nodes.len() as f32;
let cy: f32 = nodes.iter().map(|n| n.y).sum::<f32>() / nodes.len() as f32;
assert!(cx.abs() < 1e-4, "cx={cx}");
assert!(cy.abs() < 1e-4, "cy={cy}");
}
#[test]
fn relative_distances_preserved() {
let mut nodes = vec![SimNode::new(0.0, 0.0), SimNode::new(10.0, 0.0)];
let before = nodes[1].x - nodes[0].x;
let mut f = CenterForce::new().target(100.0, 100.0);
f.apply(&mut nodes, 1.0);
let after = nodes[1].x - nodes[0].x;
assert!((before - after).abs() < 1e-4);
}
}