// Copyright 2016 Mookie. All Rights Reserved. #include "EBMathematicalBallistics.h" #include "Engine/Engine.h" #include "Math/UnrealMathUtility.h" float UEBMathematicalBallistics::CalculatePenetrationDepth( const FMathematicalBulletProperties& BulletProps, const FMathematicalMaterialProperties& MaterialProps, float VelocityMPS, float ImpactAngleDegrees) { // Calculate kinetic energy in joules float KineticEnergy = CalculateKineticEnergy(BulletProps, VelocityMPS); // Calculate sectional density float SectionalDensity = BulletProps.GetSectionalDensity(); // 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, K = constant, E = kinetic energy, SD = sectional density // HF = hardness factor, AF = angle factor, SF = shape factor, ρ = density, σ = yield strength float Constant = 0.0012f; // Empirical constant float Penetration = (Constant * KineticEnergy * SectionalDensity * HardnessRatio * AngleFactor * ShapeFactor) / (MaterialProps.DensityGPerCm3 * MaterialProps.YieldStrengthMPa); return FMath::Max(0.0f, Penetration); } float UEBMathematicalBallistics::CalculateResidualVelocity( const FMathematicalBulletProperties& BulletProps, const FMathematicalMaterialProperties& MaterialProps, float VelocityMPS, float ThicknessCM, float ImpactAngleDegrees) { // Calculate ballistic limit velocity float BallisticLimit = CalculateRechtIpsonVelocity(BulletProps, MaterialProps, ThicknessCM); // Adjust for impact angle float AngleFactor = CalculateAngleFactor(ImpactAngleDegrees); BallisticLimit = BallisticLimit / AngleFactor; // If impact velocity is below ballistic limit, no penetration if (VelocityMPS <= BallisticLimit) { return 0.0f; } // Calculate residual velocity using Recht-Ipson equation // Vr = sqrt(V^2 - Vbl^2) float ResidualVelocity = FMath::Sqrt(VelocityMPS * VelocityMPS - BallisticLimit * BallisticLimit); // Apply energy absorption factor ResidualVelocity *= (1.0f - MaterialProps.EnergyAbsorptionCoefficient); 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.GetSectionalDensity(); float Constant = 0.001f; // Empirical constant return Constant * MaterialProps.DensityGPerCm3 * MaterialProps.YieldStrengthMPa / SectionalDensity; } float UEBMathematicalBallistics::CalculateRechtIpsonVelocity( const FMathematicalBulletProperties& BulletProps, const FMathematicalMaterialProperties& MaterialProps, float ThicknessCM) { // 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 float CrossSectionArea = BulletProps.GetCrossSectionCm2(); float BulletDensity = BulletProps.GetMassKg() / (CrossSectionArea * BulletProps.LengthInches * 2.54f); // Rough approximation float BallisticLimit = FMath::Sqrt(Constant * MaterialProps.YieldStrengthMPa * ThicknessCM / (BulletDensity * CrossSectionArea)); return BallisticLimit; } 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) { // Hardness ratio affects penetration and ricochet return FMath::Max(0.1f, BulletHardness / MaterialHardness); } float UEBMathematicalBallistics::CalculateVelocityFactor(float Velocity, float ThresholdVelocity) { // Velocity factor for various calculations return FMath::Clamp(Velocity / ThresholdVelocity, 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; } }