Skip to main content

ballistics_engine/
form_factor.rs

1//! Legacy name-based form-factor heuristics.
2//!
3//! These category multipliers are not literature form factors and must not be multiplied into a
4//! reference drag coefficient when retardation is already divided by a published/measured BC:
5//! `BC = sectional_density / form_factor` already carries the projectile's shape efficiency.
6//! The live solvers therefore do not call this module (MBA-1184). The public helpers remain for
7//! API compatibility and possible BC-estimation experiments where no measured BC is available.
8
9use crate::DragModel;
10
11/// Get default form factor based on bullet type
12pub fn get_default_form_factor(bullet_name: &str, drag_model: &DragModel) -> f64 {
13    let name_upper = bullet_name.to_uppercase();
14
15    match drag_model {
16        DragModel::G1 => {
17            if name_upper.contains("MATCH")
18                || name_upper.contains("SMK")
19                || name_upper.contains("SCENAR")
20                || name_upper.contains("BERGER")
21            {
22                0.90 // Match bullets
23            } else if name_upper.contains("VLD")
24                || name_upper.contains("HYBRID")
25                || name_upper.contains("ELD")
26            {
27                0.85 // Very low drag bullets
28            } else if name_upper.contains("FMJ") {
29                1.10 // Full metal jacket
30            } else if name_upper.contains("HUNT") || name_upper.contains("SP") {
31                1.05 // Hunting bullets
32            } else {
33                1.00 // Default
34            }
35        }
36        DragModel::G7 => {
37            if name_upper.contains("MATCH")
38                || name_upper.contains("SMK")
39                || name_upper.contains("SCENAR")
40            {
41                0.95 // Match bullets
42            } else if name_upper.contains("VLD") || name_upper.contains("HYBRID") {
43                0.90 // Very low drag bullets
44            } else if name_upper.contains("FMJ") {
45                1.15 // Full metal jacket (less efficient for G7)
46            } else {
47                1.05 // Default G7
48            }
49        }
50        _ => 1.00, // Default form factor for other models
51    }
52}
53
54/// Apply form factor to drag coefficient
55pub fn apply_form_factor_to_drag(
56    cd_base: f64,
57    bullet_name: Option<&str>,
58    drag_model: &DragModel,
59    use_form_factor: bool,
60) -> f64 {
61    if !use_form_factor {
62        return cd_base;
63    }
64
65    // Get form factor based on bullet type
66    let form_factor = if let Some(name) = bullet_name {
67        get_default_form_factor(name, drag_model)
68    } else {
69        1.0 // No correction if no bullet name
70    };
71
72    // Apply form factor to drag coefficient
73    cd_base * form_factor
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_g1_form_factors() {
82        let drag_model = DragModel::G1;
83
84        // Match bullets should have lower form factor
85        assert_eq!(get_default_form_factor("168gr SMK", &drag_model), 0.90);
86        assert_eq!(get_default_form_factor("Berger Match", &drag_model), 0.90);
87        assert_eq!(get_default_form_factor("Lapua Scenar", &drag_model), 0.90);
88
89        // VLD bullets should have even lower
90        assert_eq!(get_default_form_factor("VLD Target", &drag_model), 0.85);
91        assert_eq!(get_default_form_factor("ELD-X", &drag_model), 0.85); // ELD without "Match"
92        assert_eq!(get_default_form_factor("Hybrid OTM", &drag_model), 0.85); // Hybrid without "Match"
93
94        // FMJ should have higher form factor
95        assert_eq!(get_default_form_factor("M855 FMJ", &drag_model), 1.10);
96
97        // Hunting bullets
98        assert_eq!(get_default_form_factor("Hunting SP", &drag_model), 1.05);
99
100        // Default
101        assert_eq!(get_default_form_factor("Generic Bullet", &drag_model), 1.00);
102    }
103
104    #[test]
105    fn test_g7_form_factors() {
106        let drag_model = DragModel::G7;
107
108        // Match bullets
109        assert_eq!(get_default_form_factor("175gr SMK", &drag_model), 0.95);
110
111        // VLD bullets
112        assert_eq!(get_default_form_factor("VLD Hunting", &drag_model), 0.90);
113
114        // FMJ less efficient with G7
115        assert_eq!(get_default_form_factor("147gr FMJ", &drag_model), 1.15);
116
117        // Default G7
118        assert_eq!(get_default_form_factor("Generic", &drag_model), 1.05);
119    }
120
121    #[test]
122    fn test_apply_form_factor() {
123        let cd_base = 0.5;
124        let drag_model = DragModel::G1;
125
126        // With form factor enabled
127        let cd_with_ff = apply_form_factor_to_drag(cd_base, Some("168gr SMK"), &drag_model, true);
128        assert_eq!(cd_with_ff, 0.5 * 0.90); // 0.45
129
130        // With form factor disabled
131        let cd_without_ff =
132            apply_form_factor_to_drag(cd_base, Some("168gr SMK"), &drag_model, false);
133        assert_eq!(cd_without_ff, 0.5);
134
135        // No bullet name
136        let cd_no_name = apply_form_factor_to_drag(cd_base, None, &drag_model, true);
137        assert_eq!(cd_no_name, 0.5); // No correction
138    }
139
140    #[test]
141    fn test_case_insensitive() {
142        let drag_model = DragModel::G1;
143
144        // Should work with various cases
145        assert_eq!(get_default_form_factor("smk", &drag_model), 0.90);
146        assert_eq!(get_default_form_factor("SMK", &drag_model), 0.90);
147        assert_eq!(get_default_form_factor("SmK", &drag_model), 0.90);
148    }
149}