Files
BallisticsDocs/Source/EasyBallistics/Private/EBBallisticImpactComponent.cpp
T
2025-07-02 22:40:58 -07:00

200 lines
5.9 KiB
C++

// 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<UEBMaterialPropertiesAsset>(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);
}