From c48fe1ed2e18e325726108156314bc8ad19c5d7f Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Mon, 15 Jul 2024 11:38:39 -0500 Subject: [PATCH] Add unit tests for force functions Finally time to just use the unit testing tools Cargo gives me. These are all a bunch of simple tests to verify that the functions work as expected. ... They don't. I made the assertion that the input ranges are (0, 1) when, in fact, they are not. The boid's view distance is scaled into a unit vector space. The trouble with this is that a valid coordinate is (-1, 0). The force functions don't have the correct shape at this range. The fix is to get the absolute value -- the vector's magnitude -- and feed that through the force calculation function. This way it *is* in the range (0, 1). The magnitude can then be multiplied back over the deviation vector to get a proper force application. This unit vector can then optionally be multiplied back up to have more exaggerated effects. The {COHESION,SEPARATION,ALIGNMENT}_FACTOR's can pick up that role again. --- src/birdoids_plugin.rs | 225 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 223 insertions(+), 2 deletions(-) diff --git a/src/birdoids_plugin.rs b/src/birdoids_plugin.rs index 52b3c3eb..57856e8b 100644 --- a/src/birdoids_plugin.rs +++ b/src/birdoids_plugin.rs @@ -10,7 +10,7 @@ const TURN_FACTOR: f32 = 1.0; const BOID_VIEW_RANGE: f32 = 50.0; const COHESION_FACTOR: f32 = 1.0; const SEPARATION_FACTOR: f32 = 1.0; -const ALIGNMENT_FACTOR: f32 = 500.0; +const ALIGNMENT_FACTOR: f32 = 1.0; const SPACEBRAKES_COEFFICIENT: f32 = 0.01; pub struct BoidsPlugin; @@ -51,7 +51,7 @@ struct PlayerBoid; #[derive(Component, Deref, DerefMut)] pub(crate) struct Velocity(Vec3); -#[derive(Component, Deref, DerefMut)] +#[derive(Component, Deref, DerefMut, PartialEq, Debug)] pub(crate) struct Force(Vec3); #[derive(Component)] @@ -311,3 +311,224 @@ fn separation_force(us: Vec2, neighbor: Vec2) -> Force { let force_vec = -scaled.powf(2.0) + Vec2::ONE; Force(force_vec.extend(0.0)) } + +#[cfg(test)] +mod tests{ + use bevy::prelude::*; + + use crate::birdoids_plugin::{ + cohesive_force, separation_force + }; + + use super::{ + BOID_VIEW_RANGE, + Force, + }; + + // forces are relative to the boid's view range, so all + // distances need to be fractions of that + + #[test] + fn check_cohesion_zero_zero() { + todo!("Make test for cohesion_force when boid and target are at the same point") + } + + // ********************* + // Cohesion x-axis tests + // ********************* + + #[test] + fn check_cohesion_midpoint_x_positive(){ + // Pull right 0.5 units + assert_eq!( + Force(Vec3::new(0.5, 0.0, 0.0)), + cohesive_force( + Vec2::new(0.0, 0.0), + Vec2::new(0.5 * BOID_VIEW_RANGE, 0.0), + ) + ); + } + + #[test] + fn check_cohesion_midpoint_x_negative(){ + // Pull left 0.5 units + assert_eq!( + Force(Vec3::new(-0.5, 0.0, 0.0)), + cohesive_force( + Vec2::new(0.0, 0.0), + Vec2::new(-0.5 * BOID_VIEW_RANGE, 0.0), + ) + ); + } + + #[test] + fn check_cohesion_edge_x_positive() { + // pull left 1.0 units + assert_eq!( + Force(Vec3::new(1.0, 0.0, 0.0)), + cohesive_force( + Vec2::new(0.0, 0.0), + Vec2::new(1.0 * BOID_VIEW_RANGE, 0.0), + ) + ); + } + + #[test] + fn check_cohesion_edge_x_negative() { + // pull left 1.0 units + assert_eq!( + Force(Vec3::new(-1.0, 0.0, 0.0)), + cohesive_force( + Vec2::new(0.0, 0.0), + Vec2::new(-1.0 * BOID_VIEW_RANGE, 0.0), + ) + ); + } + + // ********************* + // Cohesion y-axis tests + // ********************* + + #[test] + fn check_cohesion_midpoint_y_positive(){ + // Pull up 0.5 units + assert_eq!( + Force(Vec3::new(0.0, 0.5, 0.0)), + cohesive_force( + Vec2::new(0.0, 0.0), + Vec2::new(0.0, 0.5 * BOID_VIEW_RANGE), + ) + ); + } + + #[test] + fn check_cohesion_midpoint_y_negative(){ + // Pull down 0.5 units + assert_eq!( + Force(Vec3::new(0.0, -0.5, 0.0)), + cohesive_force( + Vec2::new(0.0, 0.0), + Vec2::new(0.0, -0.5 * BOID_VIEW_RANGE), + ) + ); + } + + #[test] + fn check_cohesion_edge_y_positive() { + // Pull up 1.0 units + assert_eq!( + Force(Vec3::new(0.0, 1.0, 0.0)), + cohesive_force( + Vec2::new(0.0, 0.0), + Vec2::new(0.0, 1.0 * BOID_VIEW_RANGE) + ) + ); + } + + #[test] + fn check_cohesion_edge_y_negative() { + // pull down 0.2 units + assert_eq!( + Force(Vec3::new(0.0, -1.0, 0.0)), + cohesive_force( + Vec2::new(0.0, 0.0), + Vec2::new(0.0, -1.0 * BOID_VIEW_RANGE), + ) + ); + } + + // ********************* + // Separation x-axis tests + // ********************* + #[test] + fn check_separation_midpoint_x_positive(){ + assert_eq!( + Force(Vec3::new(0.75, 0.0, 0.0)), // expected force + separation_force( + Vec2::new(0.5, 0.0), // boid position + Vec2::ZERO, // obstacle position + ) + ); + } + + #[test] + fn check_separation_midpoint_x_negative(){ + assert_eq!( + Force(Vec3::new(-0.75, 0.0, 0.0)), // expected force + separation_force( + Vec2::new(-0.5, 0.0), // boid position + Vec2::ZERO, // obstacle position + ) + ); + } + + #[test] + fn check_separation_edge_x_positive() { + assert_eq!( + Force(Vec3::ZERO), + separation_force( + Vec2::new(1.0, 0.0), + Vec2::ZERO, + ), + ); + } + + #[test] + fn check_separation_edge_x_negative() { + assert_eq!( + Force(Vec3::ZERO), + separation_force( + Vec2::new(-1.0, 0.0), + Vec2::ZERO, + ), + ); + } + + // ********************* + // Separation y-axis tests + // ********************* + #[test] + fn check_separation_midpoint_y_positive(){ + assert_eq!( + Force(Vec3::new(0.0, 0.75, 0.0)), + separation_force( + Vec2::new(0.0, 0.5), + Vec2::ZERO, + ) + ); + } + + #[test] + fn check_separation_midpoint_y_negative(){ + assert_eq!( + Force(Vec3::new(0.0, -0.75, 0.0)), + separation_force( + Vec2::new(0.0, -0.5), + Vec2::ZERO, + ) + ); + } + + #[test] + fn check_separation_edge_y_positive() { + assert_eq!( + Force(Vec3::ZERO), + separation_force( + Vec2::new(0.0, 1.0), + Vec2::ZERO, + ) + ) + } + + #[test] + fn check_separation_edge_y_negative() { + assert_eq!( + Force(Vec3::ZERO), + separation_force( + Vec2::new(0.0, -1.0), + Vec2::ZERO, + ) + ) + } + +} \ No newline at end of file