1use crate::constants::{AIR_DENSITY_SEA_LEVEL, SPEED_OF_SOUND_MPS};
2
3#[derive(Debug, Clone, Copy)]
5pub struct AerodynamicJumpComponents {
6 pub vertical_jump_moa: f64, pub horizontal_jump_moa: f64, pub jump_angle_rad: f64, pub magnus_component_moa: f64, pub yaw_component_moa: f64, pub stabilization_factor: f64, }
13
14pub fn calculate_aerodynamic_jump(
19 muzzle_velocity_mps: f64,
20 spin_rate_rad_s: f64,
21 crosswind_mps: f64,
22 caliber_m: f64,
23 mass_kg: f64,
24 barrel_length_m: f64,
25 twist_rate_calibers: f64,
26 is_right_twist: bool,
27 initial_yaw_rad: f64,
28 air_density_kg_m3: f64,
29) -> AerodynamicJumpComponents {
30 if muzzle_velocity_mps <= 0.0 || caliber_m <= 0.0 {
31 return AerodynamicJumpComponents {
32 vertical_jump_moa: 0.0,
33 horizontal_jump_moa: 0.0,
34 jump_angle_rad: 0.0,
35 magnus_component_moa: 0.0,
36 yaw_component_moa: 0.0,
37 stabilization_factor: 0.0,
38 };
39 }
40
41 let mach = muzzle_velocity_mps / SPEED_OF_SOUND_MPS;
43 let magnus_coeff = if mach < 0.8 {
44 0.25
45 } else if mach < 1.2 {
46 0.15 } else {
48 0.20
49 };
50
51 let spin_param = (spin_rate_rad_s * caliber_m / 2.0) / muzzle_velocity_mps;
53
54 let crosswind_yaw = if crosswind_mps != 0.0 {
56 (crosswind_mps / muzzle_velocity_mps).atan()
57 } else {
58 0.0
59 };
60
61 let total_yaw_rad = crosswind_yaw + initial_yaw_rad;
62
63 let area = std::f64::consts::PI * (caliber_m / 2.0).powi(2);
65 let magnus_force = 0.5
66 * air_density_kg_m3
67 * muzzle_velocity_mps.powi(2)
68 * area
69 * magnus_coeff
70 * spin_param
71 * total_yaw_rad.sin();
72
73 let exit_time = 2.0 * barrel_length_m / muzzle_velocity_mps;
75
76 let stabilization_calibers = 20.0 / (twist_rate_calibers / 10.0).sqrt();
78 let stabilization_distance = stabilization_calibers * caliber_m;
79 let stabilization_time = stabilization_distance / muzzle_velocity_mps;
80
81 let effective_time = exit_time + stabilization_time;
83
84 let vertical_sign = if is_right_twist {
86 crosswind_mps.signum()
87 } else {
88 -crosswind_mps.signum()
89 };
90
91 let magnus_accel = magnus_force / mass_kg;
93
94 let lever_factor = (barrel_length_m / caliber_m) * 0.1;
96 let magnus_enhancement = 50.0; let mut vertical_jump_m = magnus_enhancement
100 * lever_factor
101 * vertical_sign
102 * magnus_accel.abs()
103 * effective_time.powi(2);
104
105 if total_yaw_rad != 0.0 {
107 let yaw_contribution = total_yaw_rad.abs() * barrel_length_m * 0.5;
108 vertical_jump_m += vertical_sign * yaw_contribution;
109 }
110
111 let horizontal_jump_m = 0.25 * vertical_jump_m * (2.0 * total_yaw_rad).sin();
113
114 const YARDS_TO_M: f64 = 0.9144;
116 const MOA_PER_RADIAN: f64 = 3437.7467707849; let range_100y = 100.0 * YARDS_TO_M;
119 let vertical_angle_rad = vertical_jump_m / range_100y;
120 let horizontal_angle_rad = horizontal_jump_m / range_100y;
121
122 let vertical_jump_moa = vertical_angle_rad * MOA_PER_RADIAN;
123 let horizontal_jump_moa = horizontal_angle_rad * MOA_PER_RADIAN;
124
125 let total_jump_rad = (vertical_angle_rad.powi(2) + horizontal_angle_rad.powi(2)).sqrt();
127
128 let magnus_component_moa = vertical_jump_moa.abs() * 0.8;
130 let yaw_component_moa = vertical_jump_moa.abs() * 0.2;
131
132 let caliber_in = caliber_m / 0.0254;
134 let sg_approx = 30.0 * mass_kg * 15.432 / (twist_rate_calibers.powi(2) * caliber_in.powi(3));
135 let stabilization_factor = (sg_approx / 1.5).min(1.0);
136
137 AerodynamicJumpComponents {
138 vertical_jump_moa,
139 horizontal_jump_moa,
140 jump_angle_rad: total_jump_rad,
141 magnus_component_moa,
142 yaw_component_moa,
143 stabilization_factor,
144 }
145}
146
147pub fn calculate_sight_correction_for_jump(
149 jump_components: &AerodynamicJumpComponents,
150 zero_range_m: f64,
151 sight_height_m: f64,
152) -> (f64, f64) {
153 let range_factor = 91.44 / zero_range_m; let mut vertical_correction = -jump_components.vertical_jump_moa * range_factor;
158 let horizontal_correction = -jump_components.horizontal_jump_moa * range_factor;
159
160 let sight_factor = 1.0 + (sight_height_m / 0.05);
162 vertical_correction *= sight_factor;
163
164 (vertical_correction, horizontal_correction)
165}
166
167pub fn calculate_crosswind_jump_sensitivity(
169 muzzle_velocity_mps: f64,
170 spin_rate_rad_s: f64,
171 caliber_m: f64,
172 mass_kg: f64,
173 twist_rate_calibers: f64,
174 is_right_twist: bool,
175) -> f64 {
176 const MPH_TO_MPS: f64 = 0.44704;
177 let crosswind_1mph = MPH_TO_MPS;
178
179 let jump = calculate_aerodynamic_jump(
180 muzzle_velocity_mps,
181 spin_rate_rad_s,
182 crosswind_1mph,
183 caliber_m,
184 mass_kg,
185 0.6, twist_rate_calibers,
187 is_right_twist,
188 0.0, AIR_DENSITY_SEA_LEVEL,
190 );
191
192 jump.vertical_jump_moa.abs()
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_aerodynamic_jump_zero_conditions() {
201 let jump = calculate_aerodynamic_jump(
203 800.0, 1000.0, 0.0, 0.00762, 0.01134, 0.6, 32.47, true, 0.0, 1.225, );
214
215 assert_eq!(jump.vertical_jump_moa, 0.0);
216 assert!(jump.horizontal_jump_moa.abs() < 0.001);
217 }
218
219 #[test]
220 fn test_aerodynamic_jump_with_crosswind() {
221 let jump = calculate_aerodynamic_jump(
223 800.0, 17593.0, 4.4704, 0.00782, 0.01134, 0.6096, 32.47, true, 0.0, 1.225, );
234
235 assert!(jump.vertical_jump_moa > 0.0);
237 assert!(jump.stabilization_factor > 0.0);
239 }
240
241 #[test]
242 fn test_opposite_twist_direction() {
243 let crosswind = 4.4704; let jump_right = calculate_aerodynamic_jump(
247 800.0, 17593.0, crosswind, 0.00782, 0.01134, 0.6096, 32.47, true, 0.0, 1.225,
248 );
249
250 let jump_left = calculate_aerodynamic_jump(
252 800.0, 17593.0, crosswind, 0.00782, 0.01134, 0.6096, 32.47, false, 0.0, 1.225,
253 );
254
255 assert!((jump_right.vertical_jump_moa + jump_left.vertical_jump_moa).abs() < 0.001);
257 }
258
259 #[test]
260 fn test_sight_correction() {
261 let jump = AerodynamicJumpComponents {
262 vertical_jump_moa: 0.5,
263 horizontal_jump_moa: 0.1,
264 jump_angle_rad: 0.0001,
265 magnus_component_moa: 0.4,
266 yaw_component_moa: 0.1,
267 stabilization_factor: 0.9,
268 };
269
270 let (vert, horiz) = calculate_sight_correction_for_jump(
271 &jump, 274.32, 0.05, );
274
275 assert!(vert < 0.0);
277 assert!(horiz < 0.0);
278 }
279
280 #[test]
281 fn test_crosswind_sensitivity() {
282 let sensitivity = calculate_crosswind_jump_sensitivity(
283 800.0, 17593.0, 0.00782, 0.01134, 32.47, true, );
290
291 assert!(sensitivity > 0.0);
293 assert!(sensitivity < 0.5);
294 }
295}