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.
This commit is contained in:
2024-07-15 11:38:39 -05:00
parent 651ed774c3
commit c48fe1ed2e

View File

@@ -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,
)
)
}
}