200 lines
5.9 KiB
C++
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);
|
|
} |