diff --git a/src/birdoids_plugin.rs b/src/birdoids_plugin.rs index 921bf872..6f46d559 100644 --- a/src/birdoids_plugin.rs +++ b/src/birdoids_plugin.rs @@ -51,7 +51,7 @@ struct PlayerBoid; #[derive(Component, Deref, DerefMut)] pub(crate) struct Velocity(Vec3); -#[derive(Component, Deref, DerefMut, PartialEq, Debug)] +#[derive(Component, Default, Deref, DerefMut, PartialEq, Debug)] pub(crate) struct Force(Vec3); #[derive(Component)] @@ -197,7 +197,10 @@ fn cohesion( for (transform, mut acceleration) in &mut boids { let neighbors = spatial_tree.within_distance(transform.translation.xy(), BOID_VIEW_RANGE); if let Some(center_mass) = center_of_boids(neighbors.iter().map(|boid| boid.0)) { - let force = cohesive_force(center_mass, transform.translation.xy()); + let force = cohesive_force( + center_mass, + transform.translation.xy() + ).unwrap_or_default(); acceleration.0 += *force * COHESION_FACTOR; } } @@ -217,7 +220,10 @@ fn separation( let accel = neighbors.iter().map(|(pos, _)| pos.extend(0.0)).fold( Vec3::ZERO, |accumulator, neighbor| { - let force = separation_force(boid_transform.translation.xy(), neighbor.xy()); + let force = separation_force( + boid_transform.translation.xy(), + neighbor.xy() + ).unwrap_or_default(); accumulator + *force * SEPARATION_FACTOR }, ); @@ -292,7 +298,7 @@ fn average_of_vec2s(points: impl Iterator) -> Option { } // f(x) = 4((x-0.5)^3 + 0.125) -fn cohesive_force(boid: Vec2, target: Vec2) -> Force { +fn cohesive_force(boid: Vec2, target: Vec2) -> Option { let deviation = target - boid; /* Scale deviation vector by the boid's view range. The curve is made to @@ -300,24 +306,32 @@ fn cohesive_force(boid: Vec2, target: Vec2) -> Force { */ let scaled = deviation / BOID_VIEW_RANGE; let mag = scaled.length(); - let cube: f32 = (mag - 0.5).powf(3.0); - let offset = cube + 0.125; - let mul = offset * 4.0; - // It's necessary to re-normalize the scaled vector here. - // This is because it needs to be a unit vector before getting a new - // magnitude assigned. - let force_vec = mul * scaled.normalize(); - Force(force_vec.extend(0.0)) + if mag > 0.0 { + let cube: f32 = (mag - 0.5).powf(3.0); + let offset = cube + 0.125; + let mul = offset * 4.0; + // It's necessary to re-normalize the scaled vector here. + // This is because it needs to be a unit vector before getting a new + // magnitude assigned. + let force_vec = mul * scaled.normalize(); + Some(Force(force_vec.extend(0.0))) + } else { + None + } } // f(x) = x^2 - 1 -fn separation_force(boid: Vec2, target: Vec2) -> Force { +fn separation_force(boid: Vec2, target: Vec2) -> Option { // Scale from BOID_VIEW_RANGE to unit space let distance_unit = (target - boid) / BOID_VIEW_RANGE; let mag = distance_unit.length(); - let force_mag = mag.powf(2.0) - 1.0; - let force = force_mag * distance_unit.normalize(); - Force(force.extend(0.0)) + if mag > 0.0 { + let force_mag = mag.powf(2.0) - 1.0; + let force = force_mag * distance_unit.normalize(); + Some(Force(force.extend(0.0))) + } else { + None + } } #[cfg(test)] @@ -339,8 +353,7 @@ mod tests{ #[test] fn check_cohesion_zero_zero() { let force = cohesive_force(Vec2::ZERO, Vec2::ZERO); - eprintln!("Cohesive force of overlapping points: {}", *force); - panic!() + assert!(force.is_none()); } // ********************* @@ -351,7 +364,7 @@ mod tests{ fn check_cohesion_midpoint_x_positive(){ // Pull right 0.5 units assert_eq!( - Force(Vec3::new(0.5, 0.0, 0.0)), + Some(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), @@ -363,7 +376,7 @@ mod tests{ fn check_cohesion_midpoint_x_negative(){ // Pull left 0.5 units assert_eq!( - Force(Vec3::new(-0.5, 0.0, 0.0)), + Some(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), @@ -375,7 +388,7 @@ mod tests{ fn check_cohesion_edge_x_positive() { // pull left 1.0 units assert_eq!( - Force(Vec3::new(1.0, 0.0, 0.0)), + Some(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), @@ -387,7 +400,7 @@ mod tests{ fn check_cohesion_edge_x_negative() { // pull left 1.0 units assert_eq!( - Force(Vec3::new(-1.0, 0.0, 0.0)), + Some(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), @@ -403,7 +416,7 @@ mod tests{ fn check_cohesion_midpoint_y_positive(){ // Pull up 0.5 units assert_eq!( - Force(Vec3::new(0.0, 0.5, 0.0)), + Some(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), @@ -415,7 +428,7 @@ mod tests{ fn check_cohesion_midpoint_y_negative(){ // Pull down 0.5 units assert_eq!( - Force(Vec3::new(0.0, -0.5, 0.0)), + Some(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), @@ -427,7 +440,7 @@ mod tests{ fn check_cohesion_edge_y_positive() { // Pull up 1.0 units assert_eq!( - Force(Vec3::new(0.0, 1.0, 0.0)), + Some(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) @@ -439,7 +452,7 @@ mod tests{ fn check_cohesion_edge_y_negative() { // pull down 0.2 units assert_eq!( - Force(Vec3::new(0.0, -1.0, 0.0)), + Some(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), @@ -454,8 +467,7 @@ mod tests{ Vec2::ZERO, Vec2::ZERO ); - eprintln!("Separation force of overlapping points: {}", *force); - panic!() + assert!(force.is_none()); } // ********************* @@ -464,7 +476,7 @@ mod tests{ #[test] fn check_separation_midpoint_x_positive(){ assert_eq!( - Force(Vec3::new(0.75, 0.0, 0.0)), // expected force + Some(Force(Vec3::new(0.75, 0.0, 0.0))), // expected force separation_force( Vec2::new(0.5 * BOID_VIEW_RANGE, 0.0), // boid position Vec2::ZERO, // obstacle position @@ -475,7 +487,7 @@ mod tests{ #[test] fn check_separation_midpoint_x_negative(){ assert_eq!( - Force(Vec3::new(-0.75, 0.0, 0.0)), // expected force + Some(Force(Vec3::new(-0.75, 0.0, 0.0))), // expected force separation_force( Vec2::new(-0.5 * BOID_VIEW_RANGE, 0.0), // boid position Vec2::ZERO, // obstacle position @@ -486,7 +498,7 @@ mod tests{ #[test] fn check_separation_edge_x_positive() { assert_eq!( - Force(Vec3::ZERO), + Some(Force(Vec3::ZERO)), separation_force( Vec2::new(1.0 * BOID_VIEW_RANGE, 0.0), Vec2::ZERO, @@ -497,7 +509,7 @@ mod tests{ #[test] fn check_separation_edge_x_negative() { assert_eq!( - Force(Vec3::ZERO), + Some(Force(Vec3::ZERO)), separation_force( Vec2::new(-1.0 * BOID_VIEW_RANGE, 0.0), Vec2::ZERO, @@ -511,7 +523,7 @@ mod tests{ #[test] fn check_separation_midpoint_y_positive(){ assert_eq!( - Force(Vec3::new(0.0, 0.75, 0.0)), + Some(Force(Vec3::new(0.0, 0.75, 0.0))), separation_force( Vec2::new(0.0, 0.5 * BOID_VIEW_RANGE), Vec2::ZERO, @@ -522,7 +534,7 @@ mod tests{ #[test] fn check_separation_midpoint_y_negative(){ assert_eq!( - Force(Vec3::new(0.0, -0.75, 0.0)), + Some(Force(Vec3::new(0.0, -0.75, 0.0))), separation_force( Vec2::new(0.0, -0.5 * BOID_VIEW_RANGE), Vec2::ZERO, @@ -533,7 +545,7 @@ mod tests{ #[test] fn check_separation_edge_y_positive() { assert_eq!( - Force(Vec3::ZERO), + Some(Force(Vec3::ZERO)), separation_force( Vec2::new(0.0, 1.0 * BOID_VIEW_RANGE), Vec2::ZERO, @@ -544,7 +556,7 @@ mod tests{ #[test] fn check_separation_edge_y_negative() { assert_eq!( - Force(Vec3::ZERO), + Some(Force(Vec3::ZERO)), separation_force( Vec2::new(0.0, -1.0 * BOID_VIEW_RANGE), Vec2::ZERO,