File Coverage

lib/Physics/Ballistics/Terminal.pm
Criterion Covered Total %
statement 338 414 81.6
branch 149 294 50.6
condition 18 52 34.6
subroutine 35 37 94.5
pod 19 33 57.5
total 559 830 67.3


line stmt bran cond sub pod time code
1             package Physics::Ballistics::Terminal;
2 1     1   145530 use strict;
  1         2  
  1         26  
3 1     1   4 use warnings;
  1         2  
  1         68  
4             require Exporter;
5             our @ISA = qw(Exporter);
6             our @EXPORT = qw(anderson boxes heat_dop ke me2te me2ce me2cem odermatt pc pc_simple hits_score sigma average rndto r2d d2r poncelet te2me lethality hv2bhn bhn2hv hrc2bhn bhn2hrc psi2bhn bhn2psi);
7             our $VERSION = '1.03';
8              
9 1     1   244 use Physics::Ballistics;
  1         5  
  1         44  
10 1     1   12 use Math::Trig;
  1         3  
  1         5914  
11              
12             =head1 NAME
13              
14             Physics::Ballistics::Terminal -- Terminal ballistics formulae.
15              
16             =cut
17              
18             =head1 ABSTRACT
19              
20             Terminal ballistics is the study of what happens when a projectile impacts
21             its target. This module implements a variety of functions and mathematical
22             formulae useful in the analysis and prediction of terminal ballistic effects.
23              
24             =head1 TWO DOMAINS OF VELOCITY
25              
26             Some of these functions pertain to the "ballistic" domain, and others pertain
27             to the "hypervelocity" domain. These refer to two different and somewhat
28             ill-defined ranges of velocity. "Ballistic" velocity ranges from about 300
29             to 1100 meters per second, while "Hypervelocity" ranges from about 1100 meters
30             per second to several tens of thousands of meters per second.
31              
32             Why does this matter? Because successful models of ballistic interactions
33             are not accurate in the hypervelocity domain, and successful models of
34             hypervelocty interactions are not accurate in the ballistic domain. Thus it
35             is crucial to use the model which is valid for the velocity of interaction
36             you are attempting to predict.
37              
38             Functions which are only valid in one domain or the other will be thus marked.
39              
40             =head1 REGARDING BULLET DIAMETERS
41              
42             Some of these functions require the diameter of a projectile as a parameter.
43             Please note that bullet diameters are usually different from the names of
44             their calibers. NATO 5.56mm bullets are actually 5.70mm in diameter, while
45             Russian 5.45mm bullets are actually 5.62mm. .308 caliber bullets really are
46             0.308 inches in diameter (7.82mm), but .22 Long Rifle bullets are 0.222
47             inches across. Please do not make assumptions; check before plugging it in!
48              
49             =head1 DEFINITIONS
50              
51             =head2 DU
52              
53             DU is short for "Depleted Uranium". This denotes any of a number of metallic
54             alloys containing a high fraction of Uranium, the most common of which is
55             99.25% Uranium and and 0.75% Titanium. This material is extremely dense and
56             hard, and somewhat ductile, making it excellent for armor-piercing projectiles.
57             Contrary to popular myth, DU projectiles are not "nuclear" and do not explode,
58             though in the hypervelocity domain they can be pyrophoric. Nor is DU highly
59             radioactive. It is, however, a heavy metal (like lead, mercury, and arsenic)
60             and therefore toxic.
61              
62             =head2 MILD STEEL
63              
64             There are many thousands of steel formulations and treatments, resulting in
65             myriad different strengths and hardnesses. "Mild Steel" is a general term
66             for any of a number of inexpensive steels of moderate to low strength and
67             hardness. The steels used in rebar, trench plates, automotive bodies, and
68             other commodity and construction applications tend to be of this type. Mild
69             steels are an inferior material for armor, providing only about 80% of the
70             protection as an equivalent thickness of RHA. Mild steel is often used in
71             military projectile cores (as in America's 7.62mm M80 "Ball") because it is
72             cheap and much more ductile than hardened steels. This ductility makes it
73             less likely to fracture while penetrating its target (whether the target is
74             made of flesh or harder stuff).
75              
76             =head2 RHA
77              
78             RHA is short for "Rolled Homogenous Armor". It is a commonly-used term for
79             hardened steel armor material in general, or for armor steel which complies
80             with the military specification MIL-A-12560. RHA is also a common standard
81             material for normalizing depth of penetration. It is roughly equivalent to
82             AISI 4340 steel in character, and AISI 4340 is often used in lieu of "real"
83             RHA in laboratory experiments.
84              
85             Unless otherwise noted, return values representing a depth of penetration
86             should be understood to represent depth of penetration into an RHA target.
87             It is common for armor systems to represent their resistance to penetration
88             in terms of RHA thickness.
89              
90             =head2 WC
91              
92             WC is short for "Tungsten Carbide". This is a technical ceramic of tungsten
93             and carbon, a dense and hard material of relatively low expense. WC has seen
94             prolific use in armor-piercing projectiles because high density and hardness
95             are both desirable qualities in that role. Being a ceramic, however, it is
96             also brittle, and thus vulnerable to modern composite armors, which use
97             synergistic effects to break up projectiles and thus degrade their penetration
98             capabilities. Thus, WC penetrators are less capable of penetrating composite
99             armor systems than indicated by their RHA equivalence ratings (since their
100             brittleness plays less of a role in limiting their penetration into monolithic
101             steel targets).
102              
103             =head2 WHA
104              
105             WHA is short for "Tungsten-Heavy Alloy". This denotes any of several metallic
106             alloys containing a high fraction of Tungsten. Some formulations offer high
107             density, hardness and resilience (such as 90% W / 7% Ni / 3% Fe) making them
108             excellent materials for armor-piercing projectiles. Unlike WC, appropriate WHA
109             formulations are not brittle, and are unlikely to break from passing through
110             composite armor systems.
111              
112             =head1 ANNOTATIONS OF SOURCES
113              
114             Regarding their source, these functions fall into three categories: Some are
115             simple encodings of basic physics (like energy = 1/2 * mass * velocity**2),
116             and these will not be cited. Others are from published works, such as books
117             or trade journals, and these will be cited when possible. A few are products
118             of my own efforts, and will be thus annotated.
119              
120             =head1 OOP INTERFACE
121              
122             A more integrated, object-oriented interface for these functions is under
123             development.
124              
125             =head1 FUNCTIONS
126              
127             =head2 anderson (length_cm, diam_cm, vel_kps, [penetrator_material,] [deg_angle,] [scaling_factor])
128              
129             Attempts to estimate how deeply a long-rod projectile will penetrate into RHA (semi-infinite penetration).
130              
131             This function is based on Anderson's _Accuracy of Perforation Equations_, less 11% correction per that paper's conclusions, and including adjustments from Lakowski for scale, material, and backsurface effects.
132             qv: L
133              
134             ONLY VALID IN HYPERVELOCITY DOMAIN.
135              
136             =over 4
137              
138             parameter: (float) penetrator length (in cm)
139              
140             parameter: (float) penetrator diameter (in cm)
141              
142             parameter: (float) penetrator velocity (in kilometers per second)
143              
144             parameter: (float or string) OPTIONAL: penetrator material or material multiplier. (defaults to 1.0) Valid values are:
145              
146             =over 4
147              
148             * an integer, for custom material factors
149              
150             * "steel": Hardened steel == 0.50
151              
152             * "wha": Tungsten-heavy alloy (NOT tungsten carbide) == 1.00
153              
154             * "wc": Tungsten Carbide == 0.72
155              
156             * "du": Depleted uranium alloy == 1.13
157              
158             =back
159              
160             parameter: (float) OPTIONAL: angle of impact, 0 == perpendicular to target surface (in degrees, defaults to 0)
161              
162             parameter: (float) OPTIONAL: scaling effect, relative to M829A2 dimensions (unitless, defaults to 1.0)
163              
164             returns: (float) Depth of penetration (in cm)
165              
166             =back
167              
168             =cut
169              
170             # Given attributes of a hypervelocity-domain long-rod penetrator, returns penetration into RHA, in cm.
171             # Based on Anderson TN, from _Accuracy of Perforation Equations_, less 11% correction per that paper's conclusions, and including adjustments from Lakowski for scale, material, and backsurface effects, qv: http://tank-net.com/forums/index.php?showtopic=8332&pid=156211&mode=threaded&show=&st=& and http://63.99.108.76/forums/index.php?showtopic=10482&st=100
172             # NOTE: only valid in hypervelocity domain, above 1100 meters/second.
173             sub anderson {
174 3     3 1 1638 my ($len, $diam, $vel, $material, $deg_angle, $scaling) = @_;
175 3 50 33     21 die("usage: pen_cm = anderson(length_cm, diam_cm, vel_kps, material, [ deg_angle ]") unless (defined($len) && defined($vel));
176 3 50       10 $scaling = 1.0 unless (defined($scaling));
177 3 50       8 $material = 1.0 unless (defined($material)); # 1.00 = WHA
178 3         19 $material = lc($material);
179 3 50       10 $material = 1.00 if ($material eq 'wha'); # Tungsten Heavy Alloy
180 3 50       11 $material = 1.13 if ($material eq 'du'); # Depleted Uranium / Titanium Alloy
181 3 50       8 $material = 1.20 if ($material eq 'duv'); # Depleted Uranium / Vanadium Alloy
182 3 50       8 $material = 0.72 if ($material eq 'wc'); # Tungsten Carbide
183 3 50       8 $material = 0.50 if ($material eq 'steel');
184 3 50       14 $material = 0.50 if ($material =~ /[^\d\.]/);
185 3 50       9 $deg_angle = 0 unless (defined($deg_angle));
186 3         9 my $angle = pi * $deg_angle / 180; # convert degrees to radians
187 3         13 my $log1 = log($len / $diam);
188 3         10 my $baseline = (1.044 * $vel) - (0.194 * log($len / $diam)) - 0.212;
189 3         10 my $scale_effect = 1 + ($diam / (13 * $scaling));
190 3         15 my $backsurface = $diam / cos($angle);
191 3         9 my $base_pen = $baseline * $scale_effect * $len;
192 3         10 my $penetration = $base_pen * $material;
193 3         7 $penetration = $penetration + $backsurface;
194 3         5 $penetration = $penetration * .89; # less 11% -- Anderson's own analysis concluded that this formula overpredicts penetration by about this much
195 3         10 $penetration = int($penetration * 10 + 0.5) / 10;
196 3         11 return $penetration;
197             }
198              
199             =head2 boxes (length, width, height, front thickness, back thickness, side thickness, top thickness, underside thickness, density)
200              
201             Calculates the volumes, mass, and volume-to-mass ratio of a hollow box of rectangular cross-sections.
202              
203             =over 4
204              
205             parameter: (float) interior distance from front to back (in cm)
206              
207             parameter: (float) interior distance from left to right (in cm)
208              
209             parameter: (float) interior distance from top to bottom (in cm)
210              
211             parameter: (float) thickness of front wall (in cm)
212              
213             parameter: (float) thickness of back wall (in cm)
214              
215             parameter: (float) thickness of side walls (in cm)
216              
217             parameter: (float) thickness of bottom wall (in cm)
218              
219             parameter: (float) specific density of wall material (g/cc)
220              
221             returns:
222              
223             =over 4
224              
225             * (float) interior volume (in cc)
226              
227             * (float) exterior volume (in cc)
228              
229             * (float) total wall mass (in grams)
230              
231             * (float) ratio of interior volume to mass (cc/g)
232              
233             =back
234              
235             =back
236              
237             =cut
238              
239             sub boxes { # params: length, width, height, thick / front, back, side, top, bottom, den
240 1     1 1 548 my ($inx, $iny, $inz, $tf, $tb, $ts, $tt, $tu, $den) = @_;
241 1 50       5 die("usage: boxes ( interior_x_cm, int_y, int_z, thickness_front_cm, thick_back, thick_side, thick_top, density ) = string") unless ( defined($inx) );
242 1         3 my ($inv, $ouv, $vdiff, $mass, $retval, $lbs, $rat);
243 1         3 $inv = $inx * $iny * $inz;
244 1         4 $ouv = ($inx + $tf + $tb) * ($iny + 2 * $ts) * ($inz + $tt + $tu);
245 1         2 $vdiff = $ouv - $inv;
246 1         3 $mass = $vdiff * $den;
247 1         4 $rat = int($inv * 100 / $mass) / 100;
248             # returns: interior volume in cc, exterior volume, difference between interior and exterior volumes,
249             # mass in grams, mass in pounds, and ratio of interior volume to mass (cc/g)
250 1         5 return ($inv, $ouv, $mass, $rat);
251             }
252              
253             =head2 heat_dop(diameter_mm, standoff_distance, [target_density,] [precision_bool], [target_hardness_bhn])
254              
255             Attempts to predict the depth of penetration of a copper-lined conical shaped charge into steel.
256              
257             Based on Ogorkiewicz's book, _Design and Development of Fighting Vehicles_, and
258             modified as per _Journal of Battlefield Technology_ Vol 1-1 pp 1. A copy of
259             the JBT chart may be found at:
260              
261             L
262              
263             The author has modified this formula slightly to account for errors observed in
264             Ogorkiewicz's results, relative to empirically derived results.
265              
266             For better understanding of shaped charge penetration, please review:
267              
268             L
269              
270             =over 4
271              
272             parameter: (float) cone diameter (in mm)
273              
274             parameter: (float or str) standoff distance (multiple of cone diameter if float, else in mm, for instance "80.5mm")
275              
276             parameter: (float) OPTIONAL: density of target material (in g/cc, default is 7.86, the density of RHA)
277              
278             parameter: (boolean) OPTIONAL: assume precision shaped charge (default is False)
279              
280             parameter: (float) OPTIONAL: hardness of target material (in BHN, default is 300, low in the range of RHA hardnesses)
281              
282             returns: (int) depth of penetration (in mm)
283              
284             =back
285              
286             =cut
287              
288             # Copper-lined shaped charge depth of penetration, lifted from Ogorkiewicz's _Design and Development of Fighting Vehicles_, and modified as per _Journal of Battlefield Technology_ Vol 1-1 pp 1, copy of chart from that article can be found at: http://ciar.org/ttk/mbt/news/news.smm.ww2-armor-plate.de5bf54f.0110271532.871cbf@posting.google.com.txt
289             # NOTE: does not take into account any advanced effects from composited targets.
290             # NOTE: removed jet density parameter from argument list because liner ductility effects eclipse liner density in practice, and I don't want to factor liner ductility into this code right now. (eg: according to Ogorkiewicz, a steel liner would increase penetration, but in practice steel liners reduce penetration due to their relatively low ductility.)
291             # NOTE: Ogorkiewicz's curve for nonprecision charges was a little low at optimal standoff and high elsewhere relative to the _JoBT_ article chart, so I split the difference. I suspect his precision charge curve is similarly a bit optimistic in favor of higher penetration, so take it with a grain of salt.
292             sub heat_dop {
293 3     3 1 3996 my ($diam, $soff, $aden, $prec, $hard) = @_;
294 3         8 my $jden;
295 3         6 my $pen = 0;
296 3 50       11 die("usage: heat_dop (diameter_mm, standoff[mm], [targ-den], [precision], [hard])\nDiameter units is mm\nstandoff assumed cd's unless 'mm' is specified\ntarget density default is 7.86 (RHA)\nprecision default is 0 (non-precision), 1 for high precision\nhardness is BHN, default is 300 (ignore if not steel)\nReturns depth of penetration in mm") unless (defined ($soff));
297 3   100     13 $aden //= 7.86;
298 3   50     17 $jden //= 8.96;
299 3   100     13 $prec //= 0;
300 3   50     14 $hard //= 300;
301             # print (" aden=$aden jden=$jden prec=$prec hard=$hard\n");
302 3         7 my $MIN_DENSITY = 0.00000000001;
303 3 50       10 $aden = $MIN_DENSITY if ($aden < $MIN_DENSITY); # avoid divide-by-zero error
304 3 50       23 if ($soff =~ /(\d+)mm/) { $soff = $1 / $diam; } # normalize to factor of cone diameters
  0         0  
305             # $soff *= ($hard / 300)**0.5; # I'm guessing here -- target hardness does appear to shift optimal standoff, but effects of nonoptimal standoff not quite proportional to this relation. -- zzapp, figure this out.
306 3 100       11 if ($prec) {
307             # precision shaped charge DoP curve looks something like this:
308 1 50       8 if ($soff <= 1) { $pen = 3.0 + 1.500 * ($soff - 0); }
  0 50       0  
    0          
    0          
309 1         4 elsif ($soff <= 3) { $pen = 4.5 + 0.350 * ($soff - 1); }
310 0         0 elsif ($soff <= 6) { $pen = 5.2 + 0.100 * ($soff - 3); }
311 0         0 elsif ($soff <= 9) { $pen = 5.5 - 0.033 * ($soff - 6); }
312 0         0 else { $pen = 5.4 - (($soff - 9) / 4.117); }
313             }
314             else {
315             # nonprecision shaped charge DoP curve looks something like this:
316 2 50       13 if ($soff <= 1) { $pen = 3.00 + 1.200 * ($soff - 0); }
  0 50       0  
    50          
    0          
    0          
    0          
317 0         0 elsif ($soff <= 2) { $pen = 4.20 + 0.150 * ($soff - 1); }
318 2         8 elsif ($soff <= 3) { $pen = 4.35 - 0.150 * ($soff - 2); }
319 0         0 elsif ($soff <= 4) { $pen = 4.20 - 0.400 * ($soff - 3); }
320 0         0 elsif ($soff <= 7) { $pen = 4.00 - 0.550 * ($soff - 4); }
321 0         0 elsif ($soff <= 10) { $pen = 2.35 - 0.170 * ($soff - 7); }
322 0         0 else { $pen = 1.84 - (($soff - 10) / 7.95); }
323             }
324 3         17 $pen *= (($jden / $aden) / (8.96 / 7.86))**0.5;
325 3         7 $pen *= $diam;
326             # round off, this is *not* any kind of precise estimate!
327 3         8 $pen = int($pen + 0.5);
328 3         11 return $pen; # returns millimeters
329             }
330              
331             # ke: removed: use P::B::E::muzzle_energy() instead
332              
333             =head2 me2te (mass_efficiency, density)
334              
335             Given the mass efficiency of a material, returns its thickness efficiency.
336              
337             This function is used when comparing armor materials on the basis of their RHA
338             equivalence. Mass efficiency is a factor of how much armor mass than RHA mass
339             provides the same resistance to penetration. Conversely, thickness efficiency
340             is a factor of how much less armor thickness than RHA thickness provides the
341             same resistance to penetration.
342              
343             For instance, if an armor material has a mass efficiency of 4.0, then only a
344             quarter as much mass is needed to provide a given protection level compared to
345             RHA of equivalent protection. A pound of the armor material provides the same
346             protection as four pounds of RHA.
347              
348             Similarly, if an armor material has a thickness efficiency of 3.0, then only a
349             third as much thickness is needed to provide a given protection level compared
350             to RHA of equivalent protection. An inch of the armor material provides the
351             same protection as three inches of RHA.
352              
353             If the density of the armor material is known, me2te() and te2me() may be used
354             to convert back and forth between mass efficiency or thickness efficiency,
355             depending on which is known.
356              
357             =over 4
358              
359             parameter: (float) armor material mass efficiency (unitless, factor relative to RHA)
360              
361             parameter: (float) armor material density (g/cc)
362              
363             returns: (float) armor material thickness efficiency (unitless, factor relative to RHA)
364              
365             =back
366              
367             =cut
368              
369             sub me2te { # given mass efficiency and density, returns thickness efficiency
370 1     1 1 494 my ($me, $den) = @_;
371 1         5 return $me * $den / 7.86;
372             }
373              
374             =head2 me2ce (mass_efficiency, cost_usd_per_pound)
375              
376             Given the mass efficiency of a material, returns its cost efficiency.
377              
378             See the description of me2te() for more explanation.
379              
380             This actually returns the cost efficiency relative to AISI 4340 steel, which is
381             often used as a close approximation to RHA. The costs of actual MIL-A-12560
382             compliant steel are dominated by political factors, which are beyond the scope
383             of this module.
384              
385             =over 4
386              
387             parameter: (float) armor material mass efficiency (unitless, factor relative to RHA)
388              
389             parameter: (float) armor material cost (USA dollars / pound)
390              
391             returns: (float) armor material cost efficiency (unitless, factor relative to RHA)
392              
393             =back
394              
395             =cut
396              
397             sub me2ce { # given mass efficiency and cost per pound, returns cost efficiency relative to AISI 4340 steel (which closely approximates characteristics of RHA).
398 1     1 1 487 my ($me, $cost) = @_;
399             # AISI 4340 steel is about $3.80/lb; qv https://www.metalsupermarkets.com/CART.ASPX?PRODUCTID=MR4340/9
400 1         5 return $me * 3.80 / $cost;
401             }
402              
403             =head2 me2cem (mass_efficiency, cost_usd_per_pound)
404              
405             Given the mass efficiency of a material, returns its cost efficiency relative to mild steel.
406              
407             See the description of me2ce() for more explanation.
408              
409             =over 4
410              
411             parameter: (float) armor material mass efficiency (unitless, factor relative to RHA)
412              
413             parameter: (float) armor material cost (USA dollars / pound)
414              
415             returns: (float) armor material cost efficiency (unitless, factor relative to mild steel)
416              
417             =back
418              
419             =cut
420              
421             sub me2cem { # given mass efficiency and cost per pound, returns cost efficiency relative to mild steel
422 1     1 1 486 my ($me, $cost) = @_;
423 1         3 my $mild_steel_cost = 0.40; # Mild Steel 1" Plate is about $0.40/lb
424 1         3 my $mild_steel_me = 0.81; # Mild Steel 1" Plate has mass efficiency of about 0.81
425 1         5 return ($me * $mild_steel_cost) / ($cost * $mild_steel_me);
426             }
427              
428             =head2 odermatt (length_cm, diam_cm, vel_mps, target_density, target_uts_kpsi, rod_density, deg_angle, kps_drop_per_km, range_km, target_thickness_cm, [tip_length_cm, kA1, kA2])
429              
430             Attempts to estimate perforation limit for a long-rod projectile penetrating RHA. Produces more accurate results than Anderson, but also requires more hard-to-get information, and doesn't exactly measure the same thing (perforation limit, vs depth into semi-infinite target).
431              
432             This function is based on Lanz and Odermatt's paper _Post Perforation Length & Velocity of KE Projectiles with single Oblique Targets_, published in the 15th International Symposium of Ballistics.
433              
434             ONLY VALID IN HYPERVELOCITY DOMAIN.
435              
436             Only valid for penetrator length/diameter ratios of 10.0 or higher, unless kA1 and kA2 are provided (which afaik can only be derived empirically, so good luck).
437              
438             =over 4
439              
440             parameter: (float) penetrator length (in cm)
441              
442             parameter: (float) penetrator diameter (in cm)
443              
444             parameter: (float) penetrator velocity (in meters per second)
445              
446             parameter: (float) target density (in g/cc)
447              
448             parameter: (float) target ultimate tensile strength (in kpsi)
449              
450             parameter: (float) penetrator density (in g/cc)
451              
452             parameter: (float) angle of impact (in degrees, 0 == perpendicular to target surface)
453              
454             parameter: (float) target thickness (in cm)
455              
456             parameter: (float) OPTIONAL: penetrator tip length (in cm, defaults to three times penetrator diameter)
457              
458             parameter: (float) OPTIONAL: kA1 empirically discovered constant (only required for L/D < 10.0)
459              
460             parameter: (float) OPTIONAL: kA2 empirically discovered constant (only required for L/D < 10.0)
461              
462             returns: (float) Target's perforation limit (in cm)
463              
464             =back
465              
466             =cut
467              
468             # Given attributes of a hypervelocity-domain long-rod penetrator and its target, returns limit of perforation depth, in cm.
469             # Based on Odermatt's Perforation Limit Equation for long rod penetrators, valid for L:D of 10 or over.
470             # 15th international symposium of ballistics - "Post Perforation Length & Velocity of KE Projectiles with Single Oblique Targets".
471             # NOTE: only works for L:D of 10 or greater, unless kA1 and kA2 are defined. For smaller penetrators, use anderson or pc.
472             # NOTE: only valid in hypervelocity domain, above 1100 meters/second.
473             sub odermatt {
474 1     1 1 509 my ($len, $diam, $vel, $pen_mat, $targ_mat,
475             $penden, $deg_angle, $targthick, $tip_len, $targden, $targ_kpsi, $kA1, $kA2) = @_;
476 1 0 0     8 die("Error: L:D of $len:$diam is outside this formula's domain\n") unless (($len / $diam >= 10) || (defined($kA1) && defined($kA2)));
      33        
477 1         6 my %mat_to_den = ('du' => 18800, 'wha' => 17000, 'steel' => 7860);
478 1         6 my %mat_to_a = ('du' => 0.825, 'wha' => 0.994, 'steel' => 1.104);
479 1         5 my %mat_to_c0 = ('du' => 90.0, 'wha' => 134.5, 'steel' => 9874); # term 5 differs for steel, so const c given instead of c0
480 1         4 my %mat_to_c1 = ('du' => -0.0849, 'wha' => -0.148, 'steel' => 0);
481 1 50       4 die("Error: Unsupported target material. Use one of: du wha steel\n") unless(defined($mat_to_a{$targ_mat}));
482 1 50       5 die("Error: Unsupported penetrator material. Use one of: du wha steel\n") unless(defined($mat_to_c0{$pen_mat}));
483 1         8 my $const_a = $mat_to_a{$targ_mat};
484 1         3 my $const_b0 = 0.283;
485 1         3 my $const_b1 = 0.0656;
486 1         3 my $const_c0 = $mat_to_c0{$pen_mat};
487 1         4 my $const_c1 = $mat_to_c1{$pen_mat};
488             # zzzappp
489 1         4 my $targ_mpa = $targ_kpsi * 6.895; # converting kpsi to MPa
490 1         4 my $targ_den_gm3 = $targden * 1000; # converting g/cc to kg/m3
491 1         3 my $pen_den_gm3 = $penden * 1000; # converting g/cc to kg/m3
492 1         3 my $diam_mm = $diam * 10; # cm to mm
493 1         20 my $len_mm = $len * 10; # cm to mm
494 1         3 my $targ_mm = $targthick * 10; # cm to mm
495 1   50     8 $kA1 = $kA1 || 3.94;
496 1   50     5 $kA2 = $kA2 || 11.20;
497 1   33     5 $tip_len = $tip_len || $diam * 3; # if no tip length given, assume three diameters long
498 1         3 $tip_len = $tip_len * 10; # cm to mm
499              
500             # length to diameter ratio influence (valid for Lw/D of at least 10 -- if Lw/D is lower, use anderson):
501 1         4 my $Lw = $len_mm - 2 * $tip_len / 3 - 1.5 * $diam_mm; # approximation of length of penetrator after replacing conical tip with cylinder of equal mass and diameter and reducing remaining length by 1.5 diameters
502             # print "Lw= len_mm=$len_mm - 2 * tip_len=$tip_len / 3 - 1.5 * diam_mm=$diam_mm = $Lw\n";
503 1         8 my $LDtanh = Math::Trig::tanh(($Lw / $diam_mm) - 10);
504 1         49 my $facA = 1 + $kA1 * $diam_mm / ($Lw * (1 - $LDtanh / $kA2));
505             # print "facA = 1 + kA1=$kA1 * diam_mm=$diam_mm / (Lw=$Lw * (1 - LDtanh=$LDtanh / kA2=$kA2)) = $facA\n";
506              
507             # target obliquity:
508 1         4 my $rad_angle = pi * $deg_angle / 180; # convert degrees to radians
509 1         3 my $facB = 0; # target obliquity
510 1         6 $facB = cos($rad_angle)**-0.225;
511             # print "facB = cos(rad_angle=$rad_angle)**-0.225 = $facB\n";
512              
513             # density ratio of penetrator to target
514 1         4 my $facC = ($pen_den_gm3 / $targ_den_gm3)**0.5;
515             # print "facC = (pen_den_gm3=$pen_den_gm3 / targ_den_gm3=$targ_den_gm3)**0.5 = $facC\n";
516              
517             # material properties and impact velocity
518 1         5 my $targMatFac = 22.1 + 0.01274 * $targ_mpa - 0.00000947 * $targ_mpa**2;
519             # print "pen_den_gm3 = $pen_den_gm3 vel=$vel\n";
520 1         3 my $facD_N = -1 * $targMatFac * $targ_mpa;
521             # print "facD_N = -1 * targMatFac=$targMatFac * targ_mpa=$targ_mpa = $facD_N\n";
522 1         2 my $facD_D = $pen_den_gm3 * $vel**2;
523             # print "facD_D = pen_den_gm3=$pen_den_gm3 * vel=$vel**2 = $facD_D\n";
524 1         3 my $facD = exp($facD_N / $facD_D);
525             # print "facD = exp(facD_N=$facD_N / facD_D=$facD_D) = $facD\n";
526              
527             # print "len_mm=$len_mm facA(diam,len)=$facA facB(angle)=$facB facC(pden,tden)=$facC facD(targ_mpa,pden,vel)=$facD\n";
528 1         3 my $penetration_mm = $len_mm * $facA * $facB * $facC * $facD;
529 1         4 my $penetration_cm = int($penetration_mm + 0.5) / 10; # rounding to nearest mm, then converting to cm
530 1         6 return $penetration_cm;
531             }
532              
533             # This formula implementation is horribly broken; still trying to figure it out.
534             # Given attributes of a hypervelocity-domain long-rod penetrator and its target, returns penetration into RHA, in cm.
535             # based on Lanz/Odermatt depth of penetration equation for long rod penetrators, valid for L:D of 10 or over.
536             # 15th international symposium of ballistics - "Post Perforation Length & Velocity of KE Projectiles with Single Oblique Targets".
537             # NOTE: only works for L:D of 10 or greater, unless kA1 and kA2 are defined. For smaller penetrators, use anderson or pc.
538             # NOTE: only valid in hypervelocity domain, above 1100 meters/second.
539             sub lanz_odermatt_BROKEN {
540 0     0 0 0 my ($len, $diam, $vel, $targden, $targ_kpsi, $penden, $deg_angle, $targthick, $tip_len, $kA1, $kA2) = @_;
541 0 0 0     0 die("usage: pen_cm = odermatt (length_cm, diam_cm, vel_mps, target_den, target_uts_kpsi, pen_density, deg_angle, target_thick, [tip_length_cm, kA1, kA2 ]") unless (defined($len) && defined($targthick));
542 0 0 0     0 die("error, L:D of $len:$diam is outside this formula's domain\n") unless (($len / $diam >= 10) || (defined($kA1) && defined($kA2)));
      0        
543             # print "len=$len, diam=$diam, vel=$vel, targden=$targden, targ_kpsi=$targ_kpsi, penden=$penden, deg_angle=$deg_angle, targthick=$targthick, tip_len=$tip_len\n";
544 0         0 my $targ_mpa = $targ_kpsi * 6.895; # converting kpsi to MPa
545 0         0 my $targ_den_gm3 = $targden * 1000; # converting g/cc to kg/m3
546 0         0 my $pen_den_gm3 = $penden * 1000; # converting g/cc to kg/m3
547 0         0 my $diam_mm = $diam * 10; # cm to mm
548 0         0 my $len_mm = $len * 10; # cm to mm
549 0         0 my $targ_mm = $targthick * 10; # cm to mm
550 0   0     0 $kA1 = $kA1 || 3.94;
551 0   0     0 $kA2 = $kA2 || 11.20;
552 0   0     0 $tip_len = $tip_len || $diam * 3; # if no tip length given, assume three diameters long
553 0         0 $tip_len = $tip_len * 10; # cm to mm
554              
555             # length to diameter ratio influence (valid for Lw/D of at least 10 -- if Lw/D is lower, use anderson):
556 0         0 my $Lw = $len_mm - 2 * $tip_len / 3 - 1.5 * $diam_mm; # approximation of length of penetrator after replacing conical tip with cylinder of equal mass and diameter and reducing remaining length by 1.5 diameters
557 0         0 print "Lw= len_mm=$len_mm - 2 * tip_len=$tip_len / 3 - 1.5 * diam_mm=$diam_mm = $Lw\n";
558 0         0 my $LDtanh = Math::Trig::tanh(($Lw / $diam_mm) - 10);
559 0         0 my $facA = 1 + $kA1 * $diam_mm / ($Lw * (1 - $LDtanh / $kA2));
560 0         0 print "facA = 1 + kA1=$kA1 * diam_mm=$diam_mm / (Lw=$Lw * (1 - LDtanh=$LDtanh / kA2=$kA2)) = $facA\n";
561              
562             # target obliquity:
563 0         0 my $rad_angle = pi * $deg_angle / 180; # convert degrees to radians
564 0         0 my $facB = 0; # target obliquity
565 0         0 $facB = cos($rad_angle)**-0.225;
566 0         0 print "facB = cos(rad_angle=$rad_angle)**-0.225 = $facB\n";
567              
568             # density ratio of penetrator to target
569 0         0 my $facC = ($pen_den_gm3 / $targ_den_gm3)**0.5;
570 0         0 print "facC = (pen_den_gm3=$pen_den_gm3 / targ_den_gm3=$targ_den_gm3)**0.5 = $facC\n";
571              
572             # material properties and impact velocity
573 0         0 my $targMatFac = 22.1 + 0.01274 * $targ_mpa - 0.00000947 * $targ_mpa**2;
574 0         0 print "pen_den_gm3 = $pen_den_gm3 vel=$vel\n";
575 0         0 my $facD_N = -1 * $targMatFac * $targ_mpa;
576 0         0 print "facD_N = -1 * targMatFac=$targMatFac * targ_mpa=$targ_mpa = $facD_N\n";
577 0         0 my $facD_D = $pen_den_gm3 * $vel**2;
578 0         0 print "facD_D = pen_den_gm3=$pen_den_gm3 * vel=$vel**2 = $facD_D\n";
579 0         0 my $facD = exp($facD_N / $facD_D);
580 0         0 print "facD = exp(facD_N=$facD_N / facD_D=$facD_D) = $facD\n";
581              
582 0         0 print "len_mm=$len_mm facA(diam,len)=$facA facB(angle)=$facB facC(pden,tden)=$facC facD(targ_mpa,pden,vel)=$facD\n";
583 0         0 my $penetration_mm = $len_mm * $facA * $facB * $facC * $facD;
584 0         0 my $penetration_cm = int($penetration_mm + 0.5) / 10; # rounding to nearest mm, then converting to cm
585 0         0 return $penetration_cm;
586             }
587              
588             =head2 pc (mass_grains, velocity_fps, distance_feet, diameter_inches, bullet_shape_str, [target_material])
589              
590             Attempts to estimate how deeply a small-arms projectile will penetrate into a target.
591              
592             Optimized for projectiles near 7.5mm in diameter, works okay for projectiles as small as 5mm or as large as 14mm.
593              
594             This function attempts to account for effects of projectile wobble (yaw instability) and the different effects
595             wobble has on different target materials. The bullet is assumed to stabilize eventually, the stabilization distance
596             depending on projectile mass. This feature is a work-in-progress, and should be taken with generous salt.
597              
598             This function is the original work of the author.
599              
600             ONLY VALID IN BALLISTIC DOMAIN.
601              
602             Not recommended for masses outside 55..450 grains range,
603              
604             Not recommended for velocities outside 1200..3500 feet per second.
605              
606             Not recommended for unjacketed lead projectiles.
607              
608             =over 4
609              
610             parameter: (float) penetrator mass (in grains)
611              
612             parameter: (float) penetrator velocity (in feet per second)
613              
614             parameter: (float) distance between muzzle and target (in feet)
615              
616             parameter: (float) penetrator diameter (in inches)
617              
618             parameter: (string) penetrator type, describing very approximately the general shape and composition of the projectile. Valid values are:
619              
620             =over 4
621              
622             * "hp": Hollowpoint, composed of thin brass lining over lead core.
623              
624             * "sp": Softpoint (exposed lead tip), composed of thin brass lining over lead core.
625              
626             * "bp": FMJ "ball", composed of thin brass lining over lead core.
627              
628             * "ms": Mild steel core, with ogival nose shape.
629              
630             * "sc": Hard steel core, with truncated-cone nose shape.
631              
632             * "hc": Synonym for "sc".
633              
634             * "tc": Tungsten-carbide core (not WHA), with truncated-cone nose shape.
635              
636             * "wc": Synonym for "tc".
637              
638             * "wha": Tungsten heavy alloy core (eg, 90% W / 7% Ni / 3% Fe), with truncated-cone nose shape.
639              
640             * "du": Depleted uranium alloy core (99.25% U / 0.75% Ti), with truncated-cone nose shape.
641              
642             The hash table mapping these type strings to their numeric penetration factors is available as %Physics::Ballistics::Terminal::Penetrator_Types_H, for ease of reference and modification.
643              
644             =back
645              
646             parameter: (OPTIONAL) (string) target material. Valid target materials are:
647              
648             =over 4
649              
650             * "pine": Soft, green pine wood.
651              
652             * "sand": Loose-packed, dry sand.
653              
654             * "brick": Typical firebrick, as often used in residential exterior wall construction.
655              
656             * "cinder": Cinderblock, as often used in inexpensive non-residential exterior wall construction.
657              
658             * "concrete": Reinforced concrete, poured-in-place.
659              
660             * "mild": Mild steel, as often used in civilian construction or automotive body manufacture.
661              
662             * "hard": Hardened steel of at least 250BHN, akin to RHA.
663              
664             =back
665              
666             returns: (float) estimated depth of penetration (in mm), rounded to the nearest tenth of a mm.
667              
668             =back
669              
670             =cut
671              
672             my $pc_exponents_hr = {
673             sand => 2.0,
674             pine => 2.2,
675             conc => 0.8,
676             brick => 0.55,
677             cind => 0.32,
678             mild => 0.30
679             };
680              
681             my $pc_k_hr = {
682             sand => 650**$pc_exponents_hr->{'sand'},
683             pine => 650**$pc_exponents_hr->{'pine'},
684             conc => 650**$pc_exponents_hr->{'conc'},
685             brick => 650**$pc_exponents_hr->{'brick'},
686             cind => 650**$pc_exponents_hr->{'cind'},
687             mild => 650**$pc_exponents_hr->{'mild'}
688             };
689              
690             sub stabilization_distance_meters {
691 31     31 0 68 my ($grain) = @_;
692 31         95 return int(($grain**0.333) * 37.8);
693             }
694              
695             sub penetration_curve_sand {
696 4     4 0 11 my ($dist_ft, $pen_typ, $grain) = @_;
697 4         11 my $dist_stable = stabilization_distance_meters($grain);
698 4         9 my $resistance_factor = 60;
699 4 100       13 return $resistance_factor if ($dist_ft >= $dist_stable);
700 1         4 my $e_sand = 2.7;
701 1         3 my $dist_limit = $dist_stable ** $e_sand;
702 1         6 my $wobble_penalty = 0.50 * (($dist_limit - $dist_ft**$e_sand)/$dist_limit)**0.75;
703 1         12 return $resistance_factor * (1-$wobble_penalty);
704             }
705              
706             sub penetration_curve_pine {
707 5     5 0 14 my ($dist_ft, $pen_typ, $grain) = @_;
708 5         13 my $dist_stable = stabilization_distance_meters($grain);
709 5         11 my $resistance_factor = 305; # was 803 -- TTK 2015-04-02
710 5 100       20 return $resistance_factor if ($dist_ft >= $dist_stable);
711 1         3 my $e_pine = 2.6;
712 1         7 my $dist_limit = $dist_stable ** $e_pine;
713 1         8 my $wobble_penalty = 0.75 * (($dist_limit - $dist_ft**$e_pine)/$dist_limit)**0.75;
714 1         5 return $resistance_factor * (1-$wobble_penalty);
715             }
716              
717             sub penetration_curve_concrete {
718 0     0 0 0 my ($dist_ft, $pen_typ, $grain) = @_;
719 0         0 my $dist_stable = stabilization_distance_meters($grain);
720 0         0 my $resistance_factor = 40;
721 0 0       0 return $resistance_factor if ($dist_ft >= $dist_stable);
722 0         0 my $e_concrete = 0.80;
723 0         0 my $dist_limit = $dist_stable ** $e_concrete;
724 0         0 my $wobble_penalty = 0.75 * (($dist_limit - $dist_ft**$e_concrete)/$dist_limit);
725 0         0 return $resistance_factor * (1-$wobble_penalty);
726             }
727              
728             sub penetration_curve_brick {
729 4     4 0 12 my ($dist_ft, $pen_typ, $grain) = @_;
730 4         11 my $dist_stable = stabilization_distance_meters($grain);
731 4         9 my $resistance_factor = 42;
732 4 100       16 return $resistance_factor if ($dist_ft >= $dist_stable);
733 1         3 my $e_brick = 0.55;
734 1         3 my $dist_limit = $dist_stable ** $e_brick;
735 1         5 my $wobble_penalty = 0.30 * (($dist_limit - $dist_ft**$e_brick)/$dist_limit);
736 1         5 return $resistance_factor * (1-$wobble_penalty);
737             }
738              
739             sub penetration_curve_cinder {
740 4     4 0 10 my ($dist_ft, $pen_typ, $grain) = @_;
741 4         12 my $dist_stable = stabilization_distance_meters($grain);
742 4         9 my $resistance_factor = 55; # was 157 -- TTK 2015-04-02
743 4 100       15 return $resistance_factor if ($dist_ft >= $dist_stable);
744 1         2 my $e_cinder = 0.55;
745 1         4 my $dist_limit = $dist_stable ** $e_cinder;
746 1         4 my $wobble_penalty = 0.25 * (($dist_limit - $dist_ft**$e_cinder)/$dist_limit);
747 1         4 return $resistance_factor * (1-$wobble_penalty);
748             }
749              
750             sub penetration_curve_mild_steel {
751 10     10 0 24 my ($dist_ft, $pen_typ, $grain) = @_;
752 10         28 my $dist_stable = stabilization_distance_meters($grain);
753 10         21 my $resistance_factor = 1.25;
754 10 100       32 return $resistance_factor if ($dist_ft >= $dist_stable);
755 3         8 my $e_mild = 0.82;
756 3         7 my $dist_limit = $dist_stable ** $e_mild;
757 3         10 my $wobble_penalty = 0.65 * (($dist_limit - $dist_ft**$e_mild)/$dist_limit);
758 3         11 return $resistance_factor * (1-$wobble_penalty);
759             }
760              
761             sub penetration_curve_hard_steel {
762 4     4 0 11 my ($dist_ft, $pen_typ, $grain) = @_;
763 4         10 my $dist_stable = stabilization_distance_meters($grain);
764 4         14 my $resistance_factor = 1.0;
765 4 100       14 return $resistance_factor if ($dist_ft >= $dist_stable);
766 1         3 my $e_hard = 0.91;
767 1         4 my $dist_limit = $dist_stable ** $e_hard;
768 1         5 my $wobble_penalty = 0.65 * (($dist_limit - $dist_ft**$e_hard)/$dist_limit);
769 1         4 return $resistance_factor * (1-$wobble_penalty);
770             }
771              
772             sub pc {
773 31     31 1 15815 my ($grain, $vel_fps, $dist_ft, $diam, $pen_typ, $target_material, $te) = @_;
774 31 50       106 die("mm = pc(grains, velocity_fps, distance_feet, diam_inches, [{hp,sp,bp,ms,hs,sc,wc,tc,wha,du},] [{pine, sand, brick, cinder, concrete, mild, hard},] [, Te])") unless (defined ($diam));
775 31 50       77 $te = 1 unless (defined ($te)); # thickness efficiency of target material, relative to normal case (eg, RHA)
776 31         223 my %curve_h = (
777             "pine" => \&penetration_curve_pine,
778             "sand" => \&penetration_curve_sand,
779             "brick" => \&penetration_curve_brick,
780             "cinder" => \&penetration_curve_cinder,
781             "concrete" => \&penetration_curve_concrete,
782             "mild" => \&penetration_curve_mild_steel,
783             "hard" => \&penetration_curve_hard_steel,
784             "rha" => \&penetration_curve_hard_steel
785             );
786 31         75 my $curve_cr = $curve_h{"hard"};
787 31 50       87 $curve_cr = $curve_h{$target_material} if (defined($curve_h{$target_material}));
788 31         151 my $KED = ($grain ** 1.29) * (($vel_fps/1000) ** 1.51) / ($diam ** 1.05); # energy density, sorta (fits empirical data)
789 31         53 my $pf = undef;
790             # $KED *= $pf if (defined($pf = $Physics::Ballistics::Terminal::Penetrator_Types_H{$pen_typ}));
791 31         50 my $material_independent_constant = 1750; # was 2785 -- TTK 2015-04-02
792 31         79 my $penetration_depth_mm = $KED *= $curve_cr->($dist_ft, $pen_typ, $grain) / $material_independent_constant;
793 31 50       103 $penetration_depth_mm *= $pf if (defined($pf = $Physics::Ballistics::Terminal::Penetrator_Types_H{$pen_typ}));
794 31         60 $penetration_depth_mm /= $te; # target material specific adjustment
795 31         66 $penetration_depth_mm = int (($penetration_depth_mm * 10) + 0.5) / 10; # this still gives more precision than it deserves
796 31         154 return $penetration_depth_mm;
797             }
798              
799             our %Penetrator_Types_H = (
800             'hp' => 0.775, # hollowpoint lead with gliding material jacket
801             'sp' => 0.850, # softpoint lead with gliding material jacket
802             'bp' => 1.050, # ballpoint lead with gliding material jacket
803             'ms' => 1.500, # mild steel core AP
804             'hs' => 1.800, # hard steel core AP
805             'sc' => 1.800, # hard steel core AP
806             'wc' => 2.700, # tungsten-carbide core AP (not WHA)
807             'tc' => 2.700, # tungsten-carbide core AP (not WHA)
808             'wha' => 2.900, # tungsten heavy alloy (WHA) core AP, guesstimate
809             'du' => 3.500 # uranium/titanium alloy core AP
810             );
811              
812             =head2 pc_simple (mass_grains, velocity_fps, diameter_inches, shape_str)
813              
814             Simple penetration calculator. Attempts to estimate how deeply a small-arms projectile
815             will penetrate into RHA. Optimized for projectiles near 7.5mm in diameter, works okay
816             for projectiles as small as 5mm or as large as 14mm.
817              
818             This function is the original work of the author.
819              
820             ONLY VALID IN BALLISTIC DOMAIN.
821              
822             Not recommended for masses outside 55..450 grains range,
823              
824             Not recommended for velocities outside 1200..3500 fps range,
825              
826             Not recommended for unjacketed lead projectiles.
827              
828             =over 4
829              
830             parameter: (float) penetrator mass (in grains)
831              
832             parameter: (float) penetrator velocity (in feet per second)
833              
834             parameter: (float) penetrator diameter (in inches)
835              
836             parameter: (string) penetrator type, describing very approximately the general shape and composition of the projectile. Valid values are:
837              
838             =over 4
839              
840             * "hp": Hollowpoint, composed of thin brass lining over lead core.
841              
842             * "sp": Softpoint (exposed lead tip), composed of thin brass lining over lead core.
843              
844             * "bp": FMJ "ball", composed of thin brass lining over lead core.
845              
846             * "ms": Mild steel core, with ogival nose shape.
847              
848             * "sc": Hard steel core, with truncated-cone nose shape.
849              
850             * "hc": Synonym for "sc".
851              
852             * "tc": Tungsten-carbide core (not WHA), with truncated-cone nose shape.
853              
854             * "wc": Synonym for "tc".
855              
856             * "wha": Tungsten heavy alloy core (eg, 90% W / 7% Ni / 3% Fe), with truncated-cone nose shape.
857              
858             * "du": Depleted uranium alloy core (99.25% U / 0.75% Ti), with truncated-cone nose shape.
859              
860             The hash table mapping these type strings to their numeric penetration factors is available as %Physics::Ballistics::Terminal::Penetrator_Types_H, for ease of reference and modification.
861              
862             =back
863              
864             parameter: (OPTIONAL) (string or float) thickness efficiency of target material (as ratio to RHA). Defaults to 1.0 (target is RHA).
865              
866             returns: (float) estimated depth of penetration into RHA (in mm), rounded over to the nearest tenth of a mm.
867              
868             =back
869              
870             =cut
871              
872             sub pc_simple {
873 5     5 1 2440 my ($grain, $fps, $diam, $typ, $te) = @_;
874 5 50       22 die("mm = pc(grains, velocity_fps, diam_inches, {hp,sp,bp,ms,hs,sc,wc,tc,wha,du} [, Te])") unless (defined ($diam));
875 5 50       12 $te = 1 unless (defined ($te)); # thickness efficiency of target material, relative to RHA
876 5         12 $fps /= 1000;
877 5         22 my $KED = ($grain ** 1.29) * ($fps ** 1.51) / ($diam ** 1.05); # energy density, sorta (fits empirical data)
878 5         10 my $pf = undef;
879 5 50       54 $KED *= $pf if (defined($pf = $Physics::Ballistics::Terminal::Penetrator_Types_H{$typ}));
880 5         25 my $RHA = $KED / 2785; # estimated depth of penetration of RHA, in mm
881 5         9 $RHA /= $te; # target material specific adjustment
882 5         13 $RHA = int (($RHA * 10) + 0.5) / 10; # this still gives more precision than it deserves
883 5         15 return $RHA;
884             }
885              
886              
887             =head2 hits_score (mass_grains, velocity_fps, diameter_inches)
888              
889             Computes a projectile's Hornady Index of Terminal Standards (H.I.T.S.) score, an
890             approximation of its lethality.
891              
892             Personally I think H.I.T.S. severely over-emphasizes bullet mass (the score is
893             proportional to the SQUARE of the bullet mass, times velocity, divided by bullet
894             sectional area). It is included here anyway because there are no really good
895             lethality models, and many big-game hunters like H.I.T.S. (and it is possible
896             that bullet mass really is that important when taking down very large animals).
897              
898             See also:
899              
900             L
901              
902             L
903              
904             L
905              
906             =over 4
907              
908             parameter: (float) projectile mass (in grains)
909              
910             parameter: (float) projectile velocity (in feet per second)
911              
912             parameter: (float) projectile diameter (in inches)
913              
914             returns: (integer) lethality (HITS score, qv table in L)
915              
916             =back
917              
918             =cut
919              
920             # HITS - Hornady Index of Terminal Standards formula:
921             # (Bullet weight grains)^2*(Bullet velocity fps)/(700000*Bullet diameter inches^2)
922             sub hits_score {
923 1     1 1 502 my ($mass_grains, $vel_fps, $diameter_inches) = @_;
924 1 50       5 die("index = hits_score(bullet_grains, bullet_fps, bullet_diameter_inches)") unless (defined($diameter_inches));
925 1         7 return int(($mass_grains**2) * $vel_fps / 700000 / ($diameter_inches**2) + 0.5)
926             }
927              
928              
929             # v = sigma(n1, n2, ...);
930             # Returns the standard deviation of its inputs. qv: http://en.wikipedia.org/wiki/Standard_deviation#Identities_and_mathematical_properties
931             sub sigma {
932 1     1 0 801 my $n = scalar(@_);
933 1         4 my $tot = 0;
934 1         2 my $sqs = 0;
935 1         2 my ($avg, $sig);
936 1         3 foreach my $term (@_) { $tot += $term; }
  4         8  
937 1         3 $avg = $tot / $n;
938 1         4 foreach my $term (@_) {
939 4         8 my $dif = abs($term - $avg);
940 4         10 $sqs += $dif**2;
941             }
942 1         4 $sig = ($sqs / $n)**0.5;
943 1         3 return $sig;
944             }
945              
946             # v = average(n1, n2, ...);
947             # Returns the simple arithmetic average of its inputs. qv: http://en.wikipedia.org/wiki/Arithmetic_mean
948             sub average {
949 1     1 0 521 my $n = scalar(@_);
950 1 50       6 return 0 if ($n < 1);
951 1         3 my $sum = 0;
952 1         4 foreach my $x (@_) { $sum += $x; }
  4         8  
953 1         4 return $sum / $n;
954             }
955              
956             # v = rndto(x, n);
957             # Round x over to n digits, appending sigfigs to the right of the decimal point as necessary when n < 0.
958             # rndto(1234.567, 2) == 1200
959             # rndto(1234.567, 1) == 1230
960             # rndto(1234.567, -1) == 1234.6
961             # rndto(1234.567, -2) == 1234.57
962             # rndto(1234, -2) == 1234.00
963             sub rndto {
964 5     5 0 3269 my ($x, $n) = @_;
965 5         9 my ($ret);
966 5         23 $ret = int(($x / (10**$n)) + 0.5) * (10**$n);
967 5 100       19 return $ret unless ($n < 0);
968 3         7 $n *= -1;
969 3 100       44 return $ret . ("0" x ($n - length($1))) if ($ret =~ /\.(\d+)/);
970 1         9 return "$ret." . ("0" x $n);
971             }
972              
973             sub r2d { # Convert radians to degrees
974 3     3 0 1449 return 360 * $_[0] / (2 * pi);
975             }
976              
977             sub d2r { # Convert degrees to radians
978 3     3 0 1412 return $_[0] * 2 * pi / 360;
979             }
980              
981             =head2 poncelet(diameter_mm, mass_grains, velocity_fps, target_shear_strength_psi, target_density)
982              
983             Jean-Victor Poncelet was one of the first to attempt mathematical models of
984             depth of penetration. His formula, developed in the 19th century, attempts
985             to predict the penetration of bullets into flesh-like materials. It is not
986             very good, failing to take into account such factors as bullet nose shape,
987             bullet tumbling within the target, and impacts with bone, horn, or cartilage.
988              
989             ONLY VALID IN BALLISTIC DOMAIN.
990              
991             =over 4
992              
993             parameter: (float) penetrator diameter (in mm)
994              
995             parameter: (float) penetrator mass (in grains)
996              
997             parameter: (float) penetrator velocity (in feet per second)
998              
999             parameter: (float) target material shearing strength (in PSI)
1000              
1001             parameter: (float) target density (in g/cc)
1002              
1003             returns: (int) depth of penetration (in mm)
1004              
1005             =back
1006              
1007             =cut
1008              
1009             # Old, not very useful formula by Poncelet for predicting depth of penetration into flesh.
1010             sub poncelet {
1011 1     1 1 498 my ($cal, $mass, $vel, $c0, $c1, $debug) = @_;
1012 1 50       5 die('usage: ponce (cal_mm, mass_grain, vel_fps, targ_shear_psi, targ_density) = pen_cm') unless(defined($c1));
1013             # c0 = Shearing strength
1014             # c1 = Specific density (water = 1 = 1 g/cc)
1015 1         4 $mass /= 15.43; # converting grains to grams
1016 1         3 $vel /= 3.28; # converting ft/s to m/s
1017 1         4 $c0 /= 145.04; # converting psi to MPa
1018 1 50       5 if ($debug) {
1019 0         0 print "# poncelet: mass g = $mass\n";
1020 0         0 print "# poncelet: vel m/s = $vel\n";
1021 0         0 print "# poncelet: shr MPa = $c0\n";
1022             }
1023 1         3 my $term1_1 = 2 * 3.141592 * $c1;
1024 1         3 my $term1_2 = $cal**2 / 4;
1025 1         4 my $term1 = $mass / ($term1_1 * $term1_2);
1026 1         5 my $term2_1 = ($c1 * $vel**2 + $c0) / $c0;
1027 1         5 my $term2 = log($term2_1);
1028 1         3 my $depth_cm = $term1 * $term2;
1029             # my $depth_cm = ($mass / (2 * $c1 * 3.141592 * $cal**2 / 4)) * log(($c1 * $vel**2 + $c0) / $c0);
1030 1 50       4 if ($debug) {
1031 0         0 print "# poncelet: term1_1 = 2 * 3.141592 * den $c1 = $term1_1\n";
1032 0         0 print "# poncelet: term1_2 = cal^2 $cal**2 / 4 = $term1_2\n";
1033 0         0 print "# poncelet: term1 = mass g $mass / (term1_1 * term1_2) = $term1\n";
1034 0         0 print "# poncelet: term2_1 = den $c1 * vel^2 $vel**2 + shear $c0) / shear $c0 = $term2_1\n";
1035 0         0 print "# poncelet: term2 = log(term2_1) = $term2\n";
1036 0         0 print "# poncelet: depth_cm = term1 * term2 = $depth_cm\n";
1037             }
1038 1         8 return int($depth_cm * 10 + 0.5); # converting cm to nearest mm, which is still way more precision than this deserves.
1039             }
1040              
1041             =head2 te2me (thickness_efficiency, density)
1042              
1043             Given the mass efficiency of a material, returns its thickness efficiency.
1044              
1045             See the description of me2te() for more explanation.
1046              
1047             =over 4
1048              
1049             parameter: (float) armor material thickness efficiency (unitless, factor relative to RHA)
1050              
1051             parameter: (float) armor material density (g/cc)
1052              
1053             returns: (float) armor material mass efficiency (unitless, factor relative to RHA)
1054              
1055             =back
1056              
1057             =cut
1058              
1059             sub te2me { # given thickness efficiency and density, returns mass efficiency
1060 1     1 1 6 my ($te, $den) = @_;
1061 1         8 return $te * 7.86 / $den;
1062             }
1063              
1064             =head2 lethality (grains, velocity_fps)
1065              
1066             Approximates the lethality of a projectile impacting a living creature.
1067              
1068             C and currently extremely simple.
1069              
1070             Its parameters and output are likely to change in incompatible ways in future releases.
1071              
1072             Note the caveats enumerated at L
1073              
1074             This function assumes nontrivial tissue penetration occurs. For the moment it is based on observations of a very rough correlation between velocity and mass and permanent tissue cavity volume.
1075              
1076             See also Fackler: L
1077              
1078             =over 4
1079              
1080             parameter: (integer) projectile weight (in grains)
1081              
1082             parameter: (integer) projectile velocity (in feet/second)
1083              
1084             returns: (float) lethality relative to 5.56x45mm at point blank range.
1085              
1086             =back
1087              
1088             =cut
1089              
1090             sub lethality { # given grains and fps, estimate lethality relative to 5.56x45mm by momentum method (simplest).
1091             # TODO: improve on this to take length, width, tumble rate, and fragmentation into effect (permanent cavity volume method).
1092 2     2 1 979 my ($grains, $fps) = @_;
1093 2 50 33     16 die("m = lethality(grains, feet_per_second)") unless(defined($grains) && defined($fps));
1094 2         5 my $baseline_momentum = 63 * 3100;
1095 2         5 my $posed_momentum = $grains * $fps;
1096 2         5 my $relative_momentum = $posed_momentum / $baseline_momentum;
1097 2         14 return int($relative_momentum * 100 + 0.5) / 100;
1098             }
1099              
1100             =head2 hv2bhn (hardness_vickers)
1101              
1102             Given a Vickers hardness rating, approximates the equivalent Brinell Hardness Number (via 10/3000 WC method).
1103              
1104             Vickers can be converted to other hardness ratings by first converting to BHN, and then converting from BHN to the desired hardness rating.
1105              
1106             =over 4
1107              
1108             parameter: (integer) Vickers Hardness rating
1109              
1110             returns: (float) Brinell Hardness Number (BHN)
1111              
1112             =back
1113              
1114             =cut
1115              
1116             sub hv2bhn { # very approximately converts hardness vickers to brinell hardness number (10/3000 WC method)
1117 3     3 1 1389 my ($hv) = @_;
1118 3 50       22 return undef if ($hv !~ /^(-?[0-9\.]+)/);
1119 3         10 $hv = $1;
1120 3 100       15 return ($hv * 0.92857) if ($hv < 701);
1121 2 50       8 return ($hv * 0.70588 + 156) if ($hv < 826);
1122 2 50       6 return ($hv * 0.56770 + 270) if ($hv < 851);
1123 2 100       12 return ($hv * 0.34417 + 460) if ($hv < 1001);
1124 1 50       5 return ($hv * 0.17210 + 632) if ($hv < 1201);
1125 1         6 return ($hv * 0.08605 + 735);
1126             }
1127              
1128             =head2 bhn2hv (brinell_hardness_number)
1129              
1130             Given a Brinell Hardness Number hardness rating (via 10/3000 WC method), approximates the equivalent Vickers Hardness rating.
1131              
1132             =over 4
1133              
1134             parameter: (integer) Brinell Hardness Number (BHN)
1135              
1136             returns: (float) Vickers Hardness rating
1137              
1138             =back
1139              
1140             =cut
1141              
1142             sub bhn2hv { # very approximately converts brinell hardness number (10/3000 WC method) to hardness vickers
1143 3     3 1 1408 my ($bhn) = @_;
1144 3 50       19 return undef if ($bhn !~ /^(-?[0-9\.]+)/);
1145 3         8 $bhn = $1;
1146 3 100       16 return (($bhn - 735) / 0.08605) if ($bhn > 838);
1147 2 50       7 return (($bhn - 632) / 0.17210) if ($bhn > 804);
1148 2 100       16 return (($bhn - 460) / 0.34417) if ($bhn > 752);
1149 1 50       11 return (($bhn - 270) / 0.56770) if ($bhn > 738);
1150 1 50       3 return (($bhn - 156) / 0.70588) if ($bhn > 650);
1151 1         6 return ($bhn / 0.92857);
1152             }
1153              
1154             =head2 hrc2bhn (rockwell_hardness_C)
1155              
1156             Given a Rockwell Hardness C rating, approximates the equivalent Brinell Hardness Number (via 10/3000 WC method).
1157              
1158             HRC can be converted to other hardness ratings by first converting to BHN, and then converting from BHN to the desired hardness rating.
1159              
1160             =over 4
1161              
1162             parameter: (integer) Rockwell Hardness C, valid ONLY in the range 15..65.
1163              
1164             returns: (float) Brinell Hardness Number (BHN)
1165              
1166             =back
1167              
1168             =cut
1169              
1170             sub hrc2bhn { # very approximately converts rockwell hardness C to BHN (10/3000 WC)
1171             # zzapp -- FIXME: Segment this curve to improve accuracy
1172             # actual calculated absolute
1173             # HRC BHN BHN error
1174             # 20 228 228.833 0.833
1175             # 30 286 288.475 2.475
1176             # 40 371 366.717 -4.283
1177             # 50 482 482.458 0.458
1178             # 60 657 654.6 -2.400
1179 5     5 1 1914 my ($hrc) = @_;
1180 5 100       21 return undef if ($hrc > 65); # error condition -- invalid range
1181 4 100       15 return undef if ($hrc < 15); # error condition -- invalid range
1182 3         15 my $bhn = 89.75 + (28.5125 * $hrc / 3) - (0.1905 * ($hrc**2)) + (0.00315 * ($hrc**3));
1183 3         15 return int(0.5 + $bhn); # eliminating illusion of accuracy
1184             }
1185              
1186             =head2 bhn2hrc (brinell_hardness_number)
1187              
1188             Given a Brinell Hardness Number hardness rating (via 10/3000 WC method), approximates the equivalent Rockwell Hardness C rating.
1189              
1190             Approximation is accurate to within 5% near the low end, 2% everywhere else.
1191              
1192             =over 4
1193              
1194             parameter: (integer) Brinell Hardness Number (BHN), valid ONLY in the range 200..770
1195              
1196             returns: (float) Rockwell Hardness C rating
1197              
1198             =back
1199              
1200             =cut
1201              
1202             sub bhn2hrc { # very approximately converts BHN (10/3000 WC) to rockwell hardness C
1203             # reasonably pleased with the accuracy of this -- error is up to 5% near the low end, but less than 2% otherwise.
1204 5     5 1 1458 my ($bhn) = @_;
1205 5 100       20 return undef if ($bhn < 200); # out of bounds
1206 4 100       14 return undef if ($bhn > 770); # out of bounds
1207 3         18 my $hrc = -3 + ($bhn / 14) + ($bhn / 74)**2 - ($bhn / 168)**3;
1208 3 50       10 $hrc -= 1 if ($bhn <= 215);
1209 3 50 66     13 $hrc += 2 if ($bhn <= 436 && $bhn >= 254);
1210 3 50 66     12 $hrc += 1 if ($bhn <= 402 && $bhn >= 279);
1211 3 100 66     17 $hrc -= 2 if ($bhn <= 710 && $bhn >= 553);
1212 3         13 return int($hrc);
1213             }
1214              
1215             =head2 psi2bhn (pounds_per_square_inch)
1216              
1217             Given the ultimate tensile strength of a steel formulation in PSI, approximates the equivalent Brinell Hardness Number (via 10/3000 WC method).
1218              
1219             Steel UTS PSI can be converted to other hardness ratings by first converting to BHN, and then converting from BHN to the desired hardness rating.
1220              
1221             Approximation is accurate to within 2%.
1222              
1223             See also:
1224              
1225             L
1226              
1227             L
1228              
1229             =over 4
1230              
1231             parameter: (integer) Pounds per square inch
1232              
1233             returns: (float) Brinell Hardness Number (BHN)
1234              
1235             =back
1236              
1237             =cut
1238              
1239             sub psi2bhn { # very approximately converts steel uts psi to brinell hardness number
1240             # source: http://www.monachos.gr/en/resources/hardness_conversion.asp
1241             # qv alt: http://mdmetric.com/tech/hardness.htm
1242             # this table provides approx 2% error in conversion
1243 3     3 1 972 my ($psi) = @_;
1244 3 50       27 return undef unless ($psi =~ /^(-?[0-9\.]+)/);
1245 3         11 $psi = $1;
1246 3         8 $psi /= 1000;
1247 3 100       14 return ($psi / 0.500) if ($psi < 127);
1248 2 50       7 return ($psi / 0.497) if ($psi < 140);
1249 2 50       6 return ($psi / 0.487) if ($psi < 153);
1250 2 50       5 return ($psi / 0.485) if ($psi < 166);
1251 2 50       7 return ($psi / 0.486) if ($psi < 179);
1252 2 50       6 return ($psi / 0.484) if ($psi < 192);
1253 2 50       6 return ($psi / 0.483) if ($psi < 205);
1254 2 50       6 return ($psi / 0.484) if ($psi < 218);
1255 2 100       10 return ($psi / 0.489) if ($psi < 231);
1256 1 50       5 return ($psi / 0.493) if ($psi < 257);
1257 1 50       3 return ($psi / 0.494) if ($psi < 270);
1258 1 50       17 return ($psi / 0.495) if ($psi < 283);
1259 1 50       5 return ($psi / 0.497) if ($psi < 296);
1260 1 50       3 return ($psi / 0.498) if ($psi < 322);
1261 1 50       5 return ($psi / 0.499) if ($psi < 335);
1262 1 50       3 return ($psi / 0.500) if ($psi < 348);
1263 1 50       5 return ($psi / 0.501) if ($psi < 374);
1264 1 50       9 return ($psi / 0.503) if ($psi < 387);
1265 0 0       0 return ($psi / 0.504) if ($psi < 400);
1266 0 0       0 return ($psi / 0.506) if ($psi < 426);
1267 0 0       0 return ($psi / 0.507) if ($psi < 439);
1268 0 0       0 return ($psi / 0.510) if ($psi < 452);
1269 0 0       0 return ($psi / 0.509) if ($psi < 478);
1270 0 0       0 return ($psi / 0.511) if ($psi < 491);
1271 0 0       0 return ($psi / 0.512) if ($psi < 504);
1272 0 0       0 return ($psi / 0.513) if ($psi < 530);
1273 0 0       0 return ($psi / 0.514) if ($psi < 595);
1274 0 0       0 return ($psi / 0.515) if ($psi < 621);
1275 0 0       0 return ($psi / 0.516) if ($psi < 634);
1276 0         0 return ($psi / 0.517);
1277             }
1278              
1279             =head2 bhn2psi (brinell_hardness_number)
1280              
1281             Given a Brinell Hardness Number hardness rating (via 10/3000 WC method), approximates the equivalent steel ultimate tensile strength in pounds per square inch.
1282              
1283             Approximation is accurate to within 2%.
1284              
1285             See also:
1286              
1287             L
1288              
1289             L
1290              
1291             =over 4
1292              
1293             parameter: (integer) Brinell Hardness Number (BHN)
1294              
1295             returns: (float) Steel ultimate tensile strength (psi)
1296              
1297             =back
1298              
1299             =cut
1300              
1301             sub bhn2psi { # very approximately converts brinell hardness number to steel uts psi
1302             # source: http://www.monachos.gr/en/resources/hardness_conversion.asp
1303             # qv alt: http://mdmetric.com/tech/hardness.htm
1304             # this table provides 2% or less error in conversion
1305 3     3 1 1444 my ($bhn) = @_;
1306 3 50       21 return undef unless ($bhn =~ /^(-?[0-9\.]+)/);
1307 3         7 $bhn = $1;
1308 3 100       17 return ($bhn * 500) if ($bhn < 127);
1309 2 50       6 return ($bhn * 496) if ($bhn < 131);
1310 2 50       8 return ($bhn * 489) if ($bhn < 138);
1311 2 50       11 return ($bhn * 497) if ($bhn < 144);
1312 2 50       5 return ($bhn * 490) if ($bhn < 150);
1313 2 50       6 return ($bhn * 487) if ($bhn < 157);
1314 2 50       6 return ($bhn * 485) if ($bhn < 168);
1315 2 50       5 return ($bhn * 488) if ($bhn < 171);
1316 2 50       5 return ($bhn * 489) if ($bhn < 175);
1317 2 50       10 return ($bhn * 486) if ($bhn < 184);
1318 2 50       6 return ($bhn * 481) if ($bhn < 188);
1319 2 50       6 return ($bhn * 484) if ($bhn < 193);
1320 2 50       6 return ($bhn * 482) if ($bhn < 198);
1321 2 50       6 return ($bhn * 488) if ($bhn < 202);
1322 2 50       6 return ($bhn * 483) if ($bhn < 208);
1323 2 50       6 return ($bhn * 481) if ($bhn < 213);
1324 2 50       4 return ($bhn * 484) if ($bhn < 218);
1325 2 50       6 return ($bhn * 485) if ($bhn < 230);
1326 2 50       5 return ($bhn * 489) if ($bhn < 236);
1327 2 50       9 return ($bhn * 490) if ($bhn < 242);
1328 2 50       5 return ($bhn * 492) if ($bhn < 249);
1329 2 50       8 return ($bhn * 494) if ($bhn < 256);
1330 2 50       6 return ($bhn * 492) if ($bhn < 263);
1331 2 50       8 return ($bhn * 494) if ($bhn < 270);
1332 2 50       6 return ($bhn * 495) if ($bhn < 294);
1333 2 50       5 return ($bhn * 497) if ($bhn < 303);
1334 2 50       5 return ($bhn * 498) if ($bhn < 322);
1335 2 50       6 return ($bhn * 501) if ($bhn < 332);
1336 2 50       5 return ($bhn * 499) if ($bhn < 342);
1337 2 100       9 return ($bhn * 500) if ($bhn < 353);
1338 1 50       9 return ($bhn * 501) if ($bhn < 376);
1339 1 50       15 return ($bhn * 503) if ($bhn < 389);
1340 1 50       4 return ($bhn * 504) if ($bhn < 402);
1341 1 50       4 return ($bhn * 506) if ($bhn < 430);
1342 1 50       4 return ($bhn * 507) if ($bhn < 445);
1343 1 50       4 return ($bhn * 510) if ($bhn < 462);
1344 1 50       3 return ($bhn * 509) if ($bhn < 478);
1345 1 50       3 return ($bhn * 511) if ($bhn < 496);
1346 1 50       4 return ($bhn * 512) if ($bhn < 515);
1347 1 50       5 return ($bhn * 513) if ($bhn < 535);
1348 1 50       3 return ($bhn * 514) if ($bhn < 602);
1349 1 50       4 return ($bhn * 515) if ($bhn < 628);
1350 1 50       4 return ($bhn * 514) if ($bhn < 631);
1351 1 50       8 return ($bhn * 516) if ($bhn < 639);
1352 1         4 return ($bhn * 517);
1353             }
1354              
1355             1;
1356              
1357             =head1 TODO
1358              
1359             The pc function needs a lot of improvement.
1360              
1361             Need a pc function for larger penetrators (for the ballistic domain, as anderson and odermatt suffices for hypervelocity domain).
1362              
1363             The stabilization_distance_meters function should take projectile composition into account.
1364              
1365             To be really useful the lethality function needs to take wobble, fragmentation and permanent wound cavity volume into account (per Fackler).
1366              
1367             The hardness unit conversion functions should be based on Vickers, not Brinell, as Vickers has the wider valid range.
1368              
1369             =cut