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
80 || stability_factor <= 0.0
81 || muzzle_velocity_mps <= 0.0
82 || air_density_kg_m3 <= 0.0
83 {
84 return 0.0;
85 }
86
87 let coeffs = SpinDriftCoefficients::for_bullet_type(bullet_type);
88
89 let sign = if is_right_twist { 1.0 } else { -1.0 };
91
92 let mach_current = velocity_mps / 343.0;
94 let mach_muzzle = muzzle_velocity_mps / 343.0;
95
96 let litz_drift =
98 calculate_litz_drift(stability_factor, time_of_flight_s, coeffs.litz_coefficient);
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 =
111 calculate_transonic_correction(mach_current, coeffs.transonic_factor);
112
113 let yaw_factor =
115 calculate_yaw_damping_factor(stability_factor, time_of_flight_s, coeffs.yaw_damping);
116
117 let velocity_ratio = velocity_mps / muzzle_velocity_mps;
119 let velocity_correction = velocity_ratio.powf(0.3);
120
121 let total_drift = sign
123 * (litz_drift * transonic_correction * yaw_factor * velocity_correction + jump_correction);
124
125 let density_correction = (1.225 / air_density_kg_m3).sqrt();
127
128 total_drift * density_correction
129}
130
131fn calculate_litz_drift(stability: f64, time_s: f64, coefficient: f64) -> f64 {
133 if stability <= 1.0 || time_s <= 0.0 {
134 return 0.0;
135 }
136
137 let base_drift = coefficient * (stability + 1.2) * time_s.powf(1.83);
141
142 base_drift * 0.0254
144}
145
146fn calculate_aerodynamic_jump_correction(
148 mach: f64,
149 spin_rate_rad_s: f64,
150 caliber_m: f64,
151 mass_kg: f64,
152 jump_factor: f64,
153) -> f64 {
154 if mach <= 0.0 {
159 return 0.0;
160 }
161
162 let spin_parameter = spin_rate_rad_s * caliber_m / (2.0 * 343.0 * mach);
163
164 let jump_mrad = jump_factor * spin_parameter * (mass_kg / 0.01).sqrt();
166
167 jump_mrad * 0.001 * 100.0 }
171
172fn calculate_transonic_correction(mach: f64, transonic_factor: f64) -> f64 {
174 if mach < 0.8 {
175 1.0
177 } else if mach < 1.2 {
178 let transonic_ratio = (mach - 0.8) / 0.4;
181
182 1.0 + (transonic_factor - 1.0) * (1.0 - (transonic_ratio * PI).cos()) / 2.0
183 } else {
184 0.85 + 0.15 * (2.5 - mach).max(0.0) / 1.3
186 }
187}
188
189fn calculate_yaw_damping_factor(stability: f64, time_s: f64, damping_coeff: f64) -> f64 {
191 let damping_rate = damping_coeff * stability.sqrt();
194 let damped = 1.0 - (-damping_rate * time_s).exp();
195
196 damped.max(0.5).min(1.0)
198}
199
200pub fn calculate_advanced_yaw_of_repose(
202 stability_factor: f64,
203 velocity_mps: f64,
204 crosswind_mps: f64,
205 spin_rate_rad_s: f64,
206 air_density_kg_m3: f64,
207 caliber_m: f64,
208) -> f64 {
209 if stability_factor <= 1.0 || velocity_mps <= 0.0 {
210 return 0.0;
211 }
212
213 let wind_yaw = if crosswind_mps != 0.0 && velocity_mps > 0.0 {
215 (crosswind_mps / velocity_mps).atan()
216 } else {
217 0.001 + 0.0005 * (velocity_mps / 800.0).min(2.0)
220 };
221
222 let stability_term = ((stability_factor - 1.0) / stability_factor).sqrt();
224
225 let q = 0.5 * air_density_kg_m3 * velocity_mps.powi(2);
227 let q_factor = (q / 50000.0).min(1.5).max(0.5); let spin_factor = if spin_rate_rad_s > 0.0 {
231 let spin_param = spin_rate_rad_s * caliber_m / (2.0 * velocity_mps);
232 1.0 + 0.2 * spin_param.min(0.5)
233 } else {
234 1.0
235 };
236
237 wind_yaw * stability_term * q_factor * spin_factor
238}
239
240pub fn apply_ml_correction(
242 base_drift: f64,
243 stability: f64,
244 mach: f64,
245 time_s: f64,
246 caliber_inches: f64,
247 mass_grains: f64,
248) -> f64 {
249 let mut correction = 1.0;
260
261 if stability > 2.5 && mach < 1.0 {
263 correction *= 0.92; }
265
266 if time_s > 2.0 && mach < 0.9 {
267 correction *= 1.08; }
269
270 if caliber_inches < 0.264 && mass_grains < 100.0 {
271 correction *= 0.88; }
273
274 base_drift * correction
275}
276
277#[cfg(test)]
278mod tests {
279 use super::*;
280
281 #[test]
282 fn test_advanced_spin_drift() {
283 let drift = calculate_advanced_spin_drift(
285 1.5, 1.2, 600.0, 850.0, 1500.0, 0.00308, 0.0108, 1.225, true, "match", );
296
297 assert!(drift > 0.0);
299 assert!(drift < 0.3); }
301
302 #[test]
303 fn test_transonic_correction() {
304 let subsonic = calculate_transonic_correction(0.7, 0.75);
305 let transonic = calculate_transonic_correction(1.0, 0.75);
306 let supersonic = calculate_transonic_correction(1.5, 0.75);
307
308 assert_eq!(subsonic, 1.0);
309 assert!(transonic > 0.8 && transonic < 1.0);
310 assert!(supersonic > 0.7 && supersonic < 1.0);
311 }
312}