use fixed::traits::ToFixed; use fixed_math::SinCos; use crate::math::{ int::{FGWide, FGi32}, vec::FGVec2, }; /// Applies DI to a given attack direction pub fn apply_directional_influence( receiver_stick: FGVec2, attack_angle: FGi32, di_max_angle: FGi32, ) -> FGVec2 { let (sin, cos) = attack_angle.sin_cos(); let base_launch_direction = FGVec2::new(cos, sin); let Some(base_launch_direction) = base_launch_direction.normalize() else { return FGVec2::ZERO; }; if receiver_stick.length_squared().is_zero() { return base_launch_direction; } let normal = FGVec2::new(-base_launch_direction.y, base_launch_direction.x); let di_amount = receiver_stick.dot(normal); let angle_change = di_amount * di_max_angle.to_fixed::(); let angle_change = angle_change.clamp( -di_max_angle.to_fixed::(), di_max_angle.to_fixed::(), ); let (sin, cos) = (attack_angle + angle_change.to_fixed::()).sin_cos(); FGVec2::new(cos, sin).normalize_or_zero() } #[cfg(test)] mod test { use crate::{ fighter::attack::directional_influence::apply_directional_influence, math::{int::FGi32, vec::FGVec2}, }; #[test] fn di_zero_passes_unmodified() { let di = apply_directional_influence(FGVec2::ZERO, FGi32::lit("0.0"), FGi32::lit("18.0")); const EPSILON: FGi32 = FGi32::from_bits(4); assert!(di.x.abs_diff(FGi32::ONE) < EPSILON); assert!(di.y.abs_diff(FGi32::ZERO) < EPSILON); } #[test] fn di_applies_on_the_correct_side() { let di = apply_directional_influence( FGVec2::lit("0.0", "1.0"), FGi32::lit("0.0"), FGi32::lit("18.0"), ); assert!(di.x > FGi32::ZERO); assert!(di.y > FGi32::ZERO); let di = apply_directional_influence( FGVec2::lit("0.0", "-1.0"), FGi32::lit("0.0"), FGi32::lit("18.0"), ); assert!(di.x > FGi32::ZERO); assert!(di.y < FGi32::ZERO); } }