Files
BallisticsDocs/Source/EasyBallistics/Private/EBBullet.cpp
T
2025-07-03 00:11:05 -07:00

334 lines
8.9 KiB
C++

// Copyright 2018 Mookie. All Rights Reserved.
#include "EBBullet.h"
// Sets default values
AEBBullet::AEBBullet() {
// Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it.
PrimaryActorTick.bCanEverTick = true;
SetTickGroup(ETickingGroup::TG_PrePhysics);
// Initialize firing barrel pointer
FiringBarrel = nullptr;
// Create the new ballistic impact component
BallisticImpactComponent = CreateDefaultSubobject<UEBBallisticImpactComponent>(TEXT("BallisticImpactComponent"));
BallisticImpactComponent->MaterialResponseMap = MaterialResponseMap;
}
// Called when the game starts or when spawned
void AEBBullet::BeginPlay() {
SetActorEnableCollision(AllowComponentCollisions);
// Update ballistic impact component with current settings
if (BallisticImpactComponent) {
BallisticImpactComponent->MaterialResponseMap = MaterialResponseMap;
BallisticImpactComponent->bUseMathematicalPenetration = UseMathematicalPhysics;
BallisticImpactComponent->bEnableBallisticCalculations = UseNewImpactSystem;
}
if(!IsRecycled){
Super::BeginPlay();
IsRecycled = true;
}
else{
ReceiveBeginPlay();
}
if (SafeLaunch) {
OwnerSafe = true;
}
if (DoFirstStepImmediately) {
float DeltaTime = GetWorld()->GetDeltaSeconds();
if (RandomFirstStepDelta) {
DeltaTime *= RandomStream.FRand();
};
if (FixedStep) {
Step(FixedStepSeconds);
}
else {
Step(DeltaTime);
}
}
}
// Called every frame
void AEBBullet::Tick(float DeltaTime) {
Super::Tick(DeltaTime);
if (FixedStep) {
AccumulatedDelta += DeltaTime;
while (AccumulatedDelta >= FixedStepSeconds) {
Step(FixedStepSeconds);
AccumulatedDelta -= FixedStepSeconds;
}
}
else {
Step(DeltaTime);
}
}
void AEBBullet::Step(float DeltaTime) {
FVector start = GetActorLocation();
bool sendUpdate = false;
if (Retrace && CanRetrace) {
//time travel
float remainingTime = LastTraceDelta;
int remainingSteps = MaxTracesPerStep;
FVector PreviousVelocity = LastTracePrevVelocity;
SetActorLocation(LastTraceStart);
Velocity = LastTraceVelocity;
do {
if (RetraceOnAnotherChannel) {
remainingTime = Trace(GetActorLocation(),
PreviousVelocity,
remainingTime,
RetraceChannel);
}
else {
remainingTime = Trace(GetActorLocation(),
PreviousVelocity,
remainingTime,
TraceChannel);
}
PreviousVelocity = Velocity;
remainingSteps -= 1;
if (remainingTime > 0.0f) { sendUpdate = true; };
} while (remainingTime > 0.0f && remainingSteps > 0);
}
CanRetrace = false;
FVector PreviousVelocity = Velocity;
Velocity = UpdateVelocity(GetWorld(), GetActorLocation(), Velocity, DeltaTime);
//trace
float remainingTime = DeltaTime;
int remainingSteps = MaxTracesPerStep;
do {
remainingTime = Trace(GetActorLocation(),
PreviousVelocity,
remainingTime,
TraceChannel
);
PreviousVelocity = Velocity;
remainingSteps -= 1;
if (remainingTime > 0.0f) { sendUpdate = true; };
} while (remainingTime > 0.0f && remainingSteps > 0);
if (sendUpdate) {
if (ReliableReplication) {
VelocityChangeBroadcastReliable(UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(),GetActorLocation()), Velocity);
}
else {
VelocityChangeBroadcast(UGameplayStatics::RebaseLocalOriginOntoZero(GetWorld(), GetActorLocation()), Velocity);
}
}
if(SafeDelay <= 0.0f){
OwnerSafe = false;
}
else {
SafeDelay -= DeltaTime;
}
if (RotateActor) {
FRotator NewRot = UKismetMathLibrary::MakeRotFromX(Velocity);
NewRot.Roll = GetActorRotation().Roll;
SetActorRotation(NewRot);
}
}
float AEBBullet::GetCurveValue(const UCurveFloat* curve, float in, float deflt) const {
if (curve == nullptr) return deflt;
return curve->GetFloatValue(in);
}
void AEBBullet::ApplyWorldOffset(const FVector& InOffset, bool bWorldShift) {
Super::ApplyWorldOffset(InOffset, bWorldShift);
LastTraceStart += InOffset;
}
// Mathematical Physics Function Implementations
float AEBBullet::GetEffectiveMass() const
{
if (UseMathematicalPhysics && BulletPropertiesAsset)
{
return BulletPropertiesAsset->BulletProperties.GetMassKg();
}
return Mass;
}
float AEBBullet::GetEffectiveDiameter() const
{
if (UseMathematicalPhysics && BulletPropertiesAsset)
{
return BulletPropertiesAsset->BulletProperties.GetDiameterCm();
}
return Diameter;
}
float AEBBullet::GetEffectiveDragCoefficient(float MachNumber) const
{
if (UseMathematicalPhysics && BulletPropertiesAsset)
{
return UEBMathematicalBallistics::CalculateDragCoefficient(
BulletPropertiesAsset->BulletProperties, MachNumber);
}
// Use artistic drag calculation
float DragCoeff = GetCurveValue(MachDragCurve, MachNumber, 0.5f);
return DragCoeff * FormFactor;
}
float AEBBullet::CalculateMathematicalPenetration(UPhysicalMaterial* Material, float VelocityMPS, float ImpactAngle) const
{
if (!UseMathematicalPhysics || !BulletPropertiesAsset)
{
return 0.0f;
}
FMathematicalMaterialProperties MaterialProps = GetMaterialProperties(Material);
return UEBMathematicalBallistics::CalculatePenetrationDepth(
BulletPropertiesAsset->BulletProperties,
MaterialProps,
VelocityMPS,
ImpactAngle
);
}
float AEBBullet::CalculateMathematicalRicochetProbability(UPhysicalMaterial* Material, float VelocityMPS, float ImpactAngle) const
{
if (!UseMathematicalPhysics || !BulletPropertiesAsset)
{
return 0.0f;
}
FMathematicalMaterialProperties MaterialProps = GetMaterialProperties(Material);
return UEBMathematicalBallistics::CalculateRicochetProbability(
BulletPropertiesAsset->BulletProperties,
MaterialProps,
VelocityMPS,
ImpactAngle
);
}
FMathematicalMaterialProperties AEBBullet::GetMaterialProperties(UPhysicalMaterial* Material) const
{
// Default properties for unknown materials
FMathematicalMaterialProperties DefaultProps;
if (!Material || !MaterialResponseMap)
{
return DefaultProps;
}
// Check if we have custom properties for this material
if (MaterialResponseMap->Map.Contains(Material))
{
const FEBMaterialResponseMapEntry& Entry = MaterialResponseMap->Map[Material];
if (Entry.UseMathematicalProperties)
{
return Entry.MathematicalProperties;
}
}
// If we have a material properties asset, use that as fallback
if (MaterialPropertiesAsset)
{
return MaterialPropertiesAsset->MaterialProperties;
}
return DefaultProps;
}
// Spalling Implementation
bool AEBBullet::ShouldGenerateSpalling(FVector ImpactVelocity, UPhysicalMaterial* Material) const
{
if (!EnableSpalling || IsSpallFragment || !Material || !MaterialResponseMap)
{
return false;
}
const FEBMaterialResponseMapEntry* ResponseEntry = MaterialResponseMap->Map.Find(Material);
if (!ResponseEntry || !ResponseEntry->EnableSpalling)
{
return false;
}
float ImpactSpeed = ImpactVelocity.Size();
return ImpactSpeed >= ResponseEntry->SpallVelocityThreshold;
}
void AEBBullet::GenerateSpallFragments(FVector ImpactLocation, FVector ImpactVelocity, FVector ImpactNormal, UPhysicalMaterial* Material, AActor* HitActor)
{
if (!ShouldGenerateSpalling(ImpactVelocity, Material))
{
return;
}
const FEBMaterialResponseMapEntry* ResponseEntry = MaterialResponseMap->Map.Find(Material);
if (!ResponseEntry)
{
return;
}
TSubclassOf<AEBBullet> FragmentClass = ResponseEntry->SpallFragmentClass;
if (!FragmentClass)
{
FragmentClass = GetClass();
}
int32 FragmentCount = FMath::RandRange(1, ResponseEntry->SpallFragmentCount);
float SpreadAngleRad = FMath::DegreesToRadians(ResponseEntry->SpallSpreadAngle);
float BaseVelocityMagnitude = ImpactVelocity.Size() * ResponseEntry->SpallVelocityMultiplier;
float FragmentMass = GetEffectiveMass() * ResponseEntry->SpallMassMultiplier;
FVector BaseDirection = UKismetMathLibrary::GetReflectionVector(ImpactVelocity.GetSafeNormal(), ImpactNormal);
for (int32 i = 0; i < FragmentCount; i++)
{
FVector FragmentDirection = RandomStream.VRandCone(BaseDirection, SpreadAngleRad);
float VelocityVariation = RandomStream.FRandRange(0.7f, 1.3f);
FVector FragmentVelocity = FragmentDirection * BaseVelocityMagnitude * VelocityVariation;
FVector SpawnLocation = ImpactLocation + ImpactNormal * 2.0f;
if (HasAuthority())
{
AEBBullet* Fragment = GetWorld()->SpawnActor<AEBBullet>(FragmentClass, SpawnLocation, FragmentVelocity.Rotation());
if (Fragment)
{
Fragment->SetOwner(GetOwner());
Fragment->SetInstigator(GetInstigator());
Fragment->Velocity = FragmentVelocity;
Fragment->Mass = FragmentMass;
Fragment->OwnerSafe = false;
Fragment->IsSpallFragment = true;
Fragment->SetFiringBarrel(GetFiringBarrel());
if (Fragment->MaterialResponseMap == nullptr)
{
Fragment->MaterialResponseMap = MaterialResponseMap;
}
Fragment->IgnoredActors.Add(HitActor);
Fragment->IgnoredActors.Add(this);
Fragment->IgnoredActors.Append(IgnoredActors);
}
}
OnSpallFragmentGenerated(SpawnLocation, FragmentVelocity, FragmentMass, Material);
}
}
void AEBBullet::OnSpallFragmentGenerated_Implementation(FVector FragmentLocation, FVector FragmentVelocity, float FragmentMass, UPhysicalMaterial* Material)
{
}