#[derive(Debug, Clone, Copy, PartialEq)]
pub enum ProjectileShape {
Spitzer, RoundNose, FlatBase, BoatTail, }
impl ProjectileShape {
pub fn from_str(s: &str) -> Self {
match s.to_lowercase().as_str() {
"spitzer" => Self::Spitzer,
"round_nose" => Self::RoundNose,
"flat_base" => Self::FlatBase,
"boat_tail" => Self::BoatTail,
_ => Self::Spitzer, }
}
}
#[allow(dead_code)]
fn prandtl_glauert_correction(mach: f64) -> f64 {
if mach >= 0.99 {
return 10.0;
}
let beta = (1.0 - mach * mach).sqrt();
1.0 / beta
}
fn critical_mach_number(shape: ProjectileShape) -> f64 {
match shape {
ProjectileShape::Spitzer => 0.85,
ProjectileShape::RoundNose => 0.75,
ProjectileShape::FlatBase => 0.70,
ProjectileShape::BoatTail => 0.88,
}
}
pub fn transonic_drag_rise(mach: f64, shape: ProjectileShape) -> f64 {
let m_crit = critical_mach_number(shape);
if mach < m_crit {
return 1.0;
}
if mach < 1.0 {
let denominator = 1.0 - m_crit;
if denominator.abs() < f64::EPSILON {
return 1.0; }
let progress = (mach - m_crit) / denominator;
if progress < 0.0 {
return 1.0;
}
let rise_factor = match shape {
ProjectileShape::BoatTail => {
1.0 + 1.2 * progress.powi(2)
}
ProjectileShape::RoundNose => {
1.0 + 2.0 * progress.powf(1.5)
}
_ => {
1.0 + 1.5 * progress.powf(1.8)
}
};
let rise_factor = if mach > 0.92 {
let comp_progress = (mach - 0.92) / 0.08;
let compressibility = 1.0 + 0.5 * comp_progress.powi(3);
rise_factor * compressibility
} else {
rise_factor
};
rise_factor.min(2.5) } else if mach < 1.2 {
let peak_mach = match shape {
ProjectileShape::Spitzer => 1.05,
_ => 1.02,
};
if mach <= peak_mach {
let base_rise = 1.8; let shape_factor = match shape {
ProjectileShape::RoundNose => 1.2,
_ => 1.0,
};
base_rise * shape_factor * (1.0 + 0.3 * (mach - 1.0))
} else {
let peak_drag = match shape {
ProjectileShape::RoundNose => 2.2,
_ => 1.8,
};
let decline_rate = 3.0; peak_drag * (-(decline_rate * (mach - peak_mach))).exp()
}
} else {
1.0
}
}
fn wave_drag_coefficient(mach: f64, shape: ProjectileShape) -> f64 {
if mach < 0.8 {
return 0.0;
}
if mach < 1.0 {
let m_crit = critical_mach_number(shape);
if mach < m_crit {
return 0.0;
}
let denominator = 1.0 - m_crit;
if denominator.abs() < f64::EPSILON {
return 0.0; }
let progress = (mach - m_crit) / denominator;
let max_subsonic_wave = 0.1;
max_subsonic_wave * progress.powi(2)
} else {
let fineness_ratio = 3.5;
let cd_wave_base = 0.15 / fineness_ratio;
let mach_factor = 1.0
/ (mach * mach - 1.0)
.max(crate::constants::MIN_MACH_THRESHOLD)
.sqrt();
let shape_factor = match shape {
ProjectileShape::Spitzer => 0.8, ProjectileShape::RoundNose => 1.2, ProjectileShape::FlatBase => 1.5, ProjectileShape::BoatTail => 0.7, };
cd_wave_base * mach_factor * shape_factor
}
}
pub fn transonic_correction(
mach: f64,
base_cd: f64,
shape: ProjectileShape,
include_wave_drag: bool,
) -> f64 {
let rise_factor = transonic_drag_rise(mach, shape);
let mut corrected_cd = base_cd * rise_factor;
if include_wave_drag && mach > 0.8 {
let wave_cd = wave_drag_coefficient(mach, shape);
corrected_cd += wave_cd;
}
corrected_cd
}
pub fn get_projectile_shape(caliber: f64, weight_grains: f64, g_model: &str) -> ProjectileShape {
if g_model == "G7" {
return ProjectileShape::BoatTail;
}
let weight_per_caliber = weight_grains / caliber;
if weight_per_caliber > 500.0 {
return ProjectileShape::BoatTail;
}
if caliber < 0.35 {
ProjectileShape::Spitzer
} else {
ProjectileShape::RoundNose
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_prandtl_glauert() {
assert!((prandtl_glauert_correction(0.5) - 1.1547).abs() < 0.001);
assert!((prandtl_glauert_correction(0.8) - 1.6667).abs() < 0.001);
assert!((prandtl_glauert_correction(0.95) - 3.2026).abs() < 0.001);
assert_eq!(prandtl_glauert_correction(0.99), 10.0);
}
#[test]
fn test_critical_mach() {
assert_eq!(critical_mach_number(ProjectileShape::Spitzer), 0.85);
assert_eq!(critical_mach_number(ProjectileShape::BoatTail), 0.88);
assert_eq!(critical_mach_number(ProjectileShape::FlatBase), 0.70);
}
#[test]
fn test_transonic_drag_rise() {
let shape = ProjectileShape::Spitzer;
assert_eq!(transonic_drag_rise(0.8, shape), 1.0);
let rise_0_9 = transonic_drag_rise(0.9, shape);
assert!(rise_0_9 > 1.0 && rise_0_9 < 2.0);
let rise_0_98 = transonic_drag_rise(0.98, shape);
assert!(rise_0_98 > 2.0);
let rise_1_1 = transonic_drag_rise(1.1, shape);
assert!(rise_1_1 > 1.5 && rise_1_1 < 2.5);
}
#[test]
fn test_projectile_shape_estimation() {
let shape = get_projectile_shape(0.308, 175.0, "G7");
assert!(matches!(shape, ProjectileShape::BoatTail));
let shape1 = get_projectile_shape(0.308, 200.0, "G1");
assert!(matches!(
shape1,
ProjectileShape::Spitzer | ProjectileShape::BoatTail | ProjectileShape::RoundNose
));
let shape2 = get_projectile_shape(0.224, 55.0, "G1");
assert!(matches!(
shape2,
ProjectileShape::Spitzer | ProjectileShape::BoatTail | ProjectileShape::RoundNose
));
let shape3 = get_projectile_shape(0.50, 300.0, "G1");
assert!(matches!(
shape3,
ProjectileShape::Spitzer | ProjectileShape::BoatTail | ProjectileShape::RoundNose
));
}
}