use avian3d::math::{Scalar, Vector, PI};
use crate::components::InducedDrag;
pub(super) fn apply_induced_drag(
id: &InducedDrag,
total_cl_x_area: Scalar,
s_ref: Scalar,
wing_span: Scalar,
qbar: Scalar,
vel_body_unit_global: Vector,
body_to_world: avian3d::prelude::Rotation,
) -> Vector {
let ar = wing_span * wing_span / s_ref;
let cl_aircraft = total_cl_x_area / s_ref;
let cd_i = cl_aircraft * cl_aircraft / (PI * id.oswald_factor * ar);
body_to_world * (vel_body_unit_global * (-cd_i * qbar * s_ref))
}
#[cfg(test)]
mod tests {
use avian3d::prelude::Rotation;
use super::*;
fn identity_rot() -> Rotation {
Rotation::default()
}
#[test]
fn zero_cl_produces_zero_induced_drag() {
let id = InducedDrag { oswald_factor: 0.8 };
let drag = apply_induced_drag(&id, 0.0, 10.0, 10.0, 100.0, Vector::X, identity_rot());
assert!(drag.length() < 1e-10, "zero CL → zero induced drag, got {drag:?}");
}
#[test]
fn induced_drag_opposes_velocity() {
let id = InducedDrag { oswald_factor: 0.8 };
let drag = apply_induced_drag(&id, 10.0, 10.0, 10.0, 100.0, Vector::X, identity_rot());
assert!(drag.x < 0.0, "induced drag should oppose velocity, got {drag:?}");
assert!(drag.y.abs() < 1e-10);
assert!(drag.z.abs() < 1e-10);
}
#[test]
fn induced_drag_scales_with_cl_squared() {
let id = InducedDrag { oswald_factor: 0.8 };
let drag1 = apply_induced_drag(&id, 10.0, 10.0, 10.0, 100.0, Vector::X, identity_rot());
let drag2 = apply_induced_drag(&id, 20.0, 10.0, 10.0, 100.0, Vector::X, identity_rot());
let ratio = drag2.x / drag1.x;
assert!((ratio - 4.0).abs() < 1e-5, "CD_i ∝ CL², ratio={ratio}");
}
}