341 lines
12 KiB
C++
341 lines
12 KiB
C++
// Copyright 2016 Mookie. All Rights Reserved.
|
|
|
|
#include "EBBallisticImpactComponent.h"
|
|
#include "EBMathematicalBallistics.h"
|
|
#include "EBUnitConversions.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;
|
|
|
|
// SANITY CHECK: Validate input parameters
|
|
if (!FMath::IsFinite(ImpactLocation.X) || !FMath::IsFinite(ImpactLocation.Y) || !FMath::IsFinite(ImpactLocation.Z))
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Invalid impact location"));
|
|
return ImpactLocation;
|
|
}
|
|
|
|
if (!FMath::IsFinite(ProjectileVelocity.X) || !FMath::IsFinite(ProjectileVelocity.Y) || !FMath::IsFinite(ProjectileVelocity.Z) ||
|
|
ProjectileVelocity.IsNearlyZero())
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Invalid projectile velocity"));
|
|
return 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 = FEBUnitConversions::CMPSToMPS(ProjectileVelocity.Size()); // Convert cm/s (Unreal units) to m/s
|
|
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;
|
|
}
|
|
|
|
// SANITY CHECK: Validate penetration depth
|
|
if (!FMath::IsFinite(OutPenetrationDepth) || OutPenetrationDepth < 0.0f)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Invalid penetration depth %.3f, setting to 0"), OutPenetrationDepth);
|
|
OutPenetrationDepth = 0.0f;
|
|
}
|
|
|
|
// SANITY CHECK: Clamp to reasonable maximum (5 meters)
|
|
const float MaxPenetrationDepth = 500.0f; // 5 meters in cm
|
|
if (OutPenetrationDepth > MaxPenetrationDepth)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Penetration depth %.1f cm exceeds maximum, clamping to %.1f cm"),
|
|
OutPenetrationDepth, MaxPenetrationDepth);
|
|
OutPenetrationDepth = MaxPenetrationDepth;
|
|
}
|
|
|
|
// Check if penetration occurred
|
|
float MinPenetrationThreshold = 1.0f; // 1cm minimum
|
|
bOutDidPenetrate = OutPenetrationDepth > MinPenetrationThreshold;
|
|
|
|
if (bOutDidPenetrate)
|
|
{
|
|
// Calculate exit location
|
|
FVector PenetrationDirection = ProjectileVelocity.GetSafeNormal();
|
|
FVector PenetrationVector = PenetrationDirection * OutPenetrationDepth;
|
|
|
|
// SANITY CHECK: Validate penetration vector
|
|
if (!FMath::IsFinite(PenetrationVector.X) || !FMath::IsFinite(PenetrationVector.Y) || !FMath::IsFinite(PenetrationVector.Z))
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Invalid penetration vector calculated"));
|
|
OutExitLocation = ImpactLocation;
|
|
bOutDidPenetrate = false;
|
|
}
|
|
else
|
|
{
|
|
OutExitLocation = ImpactLocation + PenetrationVector;
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
// SANITY CHECK: Validate input parameters
|
|
if (!FMath::IsFinite(ImpactLocation.X) || !FMath::IsFinite(ImpactLocation.Y) || !FMath::IsFinite(ImpactLocation.Z))
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid impact location"));
|
|
return ProjectileVelocity;
|
|
}
|
|
|
|
if (!FMath::IsFinite(ImpactNormal.X) || !FMath::IsFinite(ImpactNormal.Y) || !FMath::IsFinite(ImpactNormal.Z) ||
|
|
ImpactNormal.IsNearlyZero())
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid impact normal"));
|
|
return ProjectileVelocity;
|
|
}
|
|
|
|
if (!FMath::IsFinite(ProjectileVelocity.X) || !FMath::IsFinite(ProjectileVelocity.Y) || !FMath::IsFinite(ProjectileVelocity.Z) ||
|
|
ProjectileVelocity.IsNearlyZero())
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid projectile velocity"));
|
|
return ProjectileVelocity;
|
|
}
|
|
|
|
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);
|
|
|
|
// SANITY CHECK: Ensure reflection vector is valid
|
|
if (!FMath::IsFinite(RicochetDirection.X) || !FMath::IsFinite(RicochetDirection.Y) || !FMath::IsFinite(RicochetDirection.Z))
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid reflection vector, using simple reflection"));
|
|
RicochetDirection = ProjectileVelocity - 2.0f * FVector::DotProduct(ProjectileVelocity, ImpactNormal) * ImpactNormal;
|
|
}
|
|
|
|
// Apply some randomness based on material response
|
|
if (MaterialResponse.RicochetSpread > 0.0f)
|
|
{
|
|
// SAFETY: Clamp spread to reasonable range to prevent extreme deviations
|
|
float SafeSpread = FMath::Clamp(MaterialResponse.RicochetSpread, 0.0f, 45.0f);
|
|
FVector RandomOffset = FMath::VRand() * FMath::DegreesToRadians(SafeSpread);
|
|
RicochetDirection = (RicochetDirection + RandomOffset).GetSafeNormal();
|
|
}
|
|
|
|
// Calculate energy retention with additional safety clamping
|
|
OutEnergyRetained = FMath::Clamp(MaterialResponse.RicochetRestitution, 0.0f, 0.95f); // Never allow full energy retention
|
|
|
|
// SANITY CHECK: Validate ricochet direction
|
|
if (!FMath::IsFinite(RicochetDirection.X) || !FMath::IsFinite(RicochetDirection.Y) || !FMath::IsFinite(RicochetDirection.Z) ||
|
|
RicochetDirection.IsNearlyZero())
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid ricochet direction calculated"));
|
|
return ProjectileVelocity;
|
|
}
|
|
|
|
// Apply velocity reduction with additional safety checks
|
|
float OriginalSpeed = ProjectileVelocity.Size();
|
|
|
|
// SAFETY: Clamp original speed to prevent calculation with invalid values
|
|
if (!FMath::IsFinite(OriginalSpeed) || OriginalSpeed <= 0.0f)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid original speed %.3f"), OriginalSpeed);
|
|
return ProjectileVelocity;
|
|
}
|
|
|
|
float NewSpeed = OriginalSpeed * OutEnergyRetained;
|
|
|
|
// SANITY CHECK: Validate new speed
|
|
if (!FMath::IsFinite(NewSpeed) || NewSpeed < 0.0f)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid ricochet speed %.3f"), NewSpeed);
|
|
return ProjectileVelocity;
|
|
}
|
|
|
|
// SAFETY: Additional clamp to prevent excessive speeds
|
|
float MaxSafeSpeed = OriginalSpeed * 0.9f; // Never exceed 90% of original speed
|
|
NewSpeed = FMath::Min(NewSpeed, MaxSafeSpeed);
|
|
|
|
FVector NewVelocity = RicochetDirection * NewSpeed;
|
|
|
|
// SANITY CHECK: Validate final ricochet velocity
|
|
if (!FMath::IsFinite(NewVelocity.X) || !FMath::IsFinite(NewVelocity.Y) || !FMath::IsFinite(NewVelocity.Z))
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Invalid final ricochet velocity"));
|
|
return ProjectileVelocity;
|
|
}
|
|
|
|
// SAFETY: Final velocity magnitude check
|
|
float FinalSpeed = NewVelocity.Size();
|
|
if (!FMath::IsFinite(FinalSpeed) || FinalSpeed > OriginalSpeed)
|
|
{
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticRicochet: Ricochet velocity %.1f exceeds original %.1f, clamping"),
|
|
FinalSpeed, OriginalSpeed);
|
|
NewVelocity = NewVelocity.GetSafeNormal() * (OriginalSpeed * 0.8f);
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
// CRITICAL FIX: Use conservative default values for unmapped materials to prevent infinite speed issues
|
|
DefaultResponse.RicochetRestitution = 0.3f; // Lower energy retention for safety
|
|
DefaultResponse.RicochetProbabilityMultiplier = 0.5f; // Reduced ricochet probability
|
|
DefaultResponse.RicochetSpread = 5.0f; // Add some randomness to prevent perfect reflections
|
|
DefaultResponse.NeverRicochet = false;
|
|
DefaultResponse.PenetrationDepthMultiplier = 0.8f; // Slightly reduced penetration
|
|
|
|
// Log warning about unmapped material
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Material '%s' not found in MaterialResponseMap, using safe defaults"),
|
|
PhysicalMaterial ? *PhysicalMaterial->GetName() : TEXT("NULL"));
|
|
|
|
return DefaultResponse;
|
|
}
|
|
|
|
if (FEBMaterialResponseMapEntry* Found = MaterialResponseMap->Map.Find(PhysicalMaterial))
|
|
{
|
|
return *Found;
|
|
}
|
|
|
|
// CRITICAL FIX: Use conservative default values for unmapped materials to prevent infinite speed issues
|
|
DefaultResponse.RicochetRestitution = 0.3f; // Lower energy retention for safety
|
|
DefaultResponse.RicochetProbabilityMultiplier = 0.5f; // Reduced ricochet probability
|
|
DefaultResponse.RicochetSpread = 5.0f; // Add some randomness to prevent perfect reflections
|
|
DefaultResponse.NeverRicochet = false;
|
|
DefaultResponse.PenetrationDepthMultiplier = 0.8f; // Slightly reduced penetration
|
|
|
|
// Log warning about unmapped material
|
|
UE_LOG(LogTemp, Warning, TEXT("EBBallisticImpact: Material '%s' not found in MaterialResponseMap, using safe defaults"),
|
|
*PhysicalMaterial->GetName());
|
|
|
|
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);
|
|
} |