1use std::f64::consts::PI;
8
9#[derive(Debug, Clone)]
11pub struct SpinDriftCoefficients {
12 pub litz_coefficient: f64,
14 pub mccoy_jump_factor: f64,
16 pub transonic_factor: f64,
18 pub yaw_damping: f64,
20}
21
22impl SpinDriftCoefficients {
23 pub fn for_bullet_type(bullet_type: &str) -> Self {
25 match bullet_type.to_lowercase().as_str() {
26 "match" | "bthp" | "boat_tail" => Self {
27 litz_coefficient: 1.25,
28 mccoy_jump_factor: 0.85,
29 transonic_factor: 0.75,
30 yaw_damping: 0.92,
31 },
32 "vld" | "very_low_drag" => Self {
33 litz_coefficient: 1.15,
34 mccoy_jump_factor: 0.78,
35 transonic_factor: 0.68,
36 yaw_damping: 0.88,
37 },
38 "hybrid" | "hybrid_ogive" => Self {
39 litz_coefficient: 1.20,
40 mccoy_jump_factor: 0.82,
41 transonic_factor: 0.72,
42 yaw_damping: 0.90,
43 },
44 "flat_base" | "fb" => Self {
45 litz_coefficient: 1.35,
46 mccoy_jump_factor: 0.95,
47 transonic_factor: 0.85,
48 yaw_damping: 0.95,
49 },
50 _ => Self::default(),
51 }
52 }
53
54 pub fn default() -> Self {
55 Self {
56 litz_coefficient: 1.25,
57 mccoy_jump_factor: 0.85,
58 transonic_factor: 0.75,
59 yaw_damping: 0.92,
60 }
61 }
62}
63
64pub fn calculate_advanced_spin_drift(
66 stability_factor: f64,
67 time_of_flight_s: f64,
68 velocity_mps: f64,
69 muzzle_velocity_mps: f64,
70 spin_rate_rad_s: f64,
71 caliber_m: f64,
72 mass_kg: f64,
73 air_density_kg_m3: f64,
74 is_right_twist: bool,
75 bullet_type: &str,
76) -> f64 {
77 if time_of_flight_s <= 0.0 || stability_factor <= 0.0
80 || muzzle_velocity_mps <= 0.0 || air_density_kg_m3 <= 0.0 {
81 return 0.0;
82 }
83
84 let coeffs = SpinDriftCoefficients::for_bullet_type(bullet_type);
85
86 let sign = if is_right_twist { 1.0 } else { -1.0 };
88
89 let mach_current = velocity_mps / 343.0;
91 let mach_muzzle = muzzle_velocity_mps / 343.0;
92
93 let litz_drift = calculate_litz_drift(
95 stability_factor,
96 time_of_flight_s,
97 coeffs.litz_coefficient,
98 );
99
100 let jump_correction = calculate_aerodynamic_jump_correction(
102 mach_muzzle,
103 spin_rate_rad_s,
104 caliber_m,
105 mass_kg,
106 coeffs.mccoy_jump_factor,
107 );
108
109 let transonic_correction = calculate_transonic_correction(
111 mach_current,
112 coeffs.transonic_factor,
113 );
114
115 let yaw_factor = calculate_yaw_damping_factor(
117 stability_factor,
118 time_of_flight_s,
119 coeffs.yaw_damping,
120 );
121
122 let velocity_ratio = velocity_mps / muzzle_velocity_mps;
124 let velocity_correction = velocity_ratio.powf(0.3);
125
126 let total_drift = sign * (
128 litz_drift * transonic_correction * yaw_factor * velocity_correction
129 + jump_correction
130 );
131
132 let density_correction = (1.225 / air_density_kg_m3).sqrt();
134
135 total_drift * density_correction
136}
137
138fn calculate_litz_drift(stability: f64, time_s: f64, coefficient: f64) -> f64 {
140 if stability <= 1.0 || time_s <= 0.0 {
141 return 0.0;
142 }
143
144 let base_drift = coefficient * (stability + 1.2) * time_s.powf(1.83);
148
149 base_drift * 0.0254
151}
152
153fn calculate_aerodynamic_jump_correction(
155 mach: f64,
156 spin_rate_rad_s: f64,
157 caliber_m: f64,
158 mass_kg: f64,
159 jump_factor: f64,
160) -> f64 {
161 if mach <= 0.0 {
166 return 0.0;
167 }
168
169 let spin_parameter = spin_rate_rad_s * caliber_m / (2.0 * 343.0 * mach);
170
171 let jump_mrad = jump_factor * spin_parameter * (mass_kg / 0.01).sqrt();
173
174 jump_mrad * 0.001 * 100.0 }
178
179fn calculate_transonic_correction(mach: f64, transonic_factor: f64) -> f64 {
181 if mach < 0.8 {
182 1.0
184 } else if mach < 1.2 {
185 let transonic_ratio = (mach - 0.8) / 0.4;
188 let base_correction = 1.0 + (transonic_factor - 1.0) * (1.0 - (transonic_ratio * PI).cos()) / 2.0;
189 base_correction
190 } else {
191 0.85 + 0.15 * (2.5 - mach).max(0.0) / 1.3
193 }
194}
195
196fn calculate_yaw_damping_factor(stability: f64, time_s: f64, damping_coeff: f64) -> f64 {
198 let damping_rate = damping_coeff * stability.sqrt();
201 let damped = 1.0 - (-damping_rate * time_s).exp();
202
203 damped.max(0.5).min(1.0)
205}
206
207pub fn calculate_advanced_yaw_of_repose(
209 stability_factor: f64,
210 velocity_mps: f64,
211 crosswind_mps: f64,
212 spin_rate_rad_s: f64,
213 air_density_kg_m3: f64,
214 caliber_m: f64,
215) -> f64 {
216 if stability_factor <= 1.0 || velocity_mps <= 0.0 {
217 return 0.0;
218 }
219
220 let wind_yaw = if crosswind_mps != 0.0 && velocity_mps > 0.0 {
222 (crosswind_mps / velocity_mps).atan()
223 } else {
224 0.001 + 0.0005 * (velocity_mps / 800.0).min(2.0)
227 };
228
229 let stability_term = ((stability_factor - 1.0) / stability_factor).sqrt();
231
232 let q = 0.5 * air_density_kg_m3 * velocity_mps.powi(2);
234 let q_factor = (q / 50000.0).min(1.5).max(0.5); let spin_factor = if spin_rate_rad_s > 0.0 {
238 let spin_param = spin_rate_rad_s * caliber_m / (2.0 * velocity_mps);
239 1.0 + 0.2 * spin_param.min(0.5)
240 } else {
241 1.0
242 };
243
244 wind_yaw * stability_term * q_factor * spin_factor
245}
246
247pub fn apply_ml_correction(
249 base_drift: f64,
250 stability: f64,
251 mach: f64,
252 time_s: f64,
253 caliber_inches: f64,
254 mass_grains: f64,
255) -> f64 {
256 let mut correction = 1.0;
267
268 if stability > 2.5 && mach < 1.0 {
270 correction *= 0.92; }
272
273 if time_s > 2.0 && mach < 0.9 {
274 correction *= 1.08; }
276
277 if caliber_inches < 0.264 && mass_grains < 100.0 {
278 correction *= 0.88; }
280
281 base_drift * correction
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287
288 #[test]
289 fn test_advanced_spin_drift() {
290 let drift = calculate_advanced_spin_drift(
292 1.5, 1.2, 600.0, 850.0, 1500.0, 0.00308, 0.0108, 1.225, true, "match", );
303
304 assert!(drift > 0.0);
306 assert!(drift < 0.3); }
308
309 #[test]
310 fn test_transonic_correction() {
311 let subsonic = calculate_transonic_correction(0.7, 0.75);
312 let transonic = calculate_transonic_correction(1.0, 0.75);
313 let supersonic = calculate_transonic_correction(1.5, 0.75);
314
315 assert_eq!(subsonic, 1.0);
316 assert!(transonic > 0.8 && transonic < 1.0);
317 assert!(supersonic > 0.7 && supersonic < 1.0);
318 }
319}