// Copyright 2016 Mookie. All Rights Reserved. #include "EBBallisticImpactComponent.h" #include "EBMathematicalBallistics.h" #include "Engine/Engine.h" #include "Kismet/KismetMathLibrary.h" UEBBallisticImpactComponent::UEBBallisticImpactComponent() { PrimaryComponentTick.bCanEverTick = false; bEnableBallisticCalculations = true; bUseMathematicalPenetration = false; } void UEBBallisticImpactComponent::BeginPlay() { Super::BeginPlay(); } FVector UEBBallisticImpactComponent::CalculateBallisticImpact( const FVector& ImpactLocation, const FVector& ProjectileVelocity, const FMathematicalBulletProperties& BulletProperties, UPhysicalMaterial* HitMaterial, float& OutPenetrationDepth, bool& bOutDidPenetrate, FVector& OutExitLocation) { OutPenetrationDepth = 0.0f; bOutDidPenetrate = false; OutExitLocation = ImpactLocation; if (!bEnableBallisticCalculations || !HitMaterial) { return ImpactLocation; } // Get material response FEBMaterialResponseMapEntry MaterialResponse = GetMaterialResponse(HitMaterial); if (MaterialResponse.NeverPenetrate) { return ImpactLocation; } // Calculate penetration depth if (bUseMathematicalPenetration) { UEBMaterialPropertiesAsset* MaterialProps = GetMaterialProperties(HitMaterial); if (MaterialProps) { float VelocityMPS = ProjectileVelocity.Size() * 0.3048f; // feet to meters float ImpactAngle = CalculateImpactAngle(ProjectileVelocity, FVector::UpVector); // Simplified OutPenetrationDepth = UEBMathematicalBallistics::CalculatePenetrationDepth( BulletProperties, MaterialProps->MaterialProperties, VelocityMPS, ImpactAngle ); } } else { // Use artistic approach float BaseDepth = ProjectileVelocity.Size() * 0.01f; // Simple velocity-based calculation OutPenetrationDepth = BaseDepth * MaterialResponse.PenetrationDepthMultiplier; } // Check if penetration occurred float MinPenetrationThreshold = 1.0f; // 1cm minimum bOutDidPenetrate = OutPenetrationDepth > MinPenetrationThreshold; if (bOutDidPenetrate) { // Calculate exit location FVector PenetrationDirection = ProjectileVelocity.GetSafeNormal(); OutExitLocation = ImpactLocation + (PenetrationDirection * OutPenetrationDepth); } // Fire event OnBallisticImpact.Broadcast(ImpactLocation, FVector::UpVector, HitMaterial, OutPenetrationDepth, bOutDidPenetrate); return OutExitLocation; } FVector UEBBallisticImpactComponent::CalculateRicochet( const FVector& ImpactLocation, const FVector& ImpactNormal, const FVector& ProjectileVelocity, const FMathematicalBulletProperties& BulletProperties, UPhysicalMaterial* HitMaterial, float& OutEnergyRetained, bool& bOutDidRicochet) { OutEnergyRetained = 0.0f; bOutDidRicochet = false; if (!bEnableBallisticCalculations || !HitMaterial) { return ProjectileVelocity; } // Get material response FEBMaterialResponseMapEntry MaterialResponse = GetMaterialResponse(HitMaterial); if (MaterialResponse.NeverRicochet) { return ProjectileVelocity; } // Calculate impact angle float ImpactAngle = CalculateImpactAngle(ProjectileVelocity, ImpactNormal); // Simple ricochet probability based on angle float RicochetProbability = FMath::Clamp((90.0f - ImpactAngle) / 90.0f, 0.0f, 1.0f); RicochetProbability *= MaterialResponse.RicochetProbabilityMultiplier; // Random chance for ricochet if (FMath::RandRange(0.0f, 1.0f) < RicochetProbability) { bOutDidRicochet = true; // Calculate ricochet direction using reflection FVector RicochetDirection = UKismetMathLibrary::GetReflectionVector(ProjectileVelocity, ImpactNormal); // Apply some randomness based on material response if (MaterialResponse.RicochetSpread > 0.0f) { FVector RandomOffset = FMath::VRand() * MaterialResponse.RicochetSpread; RicochetDirection = (RicochetDirection + RandomOffset).GetSafeNormal(); } // Calculate energy retention OutEnergyRetained = MaterialResponse.RicochetRestitution; // Apply velocity reduction FVector NewVelocity = RicochetDirection * (ProjectileVelocity.Size() * OutEnergyRetained); // Fire event OnBallisticRicochet.Broadcast(ImpactLocation, RicochetDirection, HitMaterial, OutEnergyRetained); return NewVelocity; } return ProjectileVelocity; } UEBMaterialPropertiesAsset* UEBBallisticImpactComponent::GetMaterialProperties(UPhysicalMaterial* PhysicalMaterial) { if (!PhysicalMaterial) { return nullptr; } // For now, use naming convention to find associated material properties // In production, you'd want a more robust system FString AssetName = PhysicalMaterial->GetName() + TEXT("_BallisticProps"); FString PackageName = PhysicalMaterial->GetPackage()->GetName() + TEXT("_BallisticProps"); return LoadObject(nullptr, *PackageName, nullptr, LOAD_NoWarn | LOAD_Quiet); } FEBMaterialResponseMapEntry UEBBallisticImpactComponent::GetMaterialResponse(UPhysicalMaterial* PhysicalMaterial) { FEBMaterialResponseMapEntry DefaultResponse; if (!MaterialResponseMap || !PhysicalMaterial) { return DefaultResponse; } if (FEBMaterialResponseMapEntry* Found = MaterialResponseMap->Map.Find(PhysicalMaterial)) { return *Found; } return DefaultResponse; } FVector UEBBallisticImpactComponent::CalculatePenetrationVector( const FVector& ImpactLocation, const FVector& ImpactNormal, const FVector& ProjectileVelocity, float PenetrationDepth) { FVector PenetrationDirection = ProjectileVelocity.GetSafeNormal(); return ImpactLocation + (PenetrationDirection * PenetrationDepth); } float UEBBallisticImpactComponent::CalculateImpactAngle(const FVector& ProjectileVelocity, const FVector& SurfaceNormal) { FVector NormalizedVelocity = ProjectileVelocity.GetSafeNormal(); FVector NormalizedSurfaceNormal = SurfaceNormal.GetSafeNormal(); float DotProduct = FVector::DotProduct(-NormalizedVelocity, NormalizedSurfaceNormal); float AngleRadians = FMath::Acos(FMath::Clamp(DotProduct, -1.0f, 1.0f)); return FMath::RadiansToDegrees(AngleRadians); }