699 lines
25 KiB
C++
699 lines
25 KiB
C++
// Copyright 2016 Mookie. All Rights Reserved.
|
||
|
||
#include "EBMathematicalBallistics.h"
|
||
#include "EBUnitConversions.h"
|
||
#include "Engine/Engine.h"
|
||
#include "Math/UnrealMathUtility.h"
|
||
|
||
float UEBMathematicalBallistics::CalculatePenetrationDepth(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
const FMathematicalMaterialProperties& MaterialProps,
|
||
float VelocityMPS,
|
||
float ImpactAngleDegrees)
|
||
{
|
||
// SANITY CHECK: Validate input parameters
|
||
if (VelocityMPS <= 0.0f || !FMath::IsFinite(VelocityMPS) || VelocityMPS > 10000.0f) // Max 10 km/s
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid velocity %.3f m/s for penetration calculation"), VelocityMPS);
|
||
return 0.0f;
|
||
}
|
||
|
||
if (!FMath::IsFinite(ImpactAngleDegrees) || ImpactAngleDegrees < 0.0f || ImpactAngleDegrees > 90.0f)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid impact angle %.1f degrees, clamping to valid range"), ImpactAngleDegrees);
|
||
ImpactAngleDegrees = FMath::Clamp(ImpactAngleDegrees, 0.0f, 90.0f);
|
||
}
|
||
|
||
// SANITY CHECK: Validate material properties
|
||
if (MaterialProps.DensityGPerCm3 <= 0.0f || !FMath::IsFinite(MaterialProps.DensityGPerCm3))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid material density %.3f g/cm³"), MaterialProps.DensityGPerCm3);
|
||
return 0.0f;
|
||
}
|
||
|
||
if (MaterialProps.YieldStrengthMPa <= 0.0f || !FMath::IsFinite(MaterialProps.YieldStrengthMPa))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid yield strength %.1f MPa"), MaterialProps.YieldStrengthMPa);
|
||
return 0.0f;
|
||
}
|
||
|
||
// Calculate kinetic energy in joules
|
||
float KineticEnergy = CalculateKineticEnergy(BulletProps, VelocityMPS);
|
||
|
||
// SANITY CHECK: Validate kinetic energy
|
||
if (KineticEnergy <= 0.0f || !FMath::IsFinite(KineticEnergy))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid kinetic energy %.3f J"), KineticEnergy);
|
||
return 0.0f;
|
||
}
|
||
|
||
// Calculate sectional density in proper SI units (kg/m²)
|
||
float SectionalDensity = BulletProps.GetSectionalDensityKgPerM2();
|
||
|
||
// SANITY CHECK: Validate sectional density
|
||
if (SectionalDensity <= 0.0f || !FMath::IsFinite(SectionalDensity))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid sectional density %.6f kg/m²"), SectionalDensity);
|
||
return 0.0f;
|
||
}
|
||
|
||
// Calculate hardness ratio
|
||
float HardnessRatio = CalculateHardnessRatio(BulletProps.BulletHardness, MaterialProps.MaterialHardness);
|
||
|
||
// Calculate angle factor (normal impact = 1.0, grazing = 0.0)
|
||
float AngleFactor = CalculateAngleFactor(ImpactAngleDegrees);
|
||
|
||
// Calculate shape factor based on bullet type
|
||
float ShapeFactor = CalculateShapeFactorFromBulletType(BulletProps.BulletType);
|
||
|
||
// Modified Taylor-Hopkinson equation for penetration depth
|
||
// P = (K * E * SD * HF * AF * SF) / (ρ * σ)
|
||
// Where: P = penetration depth (cm), K = constant, E = kinetic energy (J), SD = sectional density (kg/m²)
|
||
// HF = hardness factor, AF = angle factor, SF = shape factor, ρ = density (g/cm³), σ = yield strength (MPa)
|
||
|
||
// Unit conversion factors for proper dimensional analysis:
|
||
// KineticEnergy is in Joules (kg⋅m²/s²)
|
||
// SectionalDensity is in kg/m²
|
||
// DensityGPerCm3 needs conversion: g/cm³ = 1000 kg/m³
|
||
// YieldStrengthMPa is in MPa = 10⁶ Pa = 10⁶ N/m²
|
||
|
||
float DensityKgPerM3 = FEBUnitConversions::GPerCM3ToKGPerM3(MaterialProps.DensityGPerCm3);
|
||
float YieldStrengthPa = FEBUnitConversions::MPaToPa(MaterialProps.YieldStrengthMPa);
|
||
|
||
// SANITY CHECK: Validate converted values
|
||
if (DensityKgPerM3 <= 0.0f || !FMath::IsFinite(DensityKgPerM3) ||
|
||
YieldStrengthPa <= 0.0f || !FMath::IsFinite(YieldStrengthPa))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid converted material properties"));
|
||
return 0.0f;
|
||
}
|
||
|
||
// Empirical constant adjusted for proper units (results in meters)
|
||
float Constant = 2.0f; // Adjusted for dimensional consistency
|
||
|
||
// SANITY CHECK: Prevent division by zero and validate all factors
|
||
float Denominator = DensityKgPerM3 * YieldStrengthPa;
|
||
if (Denominator <= 0.0f || !FMath::IsFinite(Denominator))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid denominator in penetration calculation"));
|
||
return 0.0f;
|
||
}
|
||
|
||
float Numerator = Constant * KineticEnergy * SectionalDensity * HardnessRatio * AngleFactor * ShapeFactor;
|
||
if (!FMath::IsFinite(Numerator))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid numerator in penetration calculation"));
|
||
return 0.0f;
|
||
}
|
||
|
||
float PenetrationM = Numerator / Denominator;
|
||
|
||
// SANITY CHECK: Validate result before conversion
|
||
if (!FMath::IsFinite(PenetrationM) || PenetrationM < 0.0f)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid penetration result %.6f m"), PenetrationM);
|
||
return 0.0f;
|
||
}
|
||
|
||
// Convert from meters to centimeters for consistency with Unreal Engine units
|
||
float PenetrationCm = FEBUnitConversions::MetersToCM(PenetrationM);
|
||
|
||
// SANITY CHECK: Clamp to reasonable maximum (10 meters = 1000 cm)
|
||
const float MaxPenetrationCm = 1000.0f;
|
||
if (PenetrationCm > MaxPenetrationCm)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Penetration %.1f cm exceeds maximum, clamping to %.1f cm"), PenetrationCm, MaxPenetrationCm);
|
||
PenetrationCm = MaxPenetrationCm;
|
||
}
|
||
|
||
return FMath::Max(0.0f, PenetrationCm);
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateResidualVelocity(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
const FMathematicalMaterialProperties& MaterialProps,
|
||
float VelocityMPS,
|
||
float ThicknessCM,
|
||
float ImpactAngleDegrees)
|
||
{
|
||
// SANITY CHECK: Validate input parameters
|
||
if (VelocityMPS <= 0.0f || !FMath::IsFinite(VelocityMPS))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid velocity %.3f m/s for residual velocity calculation"), VelocityMPS);
|
||
return 0.0f;
|
||
}
|
||
|
||
if (ThicknessCM <= 0.0f || !FMath::IsFinite(ThicknessCM))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid thickness %.3f cm"), ThicknessCM);
|
||
return 0.0f;
|
||
}
|
||
|
||
// Calculate ballistic limit velocity
|
||
float BallisticLimit = CalculateRechtIpsonVelocity(BulletProps, MaterialProps, ThicknessCM);
|
||
|
||
// SANITY CHECK: Validate ballistic limit
|
||
if (!FMath::IsFinite(BallisticLimit) || BallisticLimit < 0.0f)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid ballistic limit %.3f m/s"), BallisticLimit);
|
||
return 0.0f;
|
||
}
|
||
|
||
// Adjust for impact angle
|
||
float AngleFactor = CalculateAngleFactor(ImpactAngleDegrees);
|
||
|
||
// SANITY CHECK: Prevent division by zero
|
||
if (AngleFactor <= 0.001f)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Angle factor too small %.6f, using minimum"), AngleFactor);
|
||
AngleFactor = 0.001f;
|
||
}
|
||
|
||
BallisticLimit = BallisticLimit / AngleFactor;
|
||
|
||
// SANITY CHECK: Validate adjusted ballistic limit
|
||
if (!FMath::IsFinite(BallisticLimit))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid adjusted ballistic limit"));
|
||
return 0.0f;
|
||
}
|
||
|
||
// If impact velocity is below ballistic limit, no penetration
|
||
if (VelocityMPS <= BallisticLimit)
|
||
{
|
||
return 0.0f;
|
||
}
|
||
|
||
// SANITY CHECK: Ensure square root argument is positive
|
||
float VelocitySquared = VelocityMPS * VelocityMPS;
|
||
float BallisticLimitSquared = BallisticLimit * BallisticLimit;
|
||
float SquareRootArgument = VelocitySquared - BallisticLimitSquared;
|
||
|
||
if (SquareRootArgument < 0.0f)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Negative square root argument %.6f, velocity %.3f < ballistic limit %.3f"),
|
||
SquareRootArgument, VelocityMPS, BallisticLimit);
|
||
return 0.0f;
|
||
}
|
||
|
||
// Calculate residual velocity using Recht-Ipson equation
|
||
// Vr = sqrt(V^2 - Vbl^2)
|
||
float ResidualVelocity = FMath::Sqrt(SquareRootArgument);
|
||
|
||
// SANITY CHECK: Validate residual velocity before applying absorption
|
||
if (!FMath::IsFinite(ResidualVelocity))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid residual velocity calculated"));
|
||
return 0.0f;
|
||
}
|
||
|
||
// SANITY CHECK: Validate energy absorption coefficient
|
||
float EnergyAbsorption = FMath::Clamp(MaterialProps.EnergyAbsorptionCoefficient, 0.0f, 0.99f);
|
||
if (EnergyAbsorption != MaterialProps.EnergyAbsorptionCoefficient)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Energy absorption coefficient %.3f clamped to %.3f"),
|
||
MaterialProps.EnergyAbsorptionCoefficient, EnergyAbsorption);
|
||
}
|
||
|
||
// Apply energy absorption factor
|
||
ResidualVelocity *= (1.0f - EnergyAbsorption);
|
||
|
||
return FMath::Max(0.0f, ResidualVelocity);
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateRicochetProbability(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
const FMathematicalMaterialProperties& MaterialProps,
|
||
float VelocityMPS,
|
||
float ImpactAngleDegrees)
|
||
{
|
||
// Calculate critical ricochet angle
|
||
float CriticalAngle = CalculateCriticalRicochetAngle(BulletProps, MaterialProps, VelocityMPS);
|
||
|
||
// If impact angle is greater than critical angle, no ricochet
|
||
if (ImpactAngleDegrees > CriticalAngle)
|
||
{
|
||
return 0.0f;
|
||
}
|
||
|
||
// Calculate ricochet probability based on angle and material properties
|
||
float AngleRatio = ImpactAngleDegrees / CriticalAngle;
|
||
float BaseRicochetProbability = 1.0f - FMath::Pow(AngleRatio, 2.0f);
|
||
|
||
// Adjust for hardness ratio
|
||
float HardnessRatio = CalculateHardnessRatio(BulletProps.BulletHardness, MaterialProps.MaterialHardness);
|
||
float HardnessAdjustment = FMath::Clamp(HardnessRatio, 0.1f, 2.0f);
|
||
|
||
// Adjust for velocity (higher velocity reduces ricochet probability)
|
||
float VelocityFactor = CalculateVelocityFactor(VelocityMPS, 500.0f);
|
||
|
||
float RicochetProbability = BaseRicochetProbability * HardnessAdjustment * VelocityFactor;
|
||
|
||
return FMath::Clamp(RicochetProbability, 0.0f, 1.0f);
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateBulletExpansion(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
const FMathematicalMaterialProperties& MaterialProps,
|
||
float VelocityMPS)
|
||
{
|
||
// Convert velocity to FPS for comparison with expansion threshold
|
||
float VelocityFPS = ConvertMPStoFPS(VelocityMPS);
|
||
|
||
// No expansion if velocity is below threshold
|
||
if (VelocityFPS < BulletProps.ExpansionVelocityThreshold)
|
||
{
|
||
return 1.0f; // No expansion
|
||
}
|
||
|
||
// Calculate expansion based on velocity and bullet type
|
||
float VelocityRatio = VelocityFPS / BulletProps.ExpansionVelocityThreshold;
|
||
float ExpansionFactor = 1.0f;
|
||
|
||
switch (BulletProps.BulletType)
|
||
{
|
||
case EBulletType::BT_HollowPoint:
|
||
ExpansionFactor = FMath::Lerp(1.0f, BulletProps.MaxExpansionMultiplier, FMath::Clamp(VelocityRatio - 1.0f, 0.0f, 1.0f));
|
||
break;
|
||
case EBulletType::BT_SoftPoint:
|
||
ExpansionFactor = FMath::Lerp(1.0f, BulletProps.MaxExpansionMultiplier * 0.8f, FMath::Clamp(VelocityRatio - 1.0f, 0.0f, 1.0f));
|
||
break;
|
||
case EBulletType::BT_Frangible:
|
||
ExpansionFactor = FMath::Lerp(1.0f, BulletProps.MaxExpansionMultiplier * 1.2f, FMath::Clamp(VelocityRatio - 1.0f, 0.0f, 1.0f));
|
||
break;
|
||
case EBulletType::BT_FullMetalJacket:
|
||
case EBulletType::BT_ArmorPiercing:
|
||
ExpansionFactor = 1.0f; // No expansion
|
||
break;
|
||
default:
|
||
ExpansionFactor = FMath::Lerp(1.0f, BulletProps.MaxExpansionMultiplier * 0.6f, FMath::Clamp(VelocityRatio - 1.0f, 0.0f, 1.0f));
|
||
break;
|
||
}
|
||
|
||
return ExpansionFactor;
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateKineticEnergy(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
float VelocityMPS)
|
||
{
|
||
// KE = 0.5 * m * v^2
|
||
float MassKg = BulletProps.GetMassKg();
|
||
return 0.5f * MassKg * VelocityMPS * VelocityMPS;
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateMomentum(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
float VelocityMPS)
|
||
{
|
||
// p = m * v
|
||
float MassKg = BulletProps.GetMassKg();
|
||
return MassKg * VelocityMPS;
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateDragCoefficient(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
float MachNumber)
|
||
{
|
||
// Calculate drag coefficient from ballistic coefficient
|
||
// Standard drag coefficient for G1 projectile at given Mach number
|
||
float StandardDrag = 0.5f; // Simplified - normally would use complex curve
|
||
|
||
// Adjust for actual ballistic coefficient
|
||
float BC = BulletProps.UseG7Model ? BulletProps.BallisticCoefficientG7 : BulletProps.BallisticCoefficientG1;
|
||
|
||
// CD = Standard_CD / BC
|
||
return StandardDrag / BC;
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateTaylorHopkinsonLimit(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
const FMathematicalMaterialProperties& MaterialProps)
|
||
{
|
||
// Taylor-Hopkinson limit: t = K * ρ * σ / (SD * V^2)
|
||
// Rearranged to solve for limiting thickness
|
||
float SectionalDensity = BulletProps.GetSectionalDensityKgPerM2();
|
||
float Constant = 0.001f; // Empirical constant
|
||
|
||
// Convert to proper units: result in cm
|
||
float DensityKgPerM3 = MaterialProps.DensityGPerCm3 * 1000.0f;
|
||
float YieldStrengthPa = MaterialProps.YieldStrengthMPa * 1000000.0f;
|
||
|
||
float ThicknessM = Constant * DensityKgPerM3 * YieldStrengthPa / SectionalDensity;
|
||
return ThicknessM * 100.0f; // Convert to cm
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateRechtIpsonVelocity(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
const FMathematicalMaterialProperties& MaterialProps,
|
||
float ThicknessCM)
|
||
{
|
||
// SANITY CHECK: Validate input parameters
|
||
if (ThicknessCM <= 0.0f || !FMath::IsFinite(ThicknessCM))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid thickness %.3f cm for Recht-Ipson calculation"), ThicknessCM);
|
||
return 0.0f;
|
||
}
|
||
|
||
if (MaterialProps.YieldStrengthMPa <= 0.0f || !FMath::IsFinite(MaterialProps.YieldStrengthMPa))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid yield strength %.1f MPa"), MaterialProps.YieldStrengthMPa);
|
||
return 0.0f;
|
||
}
|
||
|
||
// Recht-Ipson equation for ballistic limit velocity
|
||
// Vbl = sqrt(k * σ * t / (ρ * A))
|
||
// Where k is a constant, σ is yield strength, t is thickness, ρ is bullet density, A is cross-sectional area
|
||
|
||
float Constant = 2.0f; // Empirical constant
|
||
|
||
// Convert all units to SI for calculation
|
||
float CrossSectionAreaM2 = BulletProps.GetCrossSectionCm2() * 0.0001f; // cm² to m²
|
||
float ThicknessM = ThicknessCM * 0.01f; // cm to m
|
||
float YieldStrengthPa = MaterialProps.YieldStrengthMPa * 1000000.0f; // MPa to Pa
|
||
|
||
// SANITY CHECK: Validate cross-sectional area
|
||
if (CrossSectionAreaM2 <= 0.0f || !FMath::IsFinite(CrossSectionAreaM2))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid cross-sectional area %.8f m²"), CrossSectionAreaM2);
|
||
return 0.0f;
|
||
}
|
||
|
||
// Calculate bullet density (kg/m³)
|
||
float BulletVolumeM3 = CrossSectionAreaM2 * BulletProps.LengthInches * 0.0254f; // Convert length to meters
|
||
|
||
// SANITY CHECK: Validate bullet volume
|
||
if (BulletVolumeM3 <= 0.0f || !FMath::IsFinite(BulletVolumeM3))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid bullet volume %.8f m³"), BulletVolumeM3);
|
||
return 0.0f;
|
||
}
|
||
|
||
float BulletMassKg = BulletProps.GetMassKg();
|
||
if (BulletMassKg <= 0.0f || !FMath::IsFinite(BulletMassKg))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid bullet mass %.6f kg"), BulletMassKg);
|
||
return 0.0f;
|
||
}
|
||
|
||
float BulletDensityKgPerM3 = BulletMassKg / BulletVolumeM3;
|
||
|
||
// SANITY CHECK: Validate bullet density
|
||
if (BulletDensityKgPerM3 <= 0.0f || !FMath::IsFinite(BulletDensityKgPerM3))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid bullet density %.3f kg/m³"), BulletDensityKgPerM3);
|
||
return 0.0f;
|
||
}
|
||
|
||
// SANITY CHECK: Calculate and validate square root argument
|
||
float Numerator = Constant * YieldStrengthPa * ThicknessM;
|
||
float Denominator = BulletDensityKgPerM3 * CrossSectionAreaM2;
|
||
|
||
if (Denominator <= 0.0f || !FMath::IsFinite(Denominator))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid denominator %.6f in Recht-Ipson equation"), Denominator);
|
||
return 0.0f;
|
||
}
|
||
|
||
float SquareRootArgument = Numerator / Denominator;
|
||
|
||
if (SquareRootArgument < 0.0f || !FMath::IsFinite(SquareRootArgument))
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid square root argument %.6f in Recht-Ipson equation"), SquareRootArgument);
|
||
return 0.0f;
|
||
}
|
||
|
||
// Calculate ballistic limit velocity in m/s
|
||
float BallisticLimitMPS = FMath::Sqrt(SquareRootArgument);
|
||
|
||
// SANITY CHECK: Validate final result
|
||
if (!FMath::IsFinite(BallisticLimitMPS) || BallisticLimitMPS < 0.0f)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid ballistic limit velocity %.3f m/s"), BallisticLimitMPS);
|
||
return 0.0f;
|
||
}
|
||
|
||
// SANITY CHECK: Clamp to reasonable maximum (5 km/s)
|
||
const float MaxBallisticLimit = 5000.0f;
|
||
if (BallisticLimitMPS > MaxBallisticLimit)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Ballistic limit %.1f m/s exceeds maximum, clamping to %.1f m/s"),
|
||
BallisticLimitMPS, MaxBallisticLimit);
|
||
BallisticLimitMPS = MaxBallisticLimit;
|
||
}
|
||
|
||
return BallisticLimitMPS;
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateCriticalRicochetAngle(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
const FMathematicalMaterialProperties& MaterialProps,
|
||
float VelocityMPS)
|
||
{
|
||
// Calculate critical ricochet angle using empirical formula
|
||
// Critical angle decreases with harder bullets and increases with harder targets
|
||
|
||
float HardnessRatio = CalculateHardnessRatio(BulletProps.BulletHardness, MaterialProps.MaterialHardness);
|
||
float VelocityFactor = CalculateVelocityFactor(VelocityMPS, 300.0f);
|
||
|
||
// Base critical angle (degrees)
|
||
float BaseCriticalAngle = 20.0f;
|
||
|
||
// Adjust for hardness and velocity
|
||
float CriticalAngle = BaseCriticalAngle * HardnessRatio * VelocityFactor;
|
||
|
||
return FMath::Clamp(CriticalAngle, 5.0f, 45.0f);
|
||
}
|
||
|
||
// Helper function implementations
|
||
float UEBMathematicalBallistics::CalculateHardnessRatio(float BulletHardness, float MaterialHardness)
|
||
{
|
||
// SANITY CHECK: Validate hardness values
|
||
if (!FMath::IsFinite(BulletHardness) || BulletHardness < 0.0f)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid bullet hardness %.3f, using default 15.0"), BulletHardness);
|
||
BulletHardness = 15.0f; // Default bullet hardness
|
||
}
|
||
|
||
if (!FMath::IsFinite(MaterialHardness) || MaterialHardness <= 0.0f)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid material hardness %.3f, using default 50.0"), MaterialHardness);
|
||
MaterialHardness = 50.0f; // Default material hardness
|
||
}
|
||
|
||
// Hardness ratio affects penetration and ricochet
|
||
float HardnessRatio = BulletHardness / MaterialHardness;
|
||
|
||
// SANITY CHECK: Ensure reasonable bounds
|
||
return FMath::Clamp(HardnessRatio, 0.1f, 10.0f);
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateVelocityFactor(float Velocity, float ThresholdVelocity)
|
||
{
|
||
// SANITY CHECK: Validate input values
|
||
if (!FMath::IsFinite(Velocity) || Velocity < 0.0f)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid velocity %.3f for velocity factor"), Velocity);
|
||
return 0.1f; // Minimum factor
|
||
}
|
||
|
||
if (!FMath::IsFinite(ThresholdVelocity) || ThresholdVelocity <= 0.0f)
|
||
{
|
||
UE_LOG(LogTemp, Warning, TEXT("EBMathematicalBallistics: Invalid threshold velocity %.3f, using default 300.0"), ThresholdVelocity);
|
||
ThresholdVelocity = 300.0f; // Default threshold
|
||
}
|
||
|
||
// Velocity factor for various calculations
|
||
float VelocityFactor = Velocity / ThresholdVelocity;
|
||
return FMath::Clamp(VelocityFactor, 0.1f, 5.0f);
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateAngleFactor(float ImpactAngleDegrees)
|
||
{
|
||
// Convert to radians and calculate cosine (normal impact = 1.0, grazing = 0.0)
|
||
float AngleRadians = FMath::DegreesToRadians(ImpactAngleDegrees);
|
||
return FMath::Cos(AngleRadians);
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateShapeFactorFromBulletType(EBulletType BulletType)
|
||
{
|
||
// Shape factor affects penetration efficiency
|
||
switch (BulletType)
|
||
{
|
||
case EBulletType::BT_ArmorPiercing:
|
||
return 1.3f;
|
||
case EBulletType::BT_FullMetalJacket:
|
||
return 1.0f;
|
||
case EBulletType::BT_SoftPoint:
|
||
return 0.9f;
|
||
case EBulletType::BT_HollowPoint:
|
||
return 0.8f;
|
||
case EBulletType::BT_Frangible:
|
||
return 0.6f;
|
||
case EBulletType::BT_Wadcutter:
|
||
return 0.7f;
|
||
case EBulletType::BT_Match:
|
||
return 1.1f;
|
||
default:
|
||
return 1.0f;
|
||
}
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateMaterialFactor(EBulletMaterial BulletMaterial)
|
||
{
|
||
// Material factor affects penetration and expansion
|
||
switch (BulletMaterial)
|
||
{
|
||
case EBulletMaterial::BM_Steel:
|
||
return 1.4f;
|
||
case EBulletMaterial::BM_Tungsten:
|
||
return 1.8f;
|
||
case EBulletMaterial::BM_Brass:
|
||
return 1.2f;
|
||
case EBulletMaterial::BM_Copper:
|
||
return 1.1f;
|
||
case EBulletMaterial::BM_CopperJacket:
|
||
return 1.0f;
|
||
case EBulletMaterial::BM_Lead:
|
||
return 0.8f;
|
||
case EBulletMaterial::BM_LeadAntimony:
|
||
return 0.9f;
|
||
case EBulletMaterial::BM_Bismuth:
|
||
return 0.7f;
|
||
case EBulletMaterial::BM_Zinc:
|
||
return 0.9f;
|
||
default:
|
||
return 1.0f;
|
||
}
|
||
}
|
||
|
||
// Mathematical Spalling Implementations
|
||
bool UEBMathematicalBallistics::ShouldGenerateMathematicalSpalling(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
const FMathematicalMaterialProperties& MaterialProps,
|
||
float VelocityMPS,
|
||
float ImpactAngleDegrees)
|
||
{
|
||
if (!MaterialProps.EnableMathematicalSpalling)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// Additional safety check: prevent spalling from very small fragments
|
||
if (BulletProps.GetMassKg() < 0.001f) // Less than 1 gram
|
||
{
|
||
return false;
|
||
}
|
||
|
||
// Calculate impact stress using Rankine-Hugoniot relations
|
||
float BulletDensity = BulletProps.GetMassKg() / (BulletProps.GetCrossSectionCm2() * 0.0001f); // kg/m³
|
||
float MaterialDensity = MaterialProps.DensityGPerCm3 * 1000.0f; // kg/m³
|
||
|
||
// Impact stress (Pa) = 0.5 * ρ * v²
|
||
float ImpactStress = 0.5f * FMath::Sqrt(BulletDensity * MaterialDensity) * VelocityMPS * VelocityMPS;
|
||
float ImpactStressMPa = ImpactStress / 1000000.0f; // Convert to MPa
|
||
|
||
// Critical stress for spalling
|
||
float CriticalStress = MaterialProps.SpallStrengthMPa * MaterialProps.CriticalStressFactor;
|
||
|
||
// Impact angle factor (perpendicular impacts more effective)
|
||
float AngleFactor = CalculateAngleFactor(ImpactAngleDegrees);
|
||
float EffectiveStress = ImpactStressMPa * AngleFactor;
|
||
|
||
return EffectiveStress >= CriticalStress;
|
||
}
|
||
|
||
int32 UEBMathematicalBallistics::CalculateSpallFragmentCount(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
const FMathematicalMaterialProperties& MaterialProps,
|
||
float VelocityMPS,
|
||
float ImpactAngleDegrees)
|
||
{
|
||
// Fragment count based on impact area and fragment density
|
||
float ImpactArea = BulletProps.GetCrossSectionCm2();
|
||
float AngleFactor = CalculateAngleFactor(ImpactAngleDegrees);
|
||
float EffectiveArea = ImpactArea / FMath::Max(AngleFactor, 0.1f); // Grazing impacts affect larger area
|
||
|
||
// Energy factor - higher energy creates more fragments
|
||
float KineticEnergy = CalculateKineticEnergy(BulletProps, VelocityMPS);
|
||
float EnergyFactor = FMath::Sqrt(KineticEnergy / 1000.0f); // Normalized
|
||
|
||
// Material factor - harder materials create more, smaller fragments
|
||
float MaterialFactor = MaterialProps.MaterialHardness / 100.0f;
|
||
|
||
float BaseFragmentCount = EffectiveArea * MaterialProps.MaxFragmentDensity * EnergyFactor * MaterialFactor;
|
||
|
||
return FMath::Clamp(FMath::RoundToInt(BaseFragmentCount), 1, 50);
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateSpallFragmentVelocity(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
const FMathematicalMaterialProperties& MaterialProps,
|
||
float VelocityMPS,
|
||
float FragmentMassRatio)
|
||
{
|
||
// Gurney equation for fragment velocity: v = √(2E/M)
|
||
// Where E is explosive energy equivalent and M is fragment mass
|
||
|
||
float KineticEnergy = CalculateKineticEnergy(BulletProps, VelocityMPS);
|
||
float SpallEnergyFraction = MaterialProps.FragmentVelocityEfficiency;
|
||
float AvailableEnergy = KineticEnergy * SpallEnergyFraction;
|
||
|
||
// Fragment kinetic energy based on mass ratio
|
||
float FragmentEnergy = AvailableEnergy * (1.0f / FragmentMassRatio);
|
||
|
||
// Calculate fragment velocity using kinetic energy equation
|
||
float FragmentMass = BulletProps.GetMassKg() * FragmentMassRatio;
|
||
float FragmentVelocity = FMath::Sqrt(2.0f * FragmentEnergy / FragmentMass);
|
||
|
||
// Apply material efficiency factor
|
||
return FragmentVelocity * MaterialProps.FragmentVelocityEfficiency;
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateSpallFragmentMass(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
const FMathematicalMaterialProperties& MaterialProps,
|
||
int32 FragmentIndex,
|
||
int32 TotalFragments)
|
||
{
|
||
// Power-law distribution for fragment masses
|
||
// Smaller fragments are more common (Mott distribution)
|
||
|
||
float NormalizedIndex = (float)FragmentIndex / (float)TotalFragments;
|
||
|
||
// Power-law with typical exponent around -1.6 to -2.0
|
||
float SizeDistribution = FMath::Pow(NormalizedIndex + 0.1f, MaterialProps.FragmentSizeExponent);
|
||
|
||
// Base fragment mass ratio
|
||
float BaseMassRatio = MaterialProps.AverageFragmentMassRatio;
|
||
float FragmentMassRatio = BaseMassRatio * SizeDistribution;
|
||
|
||
// Clamp to reasonable range
|
||
return FMath::Clamp(FragmentMassRatio, 0.01f, 0.5f);
|
||
}
|
||
|
||
float UEBMathematicalBallistics::CalculateSpallConeAngle(
|
||
const FMathematicalBulletProperties& BulletProps,
|
||
const FMathematicalMaterialProperties& MaterialProps,
|
||
float VelocityMPS,
|
||
float ImpactAngleDegrees)
|
||
{
|
||
// Spall cone angle based on impact angle and material properties
|
||
// Perpendicular impacts create tighter cones, grazing impacts wider
|
||
|
||
float BaseAngle = 30.0f; // Base cone half-angle in degrees
|
||
|
||
// Impact angle factor (grazing impacts create wider cones)
|
||
float AngleFactor = 1.0f + FMath::Sin(FMath::DegreesToRadians(ImpactAngleDegrees));
|
||
|
||
// Material factor (harder materials create tighter cones)
|
||
float MaterialFactor = 100.0f / MaterialProps.MaterialHardness;
|
||
MaterialFactor = FMath::Clamp(MaterialFactor, 0.5f, 2.0f);
|
||
|
||
// Velocity factor (higher velocity creates wider dispersion)
|
||
float VelocityFactor = 1.0f + (VelocityMPS / 1000.0f) * 0.2f;
|
||
|
||
float ConeAngle = BaseAngle * AngleFactor * MaterialFactor * VelocityFactor;
|
||
|
||
return FMath::Clamp(ConeAngle, 10.0f, 90.0f);
|
||
} |