File Coverage

lib/Physics/Ballistics/External.pm
Criterion Covered Total %
statement 183 405 45.1
branch 94 298 31.5
condition 16 26 61.5
subroutine 17 26 65.3
pod 5 22 22.7
total 315 777 40.5


line stmt bran cond sub pod time code
1             package Physics::Ballistics::External;
2 1     1   118861 use strict;
  1         3  
  1         29  
3 1     1   6 use warnings;
  1         3  
  1         82  
4             require Exporter;
5             our @ISA = qw(Exporter);
6             our @EXPORT = qw(ebc flight_simulator g1_drag muzzle_energy muzzle_velocity_from_energy);
7             our $VERSION = '1.03';
8              
9 1     1   537 use Math::Trig qw(tan asin acos atan pi);
  1         12582  
  1         105  
10 1     1   422 use Physics::Ballistics;
  1         3  
  1         3120  
11              
12             =head1 NAME
13              
14             Physics::Ballistics::External -- External ballistics formulae.
15              
16             =head1 ABSTRACT
17              
18             External ballistics is the study of projectiles in flight, from the time they
19             leave the barrel (or hand, or trebuchet, or whatever), to the moment before
20             they strike their target. This module implements mathematical formulae and
21             functions useful in the analysis and prediction of external ballistic behavior.
22              
23             =head1 ANNOTATIONS OF SOURCES
24              
25             Regarding their source, these functions fall into three categories: Some are
26             simple encodings of basic physics (like energy = 1/2 * mass * velocity**2),
27             and these will not be cited. Others are from published works, such as books
28             or trade journals, and these will be cited when possible. A few are products
29             of my own efforts, and will be thus annotated.
30              
31             =head1 OOP INTERFACE
32              
33             A more integrated, object-oriented interface for these functions is under
34             development.
35              
36             =head1 FUNCTIONS
37              
38             =head2 ebc (mass_grains, diameter_inches, [shape,] [form_factor])
39              
40             Attempts to predict the G1 ballistic coefficient of a projectile, based on its
41             mass, width, and shape. Useful for predicting the ballistic behavior of
42             hypothetical or new, untested projectiles. When compared against the known G1
43             BC's of well-understood projectiles, its predictions usually come within 5% of
44             actual.
45              
46             The "shape" parameter indicates that the hypothetical projectile is closest in
47             its shape, composition, and quality of manufacture to the named entity. Some
48             shapes are very general ("hollowpoint", "fmj") while others are very specific
49             to product families manufactured by particular companies ("scenar", "amax").
50              
51             The "shape" parameter maps to a numerical form base (see table below), which
52             gets tweaked a little by other factors to derive a form factor. The form
53             factor has an inverse impact on ballistic coefficient (higher form factor
54             means a lower coefficient). In lieu of depending on the "shape" parameter, a
55             form factor may be specified as a parameter (mostly useful for debugging or
56             deriving new entries in the shape table).
57              
58             When no shape or form factor are provided, the "default" shape is used, which
59             has a form base chosen to minimize the error produced by this function when
60             its output is compared to entries in http://www.frfrogspad.com/g1bclist.xls
61             not already covered in the shape table.
62              
63             A hashfile including all of the information from g1bclist.xls with "shape"
64             entries matching the names from the shape table below may be found here:
65             http://ciar.org/ttk/mbt/guns/table.bc.hash
66              
67             This function is the original work of the module's author.
68              
69             =over 4
70              
71             parameter: (float) mass of the projectile (in grains)
72              
73             parameter: (float) diameter of the projectile (in inches)
74              
75             parameter: (str) OPTIONAL: shape/composition of the projectile (see table below, default is "default")
76              
77             parameter: (float) OPTIONAL: custom form-factor of the projectile, unnecessary if "shape" is provided.
78              
79             returns: a list, containing the following values:
80              
81             * The estimated G1 ballistic coefficient,
82             * The form factor (suitable for use as as form_factor parameter)
83             * The "very short factor" (1.0 for most well-proportioned bullets)
84             * The shape parameter used
85              
86             =back
87              
88             The shape table currently contains the following entities, with the given form bases:
89              
90             '7n14' => 111, # Very deep ogival shape and boat-tail with tight tolerances, used in military 7.62x54R specifically for sniping.
91             'scenar' => 124, # Scenar, by Lapua
92             'scenar_s' => 124, # Scenar Silver, by Lapua, appears ballistically indistinguishable from Scenar
93             '7n1' => 125, # Very deep ogival shape and boat-tail, used in military 7.62x54R. qv http://7.62x54r.net/MosinID/MosinAmmo007.htm
94             '7n6' => 125, # Very deep ogival shape and boat-tail, used in military 5.45x39mm qv http://7.62x54r.net/MosinID/MosinAmmo007.htm
95             '7n6m' => 125, # Synonym for 7N6
96             'amax' => 127, # A-Max, by Hornady
97             'boat_tail_og' => 128, # Catch-all for many boat-tailed projectiles with long, pointed ogival nose shapes
98             'hollowpoint_ct' => 131, # CT variation of hollowpoint "match" projectiles, by Nosler
99             'bst' => 136, # A type of flat-bottomed ogival, by Nosler
100             'spire_point' => 138, # A type of flat-bottomed ogival, by Speer
101             'boat_tail_nosler' => 138, # Nosler's line of boat-tails perform more poorly than others for some reason
102             'boat_tail_ct' => 139, # Nosler's CT variation of boat-tail
103             'spitzer' => 139, # Another flat-bottomed ogival, by Speer. Good fit to many military bullets.
104             'hollowpoint_match' => 140, # Catch-all for many hollowpoint "match" projectiles
105             'accubond' => 142, # An expansive line by Nosler offering controlled terminal expansion
106             'interbond' => 142, # A line of polymer-tipped bullets by Hornady optimized for terminal cohesion
107             'vmax' => 145, # A line of polymer-tipped bullets by Hornady optimized for terminal expansion / fragmentation
108             'gold_match' => 147, # A type of wadcutter, by Speer
109             'grand_slam' => 159, # Another wadcutter, by Speer, with less streamlined nose shape for higher overall mass
110             'hollowpoint' => 176, # Catch-all for many large-game hollowpoint projectiles
111             'default' => 179, # Catch-all for unknown/unspecified shapes; derived via best-fit to ballistic table
112             'fmj' => 187, # Catch-all for military full metal jacket with boat-tail and short nose
113             'mag_tip' => 190, # Another wadcutter, by Speer
114             'afss' => 202,
115             'tsx_fb' => 205,
116             'plinker' => 205, # Typical of short, underweight, round-nosed, non-streamlined projectiles,
117             'semispitzer' => 224, # A foreshortened, flat-bottomed projectile, by Speer
118             'round_nose' => 228, # Catch-all for many flat-bottomed, hemispherical-nosed projectiles
119             'varminter' => 232, # Catch-all for many light hollowpoints with very large expanding cavities, for varminting
120             'fmj_2' => 268 # Woodleigh's FMJ projectiles, which are shape-optimized for travel in big game meat and bone, instead of air.
121              
122             This hash table is exported as %Physics::Ballistics::External::Bullet_Form_Factors_H,
123             so that users and modules may easily modify/add its content without resorting
124             to editing sources.
125              
126             =cut
127              
128             our %Bullet_Form_Factors_H = (
129             '7n14' => 111, # Very, very deep ogival shape and boat-tail with tight tolerances, used in military 7.62x54R specifically for sniping.
130             '7n10' => 118, # 7n6 with enhanced penetration and different weight distribution
131             'scenar' => 124, # Scenar, by Lapua
132             'scenar_s' => 124, # Scenar Silver, by Lapua, appears ballistically indistinguishable from Scenar
133             '7n1' => 125, # Russian boat-tail military bullet with very, very deep ogival shape
134             '7n6' => 125, # Russian boat-tail military bullet with very, very deep ogival shape
135             '7n6m' => 125, # Synonym for 7N6
136             'amax' => 127, # A-Max, by Hornady
137             'boat_tail_og' => 128, # Catch-all for many boat-tailed projectiles with long, pointed ogival nose shapes
138             'hollowpoint_ct' => 131, # CT variation of hollowpoint "match" projectiles, by Nosler
139             'mk318' => 133, # USMC 5.56x45mm Mk318 Mod 0 SOST open-tip/boattail 62gr half-lead/half-copper
140             'bst' => 135, # A type of flat-bottomed ogival, by Nosler
141             'btsp' => 137, # Boat-tailed spire-point from Hornady
142             'spire_point' => 138, # A type of flat-bottomed ogival, by Speer
143             'boat_tail_nosler' => 138, # Nosler's line of boat-tails perform more poorly than others for some reason
144             'boat_tail_ct' => 139, # Nosler's CT variation of boat-tail
145             'spitzer' => 139, # Another flat-bottomed ogival, by Speer
146             'hollowpoint_match' => 140, # Catch-all for many hollowpoint "match" projectiles
147             'accubond' => 142, # An expansive line by Nosler
148             'interbond' => 142, # An expansive line by Hornady
149             'tts' => 143, # Tipped Triple Shock, another line of all-copper bullets from Barnes
150             'vmax' => 145, # A line of polymer-tipped bullets by Hornady optimized for terminal expansion / fragmentation
151             'gold_match' => 147, # A type of wadcutter, by Speer
152             'tsx' => 151, # Another line of all-copper hunting bullets from Barnes
153             'partition' => 152, # A crappy hunting bullet by Nosler
154             'spbt' => 152.5, # Soft-Point Boat-Tail
155             'grand_slam' => 159, # Another wadcutter, by Speer
156             'barnes_xxx' => 161, # Flat-bottomed all-copper hunting round, by Barnes
157             'hollowpoint' => 176, # Catch-all for many large-game hollowpoint projectiles
158             'sp' => 177, # Catch-all for many softpoint hunting projectiles without sharp points
159             'default' => 179, # Catch-all for unknown/unspecified shapes; derived via best-fit to ballistic table
160             'fmj' => 187, # Catch-all for military full metal jacket with boat-tail and short nose
161             'mag_tip' => 190, # Another wadcutter, by Speer
162             'afss' => 202,
163             'tsx_fb' => 205,
164             'plinker' => 205, # Typical of short, underweight, round-nosed, non-streamlined projectiles,
165             'rws_ks' => 210,
166             'semispitzer' => 224, # A foreshortened, flat-bottomed projectile, by Speer
167             'round_nose' => 228, # Catch-all for many flat-bottomed, hemispherical-nosed projectiles
168             'varminter' => 232, # Catch-all for many light hollowpoints with very large expanding cavities, for varminting
169             'fmj_2' => 268, # Woodleigh's FMJ projectiles, which are shape-optimized for travel in big game meat and bone, instead of air.
170             'flat_nose' => 299 # Flat-nosed bullets for tubular magazines
171             );
172              
173             # TODO: Contact Geoffrey Kolbe (Inventor at Border Barrels Limited) and ask for permission to publish his splendid implementations:
174             # * Bullet drag calculator - http://www.border-barrels.com/cgi-bin/drag_working.cgi
175             # * Barrel weight calculator - http://www.border-barrels.com/cgi-bin/swamped_barrel_weight.cgi
176             #
177             # His drag calculator appears far superior to my own ebc function.
178              
179             sub ebc { # estimate ballistic coefficient (G1) from mass, diameter, and shape or form factor
180             # Form factor is based on shape (nose, tail, distance between them), and has inverse impact on BC (higher form factor == lower BC).
181             # Form factor is subject to some modification:
182             # * adjusted for diameter (normalizing on .308 inch diameters)
183             # * adjusted upward at very small L/D (very short shapes)
184 3     3 1 1701 my ($mass_gr, $diam_in, $shape, $ff) = @_;
185 3 50       14 die("bc, form-factor, very-small-factor, shape = ebc(mass_gr, diam_in[, shape[, form-factor]])") unless (defined($diam_in));
186 3         7 my $vsf = 1; # "very small" factor -- a penalty incurred when length/diameter is too small.
187 3 50       9 $shape = "default" unless (defined($shape));
188 3 50       10 if (!defined($ff)) {
189             # Form factors of various commercial shapes. Higher ff implies more drag.
190 3         6 my $vsthreshold = 4480; # determined empirically, looking at 45gr 0.224" spitzer, which looks to be just beyond the threshold, and 40gr 0.224" spire_point, which is significantly beyond it.
191 3         27 my $LD = $mass_gr / $diam_in**3;
192 3 50       12 $vsf = ($vsthreshold / $LD) if ($LD < $vsthreshold);
193 3         7 my $ff_base = undef;
194 3 50       15 if (defined($Physics::Ballistics::External::Bullet_Form_Factors_H{$shape})) {
195 3         8 $ff_base = $Physics::Ballistics::External::Bullet_Form_Factors_H{$shape};
196             } else {
197 0         0 $ff_base = $Physics::Ballistics::External::Bullet_Form_Factors_H{'default'};
198 0 0       0 $ff_base = 179 unless (defined($ff_base));
199 0         0 print STDERR "ebc: warning: unknown shape '$shape' and no form factor explicitly provided; assuming default (ff=$ff_base)\n";
200             }
201 3         19 my $diameter_factor = ($diam_in / .308)**0.541;
202 3         8 $ff = $vsf * $ff_base * $diameter_factor;
203             } # END of if !$ff
204 3         12 my $bc = ($mass_gr**1.25 / (10*$diam_in)**2) / $ff;
205 3         28 return {bc => $bc, ff => $ff, vsf => $vsf, shape => $shape};
206             }
207              
208             ############## BEGIN port of GNU-Ballistics gebc-1.07 lib/ballistics/ballistics.cpp
209             # TODO: Incorporate wobble estimation function (used by improved penetration estimator).
210             # TODO: Port bugfixes and enhancements to Inline::C. The pure-perl performance is horrible.
211              
212             my $GRAVITY = -32.194;
213             my $BCOMP_MAXRANGE = 2000;
214              
215             # Specialty angular conversion functions, straightforward PP port of GNU-Ballistics gebc-1.07 lib/ballistics/ballistics.cpp
216              
217             sub DegtoMOA {
218 0     0 0 0 my ($deg) = @_;
219 0         0 return $deg*60;
220             }
221             sub DegtoRad {
222 33     33 0 120 my ($deg) = @_;
223 33         235 return $deg*pi/180;
224             }
225             sub MOAtoDeg {
226 0     0 0 0 my ($moa) = @_;
227 0         0 return $moa/60;
228             }
229             sub MOAtoRad {
230 30     30 0 102 my ($moa) = @_;
231 30         178 return $moa/60*pi/180;
232             }
233             sub RadtoDeg {
234 1     1 0 2 my ($rad) = @_;
235 1         5 return $rad*180/pi;
236             }
237             sub RadtoMOA {
238 601     601 0 4262 my ($rad) = @_;
239 601         1397 return $rad*60*180/pi;
240             }
241              
242             # Functions for correcting for atmosphere, straightforward PP port of GNU-Ballistics gebc-1.07 lib/ballistics/ballistics.cpp
243              
244             sub calcFR {
245 0     0 0 0 my ($Temperature, $Pressure, $RelativeHumidity) = @_;
246 0         0 my $VPw = (4e-6) * $Temperature**3 - 0.0004 * $Temperature**2 + 0.0234 * $Temperature - 0.2517;
247 0         0 my $FRH = 0.995 * $Pressure / ($Pressure - 0.3783 * $RelativeHumidity * $VPw);
248 0         0 return $FRH;
249             }
250              
251             sub calcFP {
252 0     0 0 0 my ($Pressure) = @_;
253 0         0 my $Pstd = 29.53; # inches-hg
254 0         0 my $FP = ($Pressure - $Pstd) / $Pstd;
255 0         0 return $FP;
256             }
257              
258             sub calcFT {
259 0     0 0 0 my ($Temperature, $Altitude) = @_;
260 0         0 my $Tstd = -0.0036 * $Altitude + 59;
261 0         0 my $FT = ($Temperature - $Tstd) / (459.6 + $Tstd);
262 0         0 return $FT;
263             }
264              
265             sub calcFA {
266 0     0 0 0 my ($Altitude) = @_;
267 0         0 my $fa = (-4e-15) * $Altitude**3 + (4e-10) * $Altitude**2 - (3e-5) * $Altitude + 1;
268 0         0 return 1/$fa;
269             }
270              
271             sub AtmCorrect {
272 0     0 0 0 my ($DragCoefficient, $Altitude, $Barometer, $Temperature, $RelativeHumidity) = @_;
273 0         0 my $FA = calcFA($Altitude);
274 0         0 my $FT = calcFT($Temperature, $Altitude);
275 0         0 my $FR = calcFR($Temperature, $Barometer, $RelativeHumidity);
276 0         0 my $FP = calcFP($Barometer);
277              
278             # Calculate the atmospheric correction factor
279 0         0 my $CD = $FA * (1 + $FT - $FP) * $FR;
280 0         0 return $DragCoefficient * $CD;
281             }
282              
283             # Function for correcting for ballistic drag, straightforward PP port of GNU-Ballistics gebc-1.07 lib/ballistics/ballistics.cpp
284             sub velocity_loss {
285 24717     24717 0 47572 my ($DragFunction, $DragCoefficient, $Velocity) = @_;
286 24717         36108 my $vp = $Velocity;
287 24717         33708 my $val = -1;
288 24717         31942 my $A = -1;
289 24717         32891 my $M = -1;
290 24717 50       45543 if ($DragFunction eq 'G1') {
    0          
    0          
    0          
    0          
    0          
291 24717 50       118115 if ($vp > 4230) { $A = 1.477404177730177e-04; $M = 1.9565; }
  0 50       0  
  0 50       0  
    50          
    50          
    50          
    50          
    50          
    50          
    100          
    100          
    100          
    100          
    50          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
292 0         0 elsif ($vp > 3680) { $A = 1.920339268755614e-04; $M = 1.925 ; }
  0         0  
293 0         0 elsif ($vp > 3450) { $A = 2.894751026819746e-04; $M = 1.875 ; }
  0         0  
294 0         0 elsif ($vp > 3295) { $A = 4.349905111115636e-04; $M = 1.825 ; }
  0         0  
295 0         0 elsif ($vp > 3130) { $A = 6.520421871892662e-04; $M = 1.775 ; }
  0         0  
296 0         0 elsif ($vp > 2960) { $A = 9.748073694078696e-04; $M = 1.725 ; }
  0         0  
297 0         0 elsif ($vp > 2830) { $A = 1.453721560187286e-03; $M = 1.675 ; }
  0         0  
298 0         0 elsif ($vp > 2680) { $A = 2.162887202930376e-03; $M = 1.625 ; }
  0         0  
299 0         0 elsif ($vp > 2460) { $A = 3.209559783129881e-03; $M = 1.575 ; }
  0         0  
300 6704         10115 elsif ($vp > 2225) { $A = 3.904368218691249e-03; $M = 1.55 ; }
  6704         9715  
301 8358         11924 elsif ($vp > 2015) { $A = 3.222942271262336e-03; $M = 1.575 ; }
  8358         11547  
302 5040         7707 elsif ($vp > 1890) { $A = 2.203329542297809e-03; $M = 1.625 ; }
  5040         6131  
303 3289         4805 elsif ($vp > 1810) { $A = 1.511001028891904e-03; $M = 1.675 ; }
  3289         4597  
304 1326         1914 elsif ($vp > 1730) { $A = 8.609957592468259e-04; $M = 1.75 ; }
  1326         1869  
305 0         0 elsif ($vp > 1595) { $A = 4.086146797305117e-04; $M = 1.85 ; }
  0         0  
306 0         0 elsif ($vp > 1520) { $A = 1.954473210037398e-04; $M = 1.95 ; }
  0         0  
307 0         0 elsif ($vp > 1420) { $A = 5.431896266462351e-05; $M = 2.125 ; }
  0         0  
308 0         0 elsif ($vp > 1360) { $A = 8.847742581674416e-06; $M = 2.375 ; }
  0         0  
309 0         0 elsif ($vp > 1315) { $A = 1.456922328720298e-06; $M = 2.625 ; }
  0         0  
310 0         0 elsif ($vp > 1280) { $A = 2.419485191895565e-07; $M = 2.875 ; }
  0         0  
311 0         0 elsif ($vp > 1220) { $A = 1.657956321067612e-08; $M = 3.25 ; }
  0         0  
312 0         0 elsif ($vp > 1185) { $A = 4.745469537157371e-10; $M = 3.75 ; }
  0         0  
313 0         0 elsif ($vp > 1150) { $A = 1.379746590025088e-11; $M = 4.25 ; }
  0         0  
314 0         0 elsif ($vp > 1100) { $A = 4.070157961147882e-13; $M = 4.75 ; }
  0         0  
315 0         0 elsif ($vp > 1060) { $A = 2.938236954847331e-14; $M = 5.125 ; }
  0         0  
316 0         0 elsif ($vp > 1025) { $A = 1.228597370774746e-14; $M = 5.25 ; }
  0         0  
317 0         0 elsif ($vp > 980) { $A = 2.916938264100495e-14; $M = 5.125 ; }
  0         0  
318 0         0 elsif ($vp > 945) { $A = 3.855099424807451e-13; $M = 4.75 ; }
  0         0  
319 0         0 elsif ($vp > 905) { $A = 1.185097045689854e-11; $M = 4.25 ; }
  0         0  
320 0         0 elsif ($vp > 860) { $A = 3.566129470974951e-10; $M = 3.75 ; }
  0         0  
321 0         0 elsif ($vp > 810) { $A = 1.045513263966272e-08; $M = 3.25 ; }
  0         0  
322 0         0 elsif ($vp > 780) { $A = 1.291159200846216e-07; $M = 2.875 ; }
  0         0  
323 0         0 elsif ($vp > 750) { $A = 6.824429329105383e-07; $M = 2.625 ; }
  0         0  
324 0         0 elsif ($vp > 700) { $A = 3.569169672385163e-06; $M = 2.375 ; }
  0         0  
325 0         0 elsif ($vp > 640) { $A = 1.839015095899579e-05; $M = 2.125 ; }
  0         0  
326 0         0 elsif ($vp > 600) { $A = 5.71117468873424e-05 ; $M = 1.950 ; }
  0         0  
327 0         0 elsif ($vp > 550) { $A = 9.226557091973427e-05; $M = 1.875 ; }
  0         0  
328 0         0 elsif ($vp > 250) { $A = 9.337991957131389e-05; $M = 1.875 ; }
  0         0  
329 0         0 elsif ($vp > 100) { $A = 7.225247327590413e-05; $M = 1.925 ; }
  0         0  
330 0         0 elsif ($vp > 65) { $A = 5.792684957074546e-05; $M = 1.975 ; }
  0         0  
331 0         0 elsif ($vp > 0) { $A = 5.206214107320588e-05; $M = 2.000 ; }
  0         0  
332             }
333            
334             elsif ($DragFunction eq 'G2') {
335 0 0       0 if ($vp > 1674 ) { $A = .0079470052136733 ; $M = 1.36999902851493; }
  0 0       0  
  0 0       0  
    0          
    0          
    0          
    0          
336 0         0 elsif ($vp > 1172 ) { $A = 1.00419763721974e-03; $M = 1.65392237010294; }
  0         0  
337 0         0 elsif ($vp > 1060 ) { $A = 7.15571228255369e-23; $M = 7.91913562392361; }
  0         0  
338 0         0 elsif ($vp > 949 ) { $A = 1.39589807205091e-10; $M = 3.81439537623717; }
  0         0  
339 0         0 elsif ($vp > 670 ) { $A = 2.34364342818625e-04; $M = 1.71869536324748; }
  0         0  
340 0         0 elsif ($vp > 335 ) { $A = 1.77962438921838e-04; $M = 1.76877550388679; }
  0         0  
341 0         0 elsif ($vp > 0 ) { $A = 5.18033561289704e-05; $M = 1.98160270524632; }
  0         0  
342             }
343            
344             elsif ($DragFunction eq 'G5') {
345 0 0       0 if ($vp > 1730 ){ $A = 7.24854775171929e-03; $M = 1.41538574492812; }
  0 0       0  
  0 0       0  
    0          
    0          
    0          
    0          
346 0         0 elsif ($vp > 1228 ){ $A = 3.50563361516117e-05; $M = 2.13077307854948; }
  0         0  
347 0         0 elsif ($vp > 1116 ){ $A = 1.84029481181151e-13; $M = 4.81927320350395; }
  0         0  
348 0         0 elsif ($vp > 1004 ){ $A = 1.34713064017409e-22; $M = 7.8100555281422 ; }
  0         0  
349 0         0 elsif ($vp > 837 ){ $A = 1.03965974081168e-07; $M = 2.84204791809926; }
  0         0  
350 0         0 elsif ($vp > 335 ){ $A = 1.09301593869823e-04; $M = 1.81096361579504; }
  0         0  
351 0         0 elsif ($vp > 0 ){ $A = 3.51963178524273e-05; $M = 2.00477856801111; }
  0         0  
352             }
353            
354             elsif ($DragFunction eq 'G6') {
355 0 0       0 if ($vp > 3236 ) { $A = 0.0455384883480781 ; $M = 1.15997674041274; }
  0 0       0  
  0 0       0  
    0          
    0          
    0          
    0          
356 0         0 elsif ($vp > 2065 ) { $A = 7.167261849653769e-02; $M = 1.10704436538885; }
  0         0  
357 0         0 elsif ($vp > 1311 ) { $A = 1.66676386084348e-03 ; $M = 1.60085100195952; }
  0         0  
358 0         0 elsif ($vp > 1144 ) { $A = 1.01482730119215e-07 ; $M = 2.9569674731838 ; }
  0         0  
359 0         0 elsif ($vp > 1004 ) { $A = 4.31542773103552e-18 ; $M = 6.34106317069757; }
  0         0  
360 0         0 elsif ($vp > 670 ) { $A = 2.04835650496866e-05 ; $M = 2.11688446325998; }
  0         0  
361 0         0 elsif ($vp > 0 ) { $A = 7.50912466084823e-05 ; $M = 1.92031057847052; }
  0         0  
362             }
363            
364             elsif ($DragFunction eq 'G7') {
365 0 0       0 if ($vp > 4200 ) { $A = 1.29081656775919e-09; $M = 3.24121295355962; }
  0 0       0  
  0 0       0  
    0          
    0          
    0          
    0          
    0          
    0          
366 0         0 elsif ($vp > 3000 ) { $A = 0.0171422231434847 ; $M = 1.27907168025204; }
  0         0  
367 0         0 elsif ($vp > 1470 ) { $A = 2.33355948302505e-03; $M = 1.52693913274526; }
  0         0  
368 0         0 elsif ($vp > 1260 ) { $A = 7.97592111627665e-04; $M = 1.67688974440324; }
  0         0  
369 0         0 elsif ($vp > 1110 ) { $A = 5.71086414289273e-12; $M = 4.3212826264889 ; }
  0         0  
370 0         0 elsif ($vp > 960 ) { $A = 3.02865108244904e-17; $M = 5.99074203776707; }
  0         0  
371 0         0 elsif ($vp > 670 ) { $A = 7.52285155782535e-06; $M = 2.1738019851075 ; }
  0         0  
372 0         0 elsif ($vp > 540 ) { $A = 1.31766281225189e-05; $M = 2.08774690257991; }
  0         0  
373 0         0 elsif ($vp > 0 ) { $A = 1.34504843776525e-05; $M = 2.08702306738884; }
  0         0  
374             }
375              
376             elsif ($DragFunction eq 'G8') {
377 0 0       0 if ($vp > 3571 ) { $A = .0112263766252305 ; $M = 1.33207346655961; }
  0 0       0  
  0 0       0  
    0          
    0          
    0          
378 0         0 elsif ($vp > 1841 ) { $A = .0167252613732636 ; $M = 1.28662041261785; }
  0         0  
379 0         0 elsif ($vp > 1120 ) { $A = 2.20172456619625e-03; $M = 1.55636358091189; }
  0         0  
380 0         0 elsif ($vp > 1088 ) { $A = 2.0538037167098e-16 ; $M = 5.80410776994789; }
  0         0  
381 0         0 elsif ($vp > 976 ) { $A = 5.92182174254121e-12; $M = 4.29275576134191; }
  0         0  
382 0         0 elsif ($vp > 0 ) { $A = 4.3917343795117e-05 ; $M = 1.99978116283334; }
  0         0  
383             }
384              
385 24717 50 33     138524 if ($A != -1 && $M != -1 && $vp > 0 && $vp < 10000 ) {
      33        
      33        
386 24717         51395 $val = $A * $vp**$M / $DragCoefficient;
387 24717         59950 return $val;
388             }
389 0         0 return -1;
390             }
391              
392             sub Windage {
393 300     300 0 573 my ($WindSpeed, $Vi, $xx, $t) = @_;
394 300         448 my $Vw = $WindSpeed * 17.60; # Convert to inches per second.
395 300         723 return $Vw * ($t - $xx / $Vi);
396             }
397              
398             # Headwind is positive at WindAngle=0
399             sub HeadWind {
400 0     0 0 0 my ($WindSpeed, $WindAngle) = @_;
401 0         0 my $Wangle = DegtoRad($WindAngle);
402 0         0 return cos($Wangle) * $WindSpeed;
403             }
404              
405             # Positive is from Shooter's Right to Left (Wind from 90 degree)
406             sub CrossWind {
407 0     0 0 0 my ($WindSpeed, $WindAngle) = @_;
408 0         0 my $Wangle = DegtoRad($WindAngle);
409 0         0 return sin($Wangle) * $WindSpeed;
410             }
411              
412             # Equivalent to, but slightly faster than, calling HeadWind and CrossWind separately.
413             # Perl suffers from a couple thousand clock cycles of overhead per function call (about
414             # 1 microsecond of wallclock time on typical 2010's hardware), so consolidating similar
415             # functions is a performance win.
416             sub HeadAndCrossWind {
417 1     1 0 3 my ($WindSpeed, $WindAngle) = @_;
418 1         2 my $Wangle = DegtoRad($WindAngle);
419 1         12 return (cos($Wangle) * $WindSpeed, sin($Wangle) * $WindSpeed);
420             }
421              
422             # Straightforward PP port from GNU-Ballistics gebc-1.07 lib/ballistics/ballistics.cpp
423             sub ZeroAngle {
424 1     1 0 4 my ($DragFunction, $DragCoefficient, $Vi, $SightHeight, $ZeroRange, $yIntercept) = @_;
425              
426             # Numerical Integration variables
427 1         2 my $t = 0;
428 1         3 my $dt = 1/$Vi; # The solution accuracy generally doesn't suffer if its within a foot for each second of time.
429 1         4 my $y = -1 * $SightHeight / 12;
430 1         2 my $x = 0;
431 1         3 my $da; # The change in the bore angle used to iterate in on the correct zero angle.
432              
433             # State variables for each integration loop.
434             # velocity:
435 1         2 my $v = 0;
436 1         3 my $vx = 0;
437 1         2 my $vy = 0;
438             # Last frame's velocity, used for computing average velocity:
439 1         3 my $vx1 = 0;
440 1         2 my $vy1 = 0;
441             # acceleration:
442 1         2 my $dv = 0;
443 1         3 my $dvx = 0;
444 1         2 my $dvy = 0;
445             # Gravitational acceleration:
446 1         2 my $Gx = 0;
447 1         3 my $Gy = 0;
448              
449 1         3 my $angle = 0; # The actual angle of the bore.
450              
451 1         2 my $quit = 0; # We know it's time to quit our successive approximation loop when this is 1.
452              
453             # Start with a very coarse angular change, to quickly solve even large launch angle problems.
454 1         4 $da = DegtoRad(14);
455              
456             # The general idea here is to start at 0 degrees elevation, and increase the elevation by 14 degrees
457             # until we are above the correct elevation. Then reduce the angular change by half, and begin reducing
458             # the angle. Once we are again below the correct angle, reduce the angular change by half again, and go
459             # back up. This allows for a fast successive approximation of the correct elevation, usually within less
460             # than 20 iterations.
461 1         10 for ($angle = 0; $quit == 0; $angle = $angle + $da) {
462 30         126 $vy = $Vi * sin($angle);
463 30         117 $vx = $Vi * cos($angle);
464 30         68 $Gx = $GRAVITY * sin($angle);
465 30         76 $Gy = $GRAVITY * cos($angle);
466              
467 30         121 for (($t, $x, $y) = (0, 0, -1 * $SightHeight/12); $x <= $ZeroRange*3; $t += $dt) {
468 22912         32880 $vy1 = $vy;
469 22912         29503 $vx1 = $vx;
470 22912         51556 $v = ($vx**2 + $vy**2)**0.5;
471 22912         33397 $dt = 1/$v;
472            
473 22912         42813 $dv = velocity_loss ($DragFunction, $DragCoefficient, $v);
474 22912         42487 $dvy = -1 * $dv * $vy / $v * $dt;
475 22912         33448 $dvx = -1 * $dv * $vx / $v * $dt;
476              
477 22912         30570 $vx = $vx + $dvx;
478 22912         29121 $vy = $vy + $dvy;
479 22912         31800 $vy = $vy + $dt * $Gy;
480 22912         36838 $vx = $vx + $dt * $Gx;
481            
482 22912         35708 $x += $dt * ($vx + $vx1) / 2;
483 22912         33201 $y += $dt * ($vy + $vy1) / 2;
484             # Break early to save CPU time if we won't find a solution.
485 22912 100 100     58162 last if ($vy < 0 && $y < $yIntercept);
486 22898 50       65681 last if ($vy>3 * $vx);
487             }
488            
489 30 100 100     155 $da = -1 * $da / 2 if ($y > $yIntercept && $da > 0);
490 30 100 100     159 $da = -1 * $da / 2 if ($y < $yIntercept && $da < 0);
491              
492 30 100       200 $quit = 1 if (abs($da) < MOAtoRad(0.01)); # If our accuracy is sufficient, we can stop approximating.
493 30 50       132 $quit = 1 if ($angle > DegtoRad(45)); # If we exceed the 45 degree launch $angle, then the projectile just won't get there, so we stop trying.
494             }
495 1         6 return RadtoDeg($angle); # Convert to degrees for return value.
496             }
497              
498             =head2 flight_simulator (drag_function, ballistic_coefficient, muzzle_velocity_fps, sight_height_inches, shot_angle_deg, [bore_to_sight_angle_deg,] zero_range_yards, wind_speed_fps, wind_angle_deg, [max_range_yards])
499              
500             Attempts to predict the flight characteristics of a projectile in flight, providing a data point for every yard of progress it makes downrange.
501              
502             This is a pure-perl port of the "SolveAll" function from GNU-Ballistics gebc-1.07 lib/ballistics/ballistics.cpp, and as slow as one might expect from a pure-perl port of an arithmetic-intensive algorithm.
503              
504             On my system it takes about an eighth of a second to simulate a 1200-yard flight. This may be supplemented at some point with an Inline::C equivalent.
505              
506             Note that most manufacturers report G1 ballistic coefficients. Using the wrong drag function for a given ballistic coefficient will produce ludicrously incorrect results.
507              
508             To ascertain the correct bore elevation to hit a target at a specific distance, change the shot_angle_deg parameter on successive calls to flight_simulator(), and converge on drop_inches == 0.0 at the given range via binary search. I should get around to providing a function for that at some point (GNU-Ballistics has such a function, I just didn't port it).
509              
510             =over 4
511              
512             parameter: (str) drag_function is exactly one of: 'G1', 'G2', 'G5', 'G6', 'G7', 'G8'.
513              
514             parameter: (float) ballistic_coefficient, qv: http://en.wikipedia.org/wiki/Ballistic_coefficient
515              
516             parameter: (float) muzzle_velocity_fps is the velocity of the projectile at time=0 (feet per second)
517              
518             parameter: (float) sight_height_inches is the distance from the center of the sight to the center of the bore (inches)
519              
520             parameter: (float) shot_angle_deg is the bore elevation (degrees, 0 = horizontal, 90 = vertical)
521              
522             parameter: (float) OPTIONAL: bore_to_sight_angle_deg is the difference in angle between the bore elevation and the sight elevation. Set to undef or -1 to have flight_simulator() calculate it for you from the zero_range_yards parameter (degrees)
523              
524             parameter: (float) wind_speed_fps is the velocity of the wind (feet per second)
525              
526             parameter: (float) wind_angle_deg is the direction the wind is blowing (degrees, 0 = shooting directly into wind, 90 = wind is blowing from the right, perpendicular to flight path, -90 = wind is blowing from the left, perpendicular to flight path)
527              
528             parameter: (float) OPTIONAL: max_range_yards is the maximum range to which the flight will be simulated (yards, default is 2000)
529              
530             returns: a reference to an array of hash references, one per yard, denoting the projectile's disposition when it reaches that range. All data fields are floating-point numbers:
531              
532             range_yards How far downrange the projectile has travelled, in yards.
533             drop_inches How far below the horizontal plane intersecting the muzzle the projectile has travelled, in inches.
534             correction_moa The angle from the muzzle to the projectile in the vertical plane, relative to the path from the muzzle to the target, in minutes.
535             time_seconds How much time has elapsed since leaving the muzzle, in seconds.
536             windage_inches How far in the horizontal plane the projectile has moved due to wind, in inches.
537             windage_moa The angle from the muzzle to the projectile in the horzontal plane, relative to the path from the muzzle to the target, in minutes.
538             vel_fps The velocity of the projectile, in feet per second.
539             vel_horiz_fps The horizontal component of the velocity of the projectile, in feet per second.
540             vel_vert_fps The vertical component of the velocity of the projectile, in feet per second.
541              
542             =back
543              
544             =cut
545              
546             # The solve-all solution.
547             # PP port of "SolveAll" from GNU-Ballistics gebc-1.07 lib/ballistics/ballistics.cpp
548             # $DragFunction is one of 'G1', 'G2', 'G5', 'G6', 'G7', 'G8'
549             # $DragCoefficient is the ballistic coefficient
550             # $Vi is the muzzle velocity of the projectile, feet per second
551             # $SightHeight is the height of the scope in inches
552             # $ShootingAngle is the bore elevation (angle)
553             # $ZAngle is the bore-to-sight angle, or -1 to calculate from $ZRange
554             # $ZRange is the range, in yards, to which the rifle is sighted-in.
555             # $WindSpeed is the speed of the wind, in feet per second
556             # $WindAngle is the angle of the wind relative to the shooter -- coming from the right is positive, coming from the left is negative.
557             # $MaxRange is the maximum range, in yards, to simulate (defaults to $BCOMP_MAXRANGE if -1 or undef)
558             sub flight_simulator {
559 1     1 1 518 my ($DragFunction, $DragCoefficient, $Vi, $SightHeight, $ShootingAngle, $ZAngle, $ZRange, $WindSpeed, $WindAngle, $MaxRange) = @_;
560 1         4 my $ptr = {};
561              
562 1 50       9 die("usage: ar = flight_simulator(DragFunction, DragCoefficient, Vel_fps, SightHeight_inches, ShootingAngle_deg, ZAngle_deg, ZRange_yards, WindSpeed_fps, WindAngle_deg, MaxRange_yards)\n") unless(defined($DragCoefficient));
563              
564 1         3 my $t = 0;
565 1         4 my $dt = 0.5 / $Vi;
566 1         3 my $v = 0;
567 1         2 my $vx = 0; # horizontal velocity, feet per second, at beginning of quantum
568 1         3 my $vx1 = 0; # horizontal velocity, feet per second, at end of quantum
569 1         2 my $vy = 0;
570 1         3 my $vy1 = 0;
571 1         2 my $dv = 0;
572 1         3 my $dvx = 0;
573 1         2 my $dvy = 0;
574 1         24 my $x = 0;
575 1         4 my $y = 0;
576              
577             # print ("fs: 0010 ShootingAngle=$ShootingAngle ZAngle=$ZAngle\n");
578              
579 1 50 33     9 $ZAngle = ZeroAngle($DragFunction, $DragCoefficient, $Vi, $SightHeight, $ZRange, 0) if ($ZAngle == -1 || !defined($ZAngle));
580 1 50 33     10 $MaxRange = $BCOMP_MAXRANGE unless (defined($MaxRange) && $MaxRange >= 0);
581 1         3 $MaxRange *= 3; # Convert yards to feet.
582            
583             # print ("fs: 0020 ShootingAngle=$ShootingAngle ZAngle=$ZAngle MaxRange=$MaxRange\n");
584              
585 1         3 my ($headwind, $crosswind) = HeadAndCrossWind($WindSpeed, $WindAngle);
586 1         10 my $Radians = DegtoRad($ShootingAngle + $ZAngle);
587 1         2 my $CosRadians = cos($Radians);
588 1         2 my $SinRadians = sin($Radians);
589 1         2 my $Gy = $GRAVITY * $CosRadians;
590 1         2 my $Gx = $GRAVITY * $SinRadians;
591              
592             # $vx = $Vi * cos(DegtoRad($ZAngle));
593             # $vy = $Vi * sin(DegtoRad($ZAngle));
594 1         2 $vx = $Vi * $CosRadians;
595 1         2 $vy = $Vi * $SinRadians;
596              
597             # print ("fs: 0030 vx=$vx vy=$vy\n");
598              
599 1         8 $y = -1 * $SightHeight / 12;
600              
601 1         2 my $yards_downrange = 0;
602 1         2 my $rv = [];
603 1         6 for ($t = 0; 1; $t += $dt) {
604 1805         2542 $vx1 = $vx;
605 1805         2376 $vy1 = $vy;
606 1805         3877 $v = ($vx**2 + $vy**2)**0.5;
607 1805         2700 $dt = 0.5 / $v;
608              
609             # print ("fs: 0040 vx=$vx vy=$vy\n");
610            
611             # Compute acceleration using the drag function retardation
612 1805         3742 $dv = velocity_loss($DragFunction, $DragCoefficient, $v + $headwind);
613 1805         3238 $dvx = -1 * ($vx / $v) * $dv;
614 1805         3031 $dvy = -1 * ($vy / $v) * $dv;
615              
616             # Compute velocity, including the resolved gravity vectors.
617 1805         2856 $vx = $vx + $dt * $dvx + $dt * $Gx;
618 1805         2592 $vy = $vy + $dt * $dvy + $dt * $Gy;
619              
620 1805 100       3840 if ($x/3 >= $yards_downrange) {
621 301         525 my $hr = {};
622 301         761 $hr->{range_yards} = $x / 3;
623 301         567 $hr->{drop_inches} = $y * -12;
624 301         601 $hr->{correction_moa} = 0;
625 301 100       1195 $hr->{correction_moa} = -1 * RadtoMOA(atan($y / $x)) if ($x > 0);
626 301         656 $hr->{time_seconds} = $t + $dt;
627 301         485 $hr->{windage_inches} = 0;
628 301 100       840 $hr->{windage_inches} = Windage($crosswind, $Vi, $x, $t + $dt) if ($x > 0);
629 301         580 $hr->{windage_moa} = 0;
630 301 50 100     1241 $hr->{windage_moa} = RadtoMOA(atan(($hr->{windage_inches} / 12) / (($hr->{range_yards} * 3) || 0.1))) if ($crosswind > 0);
631 301         646 $hr->{vel_fps} = $v;
632 301         983 $hr->{vel_horiz_fps} = $vx;
633 301         898 $hr->{vel_vert_fps} = $vy;
634 301         430 push (@{$rv}, $hr);
  301         625  
635 301         585 $yards_downrange++;
636             }
637            
638             # Compute position based on average velocity.
639 1805         3242 $x += $dt * ($vx + $vx1) / 2;
640 1805         2925 $y += $dt * ($vy + $vy1) / 2;
641            
642             # last if (abs($vy) > abs(3 * $vx));
643 1805 50       3518 last if ($t > 3600);
644 1805 100       4068 last if ($x >= $MaxRange+1.0);
645             }
646 1         7 return $rv;
647             }
648              
649             ################# no functions ported from GNU-Ballistics gebc-1.07 lib/ballistics/ballistics.cpp after this line ##################
650              
651             =head2 g1_drag (velocity_fps)
652              
653             The canonical function for computing instantaneous velocity drop at a given velocity, per the G1 drag model.
654              
655             =over 4
656              
657             parameter: (float) velocity_fps is the velocity of the projectile (in feet per second)
658              
659             returns: (float) the deceleration of the projectile from drag (in feet per second per second)
660              
661             =back
662              
663             =cut
664              
665             # PP implementation of G1 drag model.
666             # Multiply by G1 ballistic coefficient to get instantaneous velocity drop.
667             sub g1_drag {
668 5     5 1 3788 my ($fps) = @_;
669 5 100       140 if ($fps > 4230) { return (1.477404177730177E-04) * ($fps**1.9565); }
  1 50       35  
    100          
    50          
    50          
    50          
    50          
    50          
    100          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    100          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    50          
    0          
    0          
    0          
670 0         0 elsif ($fps > 3680) { return (1.920339268755614E-04) * ($fps**1.925); }
671 1         10 elsif ($fps > 3450) { return (2.894751026819746E-04) * ($fps**1.875); }
672 0         0 elsif ($fps > 3295) { return (4.349905111115636E-04) * ($fps**1.825); }
673 0         0 elsif ($fps > 3130) { return (6.520421871892662E-04) * ($fps**1.775); }
674 0         0 elsif ($fps > 2960) { return (9.748073694078696E-04) * ($fps**1.725); }
675 0         0 elsif ($fps > 2830) { return (1.453721560187286E-03) * ($fps**1.675); }
676 0         0 elsif ($fps > 2680) { return (2.162887202930376E-03) * ($fps**1.625); }
677 1         7 elsif ($fps > 2460) { return (3.209559783129881E-03) * ($fps**1.575); }
678 0         0 elsif ($fps > 2225) { return (3.904368218691249E-03) * ($fps**1.550); }
679 0         0 elsif ($fps > 2015) { return (3.222942271262336E-03) * ($fps**1.575); }
680 0         0 elsif ($fps > 1890) { return (2.203329542297809E-03) * ($fps**1.625); }
681 0         0 elsif ($fps > 1810) { return (1.511001028891904E-03) * ($fps**1.675); }
682 0         0 elsif ($fps > 1730) { return (8.609957592468259E-04) * ($fps**1.750); }
683 0         0 elsif ($fps > 1595) { return (4.086146797305117E-04) * ($fps**1.850); }
684 0         0 elsif ($fps > 1520) { return (1.954473210037398E-04) * ($fps**1.950); }
685 1         7 elsif ($fps > 1420) { return (5.431896266462351E-05) * ($fps**2.125); }
686 0         0 elsif ($fps > 1360) { return (8.847742581674416E-06) * ($fps**2.375); }
687 0         0 elsif ($fps > 1315) { return (1.456922328720298E-06) * ($fps**2.625); }
688 0         0 elsif ($fps > 1280) { return (2.419485191895565E-07) * ($fps**2.875); }
689 0         0 elsif ($fps > 1220) { return (1.657956321067612E-08) * ($fps**3.250); }
690 0         0 elsif ($fps > 1185) { return (4.745469537157371E-10) * ($fps**3.750); }
691 0         0 elsif ($fps > 1150) { return (1.379746590025088E-11) * ($fps**4.250); }
692 0         0 elsif ($fps > 1100) { return (4.070157961147882E-13) * ($fps**4.750); }
693 0         0 elsif ($fps > 1060) { return (2.938236954847331E-14) * ($fps**5.125); }
694 0         0 elsif ($fps > 1025) { return (1.228597370774746E-14) * ($fps**5.250); }
695 0         0 elsif ($fps > 980) { return (2.916938264100495E-14) * ($fps**5.125); }
696 0         0 elsif ($fps > 945) { return (3.855099424807451E-13) * ($fps**4.750); }
697 0         0 elsif ($fps > 905) { return (1.185097045689854E-11) * ($fps**4.250); }
698 0         0 elsif ($fps > 860) { return (3.566129470974951E-10) * ($fps**3.750); }
699 0         0 elsif ($fps > 810) { return (1.045513263966272E-08) * ($fps**3.250); }
700 0         0 elsif ($fps > 780) { return (1.291159200846216E-07) * ($fps**2.875); }
701 0         0 elsif ($fps > 750) { return (6.824429329105383E-07) * ($fps**2.625); }
702 0         0 elsif ($fps > 700) { return (3.569169672385163E-06) * ($fps**2.375); }
703 0         0 elsif ($fps > 640) { return (1.839015095899579E-05) * ($fps**2.125); }
704 0         0 elsif ($fps > 600) { return (5.711174688734240E-05) * ($fps**1.950); }
705 0         0 elsif ($fps > 550) { return (9.226557091973427E-05) * ($fps**1.875); }
706 1         14 elsif ($fps > 250) { return (9.337991957131389E-05) * ($fps**1.875); }
707 0         0 elsif ($fps > 100) { return (7.225247327590413E-05) * ($fps**1.925); }
708 0         0 elsif ($fps > 65) { return (5.792684957074546E-05) * ($fps**1.975); }
709 0         0 elsif ($fps > 0) { return (5.206214107320588E-05) * ($fps**2.000); }
710 0         0 return 0.0;
711             }
712              
713             =head2 muzzle_energy (mass_grains, velocity_fps, [want_joules_bool])
714              
715             A convenience function for computing kinetic energy from mass and velocity.
716             Despite its name, it is useful for computing the kinetic energy of a projectile
717             at any point during its flight.
718              
719             =over 4
720              
721             parameter: (float) mass_grains is the mass of the projectile (in grains)
722              
723             parameter: (float) velocity_fps is the velocity of the projectile (in feet per second)
724              
725             parameter: (boolean) OPTIONAL: set want_joules_bool to a True value to get Joules instead of foot-pounds (boolean, default=False)
726              
727             returns: (float) the kinetic energy of the projectile (in foot-pounds or Joules)
728              
729             =back
730              
731             =cut
732              
733             sub muzzle_energy {
734 2     2 1 836 my ($grains, $fps, $wantJ) = @_;
735 2 50       8 die("footpounds = muzzle_energy(grains, fps[, want_Joules])") unless(defined($fps));
736 2         7 my $nrg = 0.5 * $grains * $fps * $fps / 225312.839; # footpounds
737 2 100       6 $nrg *= 1.3558 if ($wantJ);
738 2         11 return int(100 * $nrg + 0.5) / 100;
739             }
740              
741             =head2 muzzle_velocity_from_energy (mass_grains, energy_ftlbs)
742              
743             A convenience function for computing velocity from mass and kinetic energy.
744             Despite its name, it is useful for computing the velocity of a projectile
745             at any point during its flight.
746              
747             If all you have is Joules, divide by 1.3558179 to get foot-pounds.
748              
749             =over 4
750              
751             parameter: (float) mass_grains is the mass of the projectile (in grains)
752              
753             parameter: (float) energy_ftlbs is the kinetic energy of the projectile (in foot-pounds)
754              
755             returns: (float) the velocity of the projectile (in feet per second)
756              
757             =back
758              
759             =cut
760              
761             sub muzzle_velocity_from_energy {
762 1     1 1 423 my ($grains, $footpounds) = @_;
763 1 50       4 die("feet/second = muzzle_velocity_from_energy(grains, footpounds)") unless(defined($footpounds));
764 1         5 my $fps = (450625.678 * $footpounds / $grains) ** 0.5;
765 1         7 return int(100 * $fps + 0.5) / 100;
766             }
767              
768             1;
769              
770             =head1 TODO
771              
772             Contact Geoffrey Kolbe (Inventor at Border Barrels Limited) and ask for permission to publish his splendid implementations:
773              
774             * Bullet drag calculator - http://www.border-barrels.com/cgi-bin/drag_working.cgi
775              
776             * Barrel weight calculator - http://www.border-barrels.com/cgi-bin/swamped_barrel_weight.cgi
777              
778             His drag calculator seems better than my own ebc function.
779              
780             =cut