fun
This commit is contained in:
@@ -5,21 +5,45 @@ bool AEBBullet::CollisionFilter_Implementation(FHitResult HitResult) const{
|
||||
};
|
||||
|
||||
FHitResult AEBBullet::FilterHits(TArray<FHitResult> Results, bool &hit) const{
|
||||
TArray<FHitResult> OutResults;
|
||||
|
||||
for (FHitResult Result : Results) {
|
||||
if (Result.bBlockingHit) {
|
||||
|
||||
hit = true;
|
||||
return Result;
|
||||
}else{
|
||||
if (CollisionFilter(Result)) {
|
||||
hit = true;
|
||||
return Result;
|
||||
}
|
||||
}
|
||||
if (Results.Num() == 0) {
|
||||
hit = false;
|
||||
return FHitResult();
|
||||
}
|
||||
|
||||
hit = false;
|
||||
return FHitResult(); //blank
|
||||
// Sort hits by distance to get the closest valid hit
|
||||
Results.Sort([](const FHitResult& A, const FHitResult& B) {
|
||||
return A.Distance < B.Distance;
|
||||
});
|
||||
|
||||
// Filter out invalid hits and duplicates
|
||||
TArray<FHitResult> ValidHits;
|
||||
for (const FHitResult& Result : Results) {
|
||||
// Skip if not a blocking hit or if filter rejects it
|
||||
if (!Result.bBlockingHit || !CollisionFilter(Result)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if this hit is essentially the same as a previous one (duplicate/overlapping geometry)
|
||||
bool IsDuplicate = false;
|
||||
for (const FHitResult& ValidHit : ValidHits) {
|
||||
float Distance = FVector::Dist(Result.Location, ValidHit.Location);
|
||||
if (Distance < 0.5f && Result.GetComponent() == ValidHit.GetComponent()) { // 0.5cm tolerance
|
||||
IsDuplicate = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsDuplicate) {
|
||||
ValidHits.Add(Result);
|
||||
}
|
||||
}
|
||||
|
||||
if (ValidHits.Num() == 0) {
|
||||
hit = false;
|
||||
return FHitResult();
|
||||
}
|
||||
|
||||
// Return the closest valid hit
|
||||
hit = true;
|
||||
return ValidHits[0];
|
||||
};
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "EBBallisticImpactComponent.h"
|
||||
#include "EBMathematicalBallistics.h"
|
||||
#include "EBUnitConversions.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Kismet/KismetMathLibrary.h"
|
||||
|
||||
@@ -49,7 +50,7 @@ FVector UEBBallisticImpactComponent::CalculateBallisticImpact(
|
||||
UEBMaterialPropertiesAsset* MaterialProps = GetMaterialProperties(HitMaterial);
|
||||
if (MaterialProps)
|
||||
{
|
||||
float VelocityMPS = ProjectileVelocity.Size() * 0.3048f; // feet to meters
|
||||
float VelocityMPS = FEBUnitConversions::CMPSToMPS(ProjectileVelocity.Size()); // Convert cm/s (Unreal units) to m/s
|
||||
float ImpactAngle = CalculateImpactAngle(ProjectileVelocity, FVector::UpVector); // Simplified
|
||||
|
||||
OutPenetrationDepth = UEBMathematicalBallistics::CalculatePenetrationDepth(
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
// Copyright 2018 Mookie. All Rights Reserved.
|
||||
#include "EBBarrel.h"
|
||||
#include "EBBullet.h"
|
||||
#include "EngineUtils.h"
|
||||
|
||||
UEBBarrel::UEBBarrel() {
|
||||
PrimaryComponentTick.bCanEverTick = true;
|
||||
@@ -240,4 +242,88 @@ void UEBBarrel::ApplyRecoil_Implementation(UPrimitiveComponent* Component, FVect
|
||||
if (Component->IsSimulatingPhysics()) {
|
||||
Component->AddImpulseAtLocation(Impulse, InLocation);
|
||||
}
|
||||
}
|
||||
|
||||
// Enhanced Debug Control Functions
|
||||
void UEBBarrel::SetBulletDebugCategory(const FString& CategoryName, bool bEnabled)
|
||||
{
|
||||
// Find all bullets fired from this barrel and update their debug settings
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (TActorIterator<AEBBullet> ActorItr(World); ActorItr; ++ActorItr)
|
||||
{
|
||||
AEBBullet* Bullet = *ActorItr;
|
||||
if (Bullet && Bullet->GetFiringBarrel() == this)
|
||||
{
|
||||
Bullet->ToggleDebugCategory(CategoryName, bEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void UEBBarrel::SetAllBulletDebugCategories(bool bEnabled)
|
||||
{
|
||||
// Update all debug categories for bullets from this barrel
|
||||
SetBulletDebugCategory(TEXT("Trajectory"), bEnabled);
|
||||
SetBulletDebugCategory(TEXT("Impact"), bEnabled);
|
||||
SetBulletDebugCategory(TEXT("Physics"), bEnabled);
|
||||
SetBulletDebugCategory(TEXT("Performance"), bEnabled);
|
||||
SetBulletDebugCategory(TEXT("Ballistics"), bEnabled);
|
||||
SetBulletDebugCategory(TEXT("Spalling"), bEnabled);
|
||||
SetBulletDebugCategory(TEXT("Pooling"), bEnabled);
|
||||
}
|
||||
|
||||
void UEBBarrel::ClearAllBulletDebugDisplay()
|
||||
{
|
||||
// Clear debug display for all bullets from this barrel
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (TActorIterator<AEBBullet> ActorItr(World); ActorItr; ++ActorItr)
|
||||
{
|
||||
AEBBullet* Bullet = *ActorItr;
|
||||
if (Bullet && Bullet->GetFiringBarrel() == this)
|
||||
{
|
||||
Bullet->ClearDebugDisplay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FString UEBBarrel::GetBulletDebugInfo() const
|
||||
{
|
||||
// Collect debug information from all bullets fired from this barrel
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return TEXT("No world available");
|
||||
}
|
||||
|
||||
int32 BulletCount = 0;
|
||||
int32 DebugEnabledCount = 0;
|
||||
FString DebugInfo = TEXT("Barrel Debug Info:\n");
|
||||
|
||||
for (TActorIterator<AEBBullet> ActorItr(World); ActorItr; ++ActorItr)
|
||||
{
|
||||
AEBBullet* Bullet = *ActorItr;
|
||||
if (Bullet && Bullet->GetFiringBarrel() == this)
|
||||
{
|
||||
BulletCount++;
|
||||
if (Bullet->DebugEnabled)
|
||||
{
|
||||
DebugEnabledCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DebugInfo += FString::Printf(TEXT("Total Bullets: %d\n"), BulletCount);
|
||||
DebugInfo += FString::Printf(TEXT("Debug Enabled: %d\n"), DebugEnabledCount);
|
||||
DebugInfo += FString::Printf(TEXT("Impact Debug: %s\n"), DebugImpactInfo ? TEXT("ON") : TEXT("OFF"));
|
||||
|
||||
return DebugInfo;
|
||||
}
|
||||
@@ -494,4 +494,57 @@ void AEBBullet::ConfigureAsSpallFragment(float FragmentMass, float FragmentVeloc
|
||||
|
||||
// Safety settings
|
||||
OwnerSafe = false;
|
||||
}
|
||||
|
||||
bool AEBBullet::ProcessSequentialHits(TArray<FHitResult>& AllHits, FVector& CurrentVelocity, float DeltaTime, int32& ProcessedHitCount)
|
||||
{
|
||||
const float MinWallSeparation = 5.0f; // Minimum distance (cm) between walls to consider them separate
|
||||
const int32 MaxSequentialHits = 5; // Maximum hits to process in one trace step
|
||||
|
||||
ProcessedHitCount = 0;
|
||||
bool AnyPenetration = false;
|
||||
|
||||
// Sort hits by distance
|
||||
AllHits.Sort([](const FHitResult& A, const FHitResult& B) {
|
||||
return A.Distance < B.Distance;
|
||||
});
|
||||
|
||||
for (int32 i = 0; i < AllHits.Num() && ProcessedHitCount < MaxSequentialHits; i++)
|
||||
{
|
||||
const FHitResult& Hit = AllHits[i];
|
||||
|
||||
// Skip if this hit is too close to the previous one (likely same wall)
|
||||
if (ProcessedHitCount > 0)
|
||||
{
|
||||
float DistanceToPrevious = FVector::Dist(Hit.Location, AllHits[ProcessedHitCount-1].Location);
|
||||
if (DistanceToPrevious < MinWallSeparation)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if we have enough energy to continue penetrating
|
||||
float CurrentSpeed = CurrentVelocity.Size();
|
||||
if (CurrentSpeed < DespawnVelocity)
|
||||
{
|
||||
break; // Not enough energy to continue
|
||||
}
|
||||
|
||||
// Process this hit like a normal penetration
|
||||
// This is a simplified version - in practice you'd want to call the full penetration logic
|
||||
float VelocityLoss = FMath::Clamp(Hit.Distance / 100.0f, 0.1f, 0.8f); // Lose velocity based on wall thickness
|
||||
CurrentVelocity *= (1.0f - VelocityLoss);
|
||||
|
||||
ProcessedHitCount++;
|
||||
AnyPenetration = true;
|
||||
|
||||
// Add small random deflection to simulate material effects
|
||||
if (CurrentVelocity.Size() > 0.1f)
|
||||
{
|
||||
FVector RandomDeflection = RandomStream.VRandCone(CurrentVelocity.GetSafeNormal(), FMath::DegreesToRadians(2.0f));
|
||||
CurrentVelocity = RandomDeflection * CurrentVelocity.Size();
|
||||
}
|
||||
}
|
||||
|
||||
return AnyPenetration;
|
||||
}
|
||||
@@ -2,13 +2,22 @@
|
||||
|
||||
#include "EBBullet.h"
|
||||
#include "EBBarrel.h"
|
||||
#include "EBUnitConversions.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "PhysicalMaterials/PhysicalMaterial.h"
|
||||
#include "DrawDebugHelpers.h"
|
||||
|
||||
// Initialize static debug counter
|
||||
int32 AEBBullet::DebugMessageCounter = 0;
|
||||
|
||||
void AEBBullet::CreateDebugImpactWidget(FVector Location, bool bRicochet, bool bPenetration, FVector ImpactVelocity, float PenetrationDepth, UPhysicalMaterial* Material)
|
||||
{
|
||||
if (!DebugEnabled || !DebugImpact)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UEBBarrel* Barrel = GetFiringBarrel();
|
||||
if (!Barrel || !Barrel->DebugImpactInfo)
|
||||
{
|
||||
@@ -21,44 +30,536 @@ void AEBBullet::CreateDebugImpactWidget(FVector Location, bool bRicochet, bool b
|
||||
return;
|
||||
}
|
||||
|
||||
// Add to debug tracking
|
||||
DebugImpactLocations.Add(Location);
|
||||
DebugImpactTimes.Add(World->GetTimeSeconds());
|
||||
|
||||
// Clean up old debug data
|
||||
float CurrentTime = World->GetTimeSeconds();
|
||||
for (int32 i = DebugImpactLocations.Num() - 1; i >= 0; i--)
|
||||
{
|
||||
if (CurrentTime - DebugImpactTimes[i] > ImpactDebugTime)
|
||||
{
|
||||
DebugImpactLocations.RemoveAt(i);
|
||||
DebugImpactTimes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Build comprehensive debug information
|
||||
FString ImpactType;
|
||||
FLinearColor ImpactColor;
|
||||
if (bRicochet)
|
||||
{
|
||||
ImpactType = TEXT("RICOCHET");
|
||||
ImpactColor = ImpactColorRicochet;
|
||||
}
|
||||
else if (bPenetration)
|
||||
{
|
||||
ImpactType = TEXT("PENETRATION");
|
||||
ImpactColor = ImpactColorPenetration;
|
||||
}
|
||||
else
|
||||
{
|
||||
ImpactType = TEXT("STOPPED");
|
||||
ImpactColor = ImpactColorStopped;
|
||||
}
|
||||
|
||||
FString MaterialName = Material ? Material->GetName() : TEXT("Unknown");
|
||||
|
||||
float VelocityMPS = ImpactVelocity.Size();
|
||||
float VelocityKMH = VelocityMPS * 0.036f; // Convert cm/s to km/h
|
||||
float VelocityCMPS = ImpactVelocity.Size();
|
||||
float VelocityMPS = FEBUnitConversions::CMPSToMPS(VelocityCMPS);
|
||||
float VelocityKMH = FEBUnitConversions::CMPSToKMH(VelocityCMPS);
|
||||
float KineticEnergy = FEBUnitConversions::CalculateKineticEnergyJoules(GetEffectiveMass(), VelocityMPS);
|
||||
|
||||
FString DebugInfo = FString::Printf(TEXT(
|
||||
"Impact: %s | Material: %s | Velocity: %.1f m/s (%.1f km/h) | Mass: %.3f kg | Diameter: %.2f cm | Energy: %.1f J | Penetration: %.2f cm"
|
||||
),
|
||||
*ImpactType,
|
||||
*MaterialName,
|
||||
VelocityMPS / 100.0f, // Convert cm/s to m/s for display
|
||||
VelocityMPS,
|
||||
VelocityKMH,
|
||||
GetEffectiveMass(),
|
||||
GetEffectiveDiameter(),
|
||||
0.5f * GetEffectiveMass() * FMath::Pow(VelocityMPS / 100.0f, 2.0f), // Kinetic energy in Joules
|
||||
KineticEnergy,
|
||||
PenetrationDepth
|
||||
);
|
||||
|
||||
// Use simple on-screen debug message instead of UMG widget
|
||||
if (GEngine)
|
||||
// Display debug information
|
||||
if (UseOnScreenMessages && GEngine)
|
||||
{
|
||||
GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Yellow, DebugInfo);
|
||||
// Manage message count
|
||||
if (DebugMessageCounter > MaxOnScreenMessages)
|
||||
{
|
||||
DebugMessageCounter = 0;
|
||||
}
|
||||
|
||||
GEngine->AddOnScreenDebugMessage(DebugMessageCounter++, ImpactDebugTime,
|
||||
FColor(ImpactColor.R * 255, ImpactColor.G * 255, ImpactColor.B * 255), DebugInfo);
|
||||
}
|
||||
|
||||
// Also draw debug text in world space
|
||||
DrawDebugString(World, Location + FVector(0, 0, 50), DebugInfo, nullptr, FColor::Yellow, 10.0f, false, 1.2f);
|
||||
if (UseWorldSpaceText)
|
||||
{
|
||||
DrawDebugString(World, Location + FVector(0, 0, 50), DebugInfo, nullptr,
|
||||
FColor(ImpactColor.R * 255, ImpactColor.G * 255, ImpactColor.B * 255),
|
||||
ImpactDebugTime, false, WorldTextScale);
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::DrawTrajectoryDebug(FVector StartLocation, FVector EndLocation, FVector CurrentVelocity, float DeltaTime)
|
||||
{
|
||||
if (!DebugEnabled || !DebugTrajectory)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
float CurrentTime = World->GetTimeSeconds();
|
||||
|
||||
// Add trajectory point
|
||||
if (ShowTrail)
|
||||
{
|
||||
DebugTrajectoryPoints.Add(StartLocation);
|
||||
DebugTrajectoryTimes.Add(CurrentTime);
|
||||
|
||||
// Clean up old trajectory points
|
||||
for (int32 i = DebugTrajectoryPoints.Num() - 1; i >= 0; i--)
|
||||
{
|
||||
if (CurrentTime - DebugTrajectoryTimes[i] > DebugTrailTime)
|
||||
{
|
||||
DebugTrajectoryPoints.RemoveAt(i);
|
||||
DebugTrajectoryTimes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw trajectory trail
|
||||
if (DebugTrajectoryPoints.Num() > 1)
|
||||
{
|
||||
for (int32 i = 1; i < DebugTrajectoryPoints.Num(); i++)
|
||||
{
|
||||
float Alpha = FMath::Clamp((CurrentTime - DebugTrajectoryTimes[i]) / DebugTrailTime, 0.0f, 1.0f);
|
||||
float VelocityNormalized = FMath::Clamp(CurrentVelocity.Size() / 100000.0f, 0.0f, 1.0f); // Normalize to ~1000m/s
|
||||
|
||||
FLinearColor TrailColor = FMath::Lerp(DebugTrailColorSlow, DebugTrailColorFast, VelocityNormalized);
|
||||
TrailColor.A = 1.0f - Alpha;
|
||||
|
||||
float LineThickness = DebugTrailWidth > 0 ? DebugTrailWidth : 1.0f;
|
||||
DrawDebugLine(World, DebugTrajectoryPoints[i-1], DebugTrajectoryPoints[i],
|
||||
FColor(TrailColor.R * 255, TrailColor.G * 255, TrailColor.B * 255, TrailColor.A * 255),
|
||||
false, -1, 0, LineThickness);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Show velocity vectors
|
||||
if (ShowVelocityVectors)
|
||||
{
|
||||
FVector VelocityVector = CurrentVelocity * VelocityVectorScale;
|
||||
DrawDebugDirectionalArrow(World, StartLocation, StartLocation + VelocityVector,
|
||||
50.0f, FColor::Blue, false, DebugTrailTime, 0, 2.0f);
|
||||
|
||||
// Add velocity magnitude text
|
||||
FString VelocityText = FString::Printf(TEXT("%.1f m/s"),
|
||||
FEBUnitConversions::CMPSToMPS(CurrentVelocity.Size()));
|
||||
DrawDebugString(World, StartLocation + FVector(0, 0, 30), VelocityText, nullptr,
|
||||
FColor::Blue, DebugTrailTime, false, WorldTextScale);
|
||||
}
|
||||
|
||||
// Show path prediction
|
||||
if (ShowPathPrediction && PathPredictionSteps > 0)
|
||||
{
|
||||
FVector PredictLocation = StartLocation;
|
||||
FVector PredictVelocity = CurrentVelocity;
|
||||
float PredictDeltaTime = DeltaTime * 5.0f; // Predict further ahead
|
||||
|
||||
for (int32 i = 0; i < PathPredictionSteps; i++)
|
||||
{
|
||||
FVector NextLocation = PredictLocation + PredictVelocity * PredictDeltaTime;
|
||||
|
||||
// Apply gravity and drag prediction (simplified)
|
||||
FVector GravityForce = OverrideGravity ? Gravity : FVector(0, 0, -980);
|
||||
PredictVelocity += GravityForce * PredictDeltaTime;
|
||||
|
||||
DrawDebugLine(World, PredictLocation, NextLocation, FColor::Yellow, false,
|
||||
DebugTrailTime, 0, 1.0f);
|
||||
DrawDebugSphere(World, NextLocation, 2.0f, 8, FColor::Yellow, false, DebugTrailTime);
|
||||
|
||||
PredictLocation = NextLocation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::DrawImpactDebug(FVector ImpactLocation, FVector ImpactVelocity, FVector ImpactNormal, bool bRicochet, bool bPenetration, float PenetrationDepth, UPhysicalMaterial* Material)
|
||||
{
|
||||
if (!DebugEnabled || !DebugImpact)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Choose color based on impact type
|
||||
FLinearColor ImpactColor;
|
||||
if (bRicochet)
|
||||
{
|
||||
ImpactColor = ImpactColorRicochet;
|
||||
}
|
||||
else if (bPenetration)
|
||||
{
|
||||
ImpactColor = ImpactColorPenetration;
|
||||
}
|
||||
else
|
||||
{
|
||||
ImpactColor = ImpactColorStopped;
|
||||
}
|
||||
|
||||
FColor DebugColor(ImpactColor.R * 255, ImpactColor.G * 255, ImpactColor.B * 255);
|
||||
|
||||
// Show impact points
|
||||
if (ShowImpactPoints)
|
||||
{
|
||||
DrawDebugSphere(World, ImpactLocation, ImpactPointSize, 12, DebugColor, false, ImpactDebugTime);
|
||||
}
|
||||
|
||||
// Show impact normals
|
||||
if (ShowImpactNormals)
|
||||
{
|
||||
DrawDebugDirectionalArrow(World, ImpactLocation, ImpactLocation + ImpactNormal * 50.0f,
|
||||
20.0f, DebugColor, false, ImpactDebugTime, 0, 2.0f);
|
||||
}
|
||||
|
||||
// Show penetration depth
|
||||
if (ShowPenetrationDepth && bPenetration)
|
||||
{
|
||||
FVector PenetrationVector = -ImpactNormal * PenetrationDepth;
|
||||
DrawDebugLine(World, ImpactLocation, ImpactLocation + PenetrationVector,
|
||||
FColor::Green, false, ImpactDebugTime, 0, 3.0f);
|
||||
|
||||
FString PenetrationText = FString::Printf(TEXT("Pen: %.1f cm"), PenetrationDepth);
|
||||
DrawDebugString(World, ImpactLocation + PenetrationVector + FVector(0, 0, 20),
|
||||
PenetrationText, nullptr, FColor::Green, ImpactDebugTime, false, WorldTextScale);
|
||||
}
|
||||
|
||||
// Show ricochet angles
|
||||
if (ShowRicochetAngles && bRicochet)
|
||||
{
|
||||
FVector ReflectedVelocity = ImpactVelocity - 2 * FVector::DotProduct(ImpactVelocity, ImpactNormal) * ImpactNormal;
|
||||
DrawDebugDirectionalArrow(World, ImpactLocation, ImpactLocation + ReflectedVelocity.GetSafeNormal() * 75.0f,
|
||||
30.0f, FColor::Yellow, false, ImpactDebugTime, 0, 2.0f);
|
||||
|
||||
float RicochetAngle = FMath::Acos(FVector::DotProduct(-ImpactVelocity.GetSafeNormal(), ImpactNormal));
|
||||
FString AngleText = FString::Printf(TEXT("Angle: %.1f°"), FMath::RadiansToDegrees(RicochetAngle));
|
||||
DrawDebugString(World, ImpactLocation + FVector(0, 0, 40), AngleText, nullptr,
|
||||
FColor::Yellow, ImpactDebugTime, false, WorldTextScale);
|
||||
}
|
||||
|
||||
// Show material information
|
||||
if (ShowMaterialInfo && Material)
|
||||
{
|
||||
FString MaterialInfo = FString::Printf(TEXT("Material: %s"), *Material->GetName());
|
||||
DrawDebugString(World, ImpactLocation + FVector(0, 0, 60), MaterialInfo, nullptr,
|
||||
DebugColor, ImpactDebugTime, false, WorldTextScale);
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::DrawPhysicsDebug(FVector Location, FVector DragForce, FVector GravityForce, FVector WindForce, float AirDensity, float SpeedOfSound)
|
||||
{
|
||||
if (!DebugEnabled || !DebugPhysics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Show drag forces
|
||||
if (ShowDragForces && !DragForce.IsNearlyZero())
|
||||
{
|
||||
FVector DragVector = DragForce * ForceVectorScale;
|
||||
DrawDebugDirectionalArrow(World, Location, Location + DragVector,
|
||||
10.0f, FColor::Red, false, PhysicsDebugTime, 0, 1.5f);
|
||||
|
||||
FString DragText = FString::Printf(TEXT("Drag: %.1f N"), DragForce.Size());
|
||||
DrawDebugString(World, Location + DragVector + FVector(0, 0, 10), DragText, nullptr,
|
||||
FColor::Red, PhysicsDebugTime, false, WorldTextScale);
|
||||
}
|
||||
|
||||
// Show gravity effect
|
||||
if (ShowGravityEffect && !GravityForce.IsNearlyZero())
|
||||
{
|
||||
FVector GravityVector = GravityForce * ForceVectorScale;
|
||||
DrawDebugDirectionalArrow(World, Location, Location + GravityVector,
|
||||
10.0f, FColor::Purple, false, PhysicsDebugTime, 0, 1.5f);
|
||||
|
||||
FString GravityText = FString::Printf(TEXT("Gravity: %.1f N"), GravityForce.Size());
|
||||
DrawDebugString(World, Location + GravityVector + FVector(0, 0, 10), GravityText, nullptr,
|
||||
FColor::Purple, PhysicsDebugTime, false, WorldTextScale);
|
||||
}
|
||||
|
||||
// Show wind effect
|
||||
if (ShowWindEffect && !WindForce.IsNearlyZero())
|
||||
{
|
||||
FVector WindVector = WindForce * ForceVectorScale;
|
||||
DrawDebugDirectionalArrow(World, Location, Location + WindVector,
|
||||
10.0f, FColor::Cyan, false, PhysicsDebugTime, 0, 1.5f);
|
||||
|
||||
FString WindText = FString::Printf(TEXT("Wind: %.1f N"), WindForce.Size());
|
||||
DrawDebugString(World, Location + WindVector + FVector(0, 0, 10), WindText, nullptr,
|
||||
FColor::Cyan, PhysicsDebugTime, false, WorldTextScale);
|
||||
}
|
||||
|
||||
// Show atmospheric data
|
||||
if (ShowAtmosphericData)
|
||||
{
|
||||
FString AtmosphericInfo = FString::Printf(TEXT(
|
||||
"Air Density: %.3f kg/m³\nSpeed of Sound: %.1f m/s\nAltitude: %.1f m"
|
||||
),
|
||||
AirDensity,
|
||||
FEBUnitConversions::CMPSToMPS(SpeedOfSound),
|
||||
FEBUnitConversions::CMToM(GetAltitude(World, Location))
|
||||
);
|
||||
|
||||
DrawDebugString(World, Location + FVector(0, 0, 80), AtmosphericInfo, nullptr,
|
||||
FColor::White, PhysicsDebugTime, false, WorldTextScale);
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::DrawBallisticsDebug(FVector Location, FVector BulletVelocity, float KineticEnergy, float DragCoefficient, float MachNumber)
|
||||
{
|
||||
if (!DebugEnabled || !DebugBallistics)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Show energy calculations
|
||||
if (ShowEnergyCalculations)
|
||||
{
|
||||
FString EnergyInfo = FString::Printf(TEXT(
|
||||
"Kinetic Energy: %.1f J\nMomentum: %.3f kg⋅m/s\nPower: %.1f W"
|
||||
),
|
||||
KineticEnergy,
|
||||
GetEffectiveMass() * FEBUnitConversions::CMPSToMPS(BulletVelocity.Size()),
|
||||
KineticEnergy * BulletVelocity.Size() / 100.0f // Simplified power calculation
|
||||
);
|
||||
|
||||
DrawDebugString(World, Location + FVector(0, 0, 100), EnergyInfo, nullptr,
|
||||
FColor::Orange, BallisticsDebugTime, false, WorldTextScale);
|
||||
}
|
||||
|
||||
// Show mathematical comparison
|
||||
if (ShowMathematicalComparison && UseMathematicalPhysics)
|
||||
{
|
||||
FString MathInfo = FString::Printf(TEXT(
|
||||
"Mathematical Mode: ON\nDrag Coefficient: %.3f\nMach Number: %.2f"
|
||||
),
|
||||
DragCoefficient,
|
||||
MachNumber
|
||||
);
|
||||
|
||||
DrawDebugString(World, Location + FVector(0, 0, 120), MathInfo, nullptr,
|
||||
FColor::Green, BallisticsDebugTime, false, WorldTextScale);
|
||||
}
|
||||
|
||||
// Show ballistic coefficient
|
||||
if (ShowBallisticCoefficient)
|
||||
{
|
||||
float BallisticCoefficient = GetEffectiveMass() / (DragCoefficient * GetEffectiveDiameter() * GetEffectiveDiameter());
|
||||
FString BCInfo = FString::Printf(TEXT("Ballistic Coefficient: %.3f"), BallisticCoefficient);
|
||||
|
||||
DrawDebugString(World, Location + FVector(0, 0, 140), BCInfo, nullptr,
|
||||
FColor::Magenta, BallisticsDebugTime, false, WorldTextScale);
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::DrawSpallDebug(FVector ImpactLocation, FVector ImpactVelocity, FVector ImpactNormal, TArray<FVector> FragmentVelocities)
|
||||
{
|
||||
if (!DebugEnabled || !DebugSpalling)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FColor PrimaryColor(SpallColorPrimary.R * 255, SpallColorPrimary.G * 255, SpallColorPrimary.B * 255);
|
||||
FColor FragmentColor(SpallColorFragment.R * 255, SpallColorFragment.G * 255, SpallColorFragment.B * 255);
|
||||
|
||||
// Show spall generation
|
||||
if (ShowSpallGeneration)
|
||||
{
|
||||
DrawDebugSphere(World, ImpactLocation, 8.0f, 16, PrimaryColor, false, SpallDebugTime);
|
||||
|
||||
FString SpallInfo = FString::Printf(TEXT("Spall Generated: %d fragments"), FragmentVelocities.Num());
|
||||
DrawDebugString(World, ImpactLocation + FVector(0, 0, 70), SpallInfo, nullptr,
|
||||
PrimaryColor, SpallDebugTime, false, WorldTextScale);
|
||||
}
|
||||
|
||||
// Show fragment trajectories
|
||||
if (ShowFragmentTrajectories)
|
||||
{
|
||||
for (int32 i = 0; i < FragmentVelocities.Num(); i++)
|
||||
{
|
||||
FVector FragmentDirection = FragmentVelocities[i].GetSafeNormal();
|
||||
FVector FragmentEndpoint = ImpactLocation + FragmentDirection * 100.0f;
|
||||
|
||||
DrawDebugDirectionalArrow(World, ImpactLocation, FragmentEndpoint,
|
||||
15.0f, FragmentColor, false, SpallDebugTime, 0, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// Show spall cones
|
||||
if (ShowSpallCones && FragmentVelocities.Num() > 0)
|
||||
{
|
||||
// Calculate cone parameters
|
||||
FVector ConeDirection = ImpactNormal;
|
||||
float ConeAngle = FMath::DegreesToRadians(45.0f); // 45 degree cone
|
||||
float ConeLength = 80.0f;
|
||||
|
||||
// Draw cone outline
|
||||
int32 ConeSides = 8;
|
||||
TArray<FVector> ConePoints;
|
||||
|
||||
for (int32 i = 0; i < ConeSides; i++)
|
||||
{
|
||||
float Angle = (2.0f * PI * i) / ConeSides;
|
||||
FVector RadialDirection = FVector(FMath::Cos(Angle), FMath::Sin(Angle), 0);
|
||||
FVector ConePoint = ImpactLocation + ConeDirection * ConeLength + RadialDirection * FMath::Tan(ConeAngle) * ConeLength;
|
||||
ConePoints.Add(ConePoint);
|
||||
|
||||
// Draw lines from impact point to cone edge
|
||||
DrawDebugLine(World, ImpactLocation, ConePoint, PrimaryColor, false, SpallDebugTime, 0, 0.5f);
|
||||
}
|
||||
|
||||
// Draw cone base
|
||||
for (int32 i = 0; i < ConeSides; i++)
|
||||
{
|
||||
int32 NextIndex = (i + 1) % ConeSides;
|
||||
DrawDebugLine(World, ConePoints[i], ConePoints[NextIndex], PrimaryColor, false, SpallDebugTime, 0, 0.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::DrawPerformanceDebug(FVector Location, int32 TraceCount, float FrameTime, int32 PooledBullets)
|
||||
{
|
||||
if (!DebugEnabled || !DebugPerformance)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
UWorld* World = GetWorld();
|
||||
if (!World)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
FString PerformanceInfo;
|
||||
|
||||
// Show trace count
|
||||
if (ShowTraceCount)
|
||||
{
|
||||
PerformanceInfo += FString::Printf(TEXT("Traces: %d\n"), TraceCount);
|
||||
}
|
||||
|
||||
// Show frame timing
|
||||
if (ShowFrameTiming)
|
||||
{
|
||||
float FPS = FrameTime > 0 ? 1.0f / FrameTime : 0.0f;
|
||||
PerformanceInfo += FString::Printf(TEXT("Frame Time: %.2f ms (%.1f FPS)\n"), FrameTime * 1000.0f, FPS);
|
||||
}
|
||||
|
||||
// Show pooling stats
|
||||
if (ShowPoolingStats && EnablePooling)
|
||||
{
|
||||
PerformanceInfo += FString::Printf(TEXT("Pooled Bullets: %d/%d\n"), PooledBullets, MaxPoolSize);
|
||||
}
|
||||
|
||||
if (!PerformanceInfo.IsEmpty())
|
||||
{
|
||||
DrawDebugString(World, Location + FVector(0, 0, 160), PerformanceInfo, nullptr,
|
||||
FColor::Cyan, PerformanceDebugTime, false, WorldTextScale);
|
||||
}
|
||||
}
|
||||
|
||||
void AEBBullet::ClearDebugDisplay()
|
||||
{
|
||||
DebugTrajectoryPoints.Empty();
|
||||
DebugTrajectoryTimes.Empty();
|
||||
DebugImpactLocations.Empty();
|
||||
DebugImpactTimes.Empty();
|
||||
DebugTraceCounter = 0;
|
||||
DebugMessageCounter = 0;
|
||||
}
|
||||
|
||||
void AEBBullet::ToggleDebugCategory(const FString& CategoryName, bool bEnabled)
|
||||
{
|
||||
if (CategoryName == TEXT("Trajectory"))
|
||||
{
|
||||
DebugTrajectory = bEnabled;
|
||||
}
|
||||
else if (CategoryName == TEXT("Impact"))
|
||||
{
|
||||
DebugImpact = bEnabled;
|
||||
}
|
||||
else if (CategoryName == TEXT("Physics"))
|
||||
{
|
||||
DebugPhysics = bEnabled;
|
||||
}
|
||||
else if (CategoryName == TEXT("Performance"))
|
||||
{
|
||||
DebugPerformance = bEnabled;
|
||||
}
|
||||
else if (CategoryName == TEXT("Ballistics"))
|
||||
{
|
||||
DebugBallistics = bEnabled;
|
||||
}
|
||||
else if (CategoryName == TEXT("Spalling"))
|
||||
{
|
||||
DebugSpalling = bEnabled;
|
||||
}
|
||||
else if (CategoryName == TEXT("Pooling"))
|
||||
{
|
||||
DebugPooling = bEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
FString AEBBullet::GetDebugInfoString() const
|
||||
{
|
||||
return FString::Printf(TEXT(
|
||||
"Bullet Debug Info:\n"
|
||||
"Enabled: %s\n"
|
||||
"Trajectory: %s | Impact: %s | Physics: %s\n"
|
||||
"Performance: %s | Ballistics: %s | Spalling: %s\n"
|
||||
"Trace Count: %d | Active Trajectory Points: %d\n"
|
||||
"Active Impact Points: %d"
|
||||
),
|
||||
DebugEnabled ? TEXT("Yes") : TEXT("No"),
|
||||
DebugTrajectory ? TEXT("On") : TEXT("Off"),
|
||||
DebugImpact ? TEXT("On") : TEXT("Off"),
|
||||
DebugPhysics ? TEXT("On") : TEXT("Off"),
|
||||
DebugPerformance ? TEXT("On") : TEXT("Off"),
|
||||
DebugBallistics ? TEXT("On") : TEXT("Off"),
|
||||
DebugSpalling ? TEXT("On") : TEXT("Off"),
|
||||
DebugTraceCounter,
|
||||
DebugTrajectoryPoints.Num(),
|
||||
DebugImpactLocations.Num()
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EBMathematicalBallistics.h"
|
||||
#include "EBUnitConversions.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "Math/UnrealMathUtility.h"
|
||||
|
||||
@@ -13,8 +14,8 @@ float UEBMathematicalBallistics::CalculatePenetrationDepth(
|
||||
// Calculate kinetic energy in joules
|
||||
float KineticEnergy = CalculateKineticEnergy(BulletProps, VelocityMPS);
|
||||
|
||||
// Calculate sectional density
|
||||
float SectionalDensity = BulletProps.GetSectionalDensity();
|
||||
// Calculate sectional density in proper SI units (kg/m²)
|
||||
float SectionalDensity = BulletProps.GetSectionalDensityKgPerM2();
|
||||
|
||||
// Calculate hardness ratio
|
||||
float HardnessRatio = CalculateHardnessRatio(BulletProps.BulletHardness, MaterialProps.MaterialHardness);
|
||||
@@ -27,14 +28,28 @@ float UEBMathematicalBallistics::CalculatePenetrationDepth(
|
||||
|
||||
// Modified Taylor-Hopkinson equation for penetration depth
|
||||
// P = (K * E * SD * HF * AF * SF) / (ρ * σ)
|
||||
// Where: P = penetration depth, K = constant, E = kinetic energy, SD = sectional density
|
||||
// HF = hardness factor, AF = angle factor, SF = shape factor, ρ = density, σ = yield strength
|
||||
// Where: P = penetration depth (cm), K = constant, E = kinetic energy (J), SD = sectional density (kg/m²)
|
||||
// HF = hardness factor, AF = angle factor, SF = shape factor, ρ = density (g/cm³), σ = yield strength (MPa)
|
||||
|
||||
float Constant = 0.0012f; // Empirical constant
|
||||
float Penetration = (Constant * KineticEnergy * SectionalDensity * HardnessRatio * AngleFactor * ShapeFactor) /
|
||||
(MaterialProps.DensityGPerCm3 * MaterialProps.YieldStrengthMPa);
|
||||
// Unit conversion factors for proper dimensional analysis:
|
||||
// KineticEnergy is in Joules (kg⋅m²/s²)
|
||||
// SectionalDensity is in kg/m²
|
||||
// DensityGPerCm3 needs conversion: g/cm³ = 1000 kg/m³
|
||||
// YieldStrengthMPa is in MPa = 10⁶ Pa = 10⁶ N/m²
|
||||
|
||||
return FMath::Max(0.0f, Penetration);
|
||||
float DensityKgPerM3 = FEBUnitConversions::GPerCM3ToKGPerM3(MaterialProps.DensityGPerCm3);
|
||||
float YieldStrengthPa = FEBUnitConversions::MPaToPa(MaterialProps.YieldStrengthMPa);
|
||||
|
||||
// Empirical constant adjusted for proper units (results in meters)
|
||||
float Constant = 2.0f; // Adjusted for dimensional consistency
|
||||
|
||||
float PenetrationM = (Constant * KineticEnergy * SectionalDensity * HardnessRatio * AngleFactor * ShapeFactor) /
|
||||
(DensityKgPerM3 * YieldStrengthPa);
|
||||
|
||||
// Convert from meters to centimeters for consistency with Unreal Engine units
|
||||
float PenetrationCm = FEBUnitConversions::MetersToCM(PenetrationM);
|
||||
|
||||
return FMath::Max(0.0f, PenetrationCm);
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateResidualVelocity(
|
||||
@@ -178,10 +193,15 @@ float UEBMathematicalBallistics::CalculateTaylorHopkinsonLimit(
|
||||
{
|
||||
// Taylor-Hopkinson limit: t = K * ρ * σ / (SD * V^2)
|
||||
// Rearranged to solve for limiting thickness
|
||||
float SectionalDensity = BulletProps.GetSectionalDensity();
|
||||
float SectionalDensity = BulletProps.GetSectionalDensityKgPerM2();
|
||||
float Constant = 0.001f; // Empirical constant
|
||||
|
||||
return Constant * MaterialProps.DensityGPerCm3 * MaterialProps.YieldStrengthMPa / SectionalDensity;
|
||||
// Convert to proper units: result in cm
|
||||
float DensityKgPerM3 = MaterialProps.DensityGPerCm3 * 1000.0f;
|
||||
float YieldStrengthPa = MaterialProps.YieldStrengthMPa * 1000000.0f;
|
||||
|
||||
float ThicknessM = Constant * DensityKgPerM3 * YieldStrengthPa / SectionalDensity;
|
||||
return ThicknessM * 100.0f; // Convert to cm
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateRechtIpsonVelocity(
|
||||
@@ -194,13 +214,21 @@ float UEBMathematicalBallistics::CalculateRechtIpsonVelocity(
|
||||
// Where k is a constant, σ is yield strength, t is thickness, ρ is bullet density, A is cross-sectional area
|
||||
|
||||
float Constant = 2.0f; // Empirical constant
|
||||
float CrossSectionArea = BulletProps.GetCrossSectionCm2();
|
||||
float BulletDensity = BulletProps.GetMassKg() / (CrossSectionArea * BulletProps.LengthInches * 2.54f); // Rough approximation
|
||||
|
||||
float BallisticLimit = FMath::Sqrt(Constant * MaterialProps.YieldStrengthMPa * ThicknessCM /
|
||||
(BulletDensity * CrossSectionArea));
|
||||
// Convert all units to SI for calculation
|
||||
float CrossSectionAreaM2 = BulletProps.GetCrossSectionCm2() * 0.0001f; // cm² to m²
|
||||
float ThicknessM = ThicknessCM * 0.01f; // cm to m
|
||||
float YieldStrengthPa = MaterialProps.YieldStrengthMPa * 1000000.0f; // MPa to Pa
|
||||
|
||||
return BallisticLimit;
|
||||
// Calculate bullet density (kg/m³)
|
||||
float BulletVolumeM3 = CrossSectionAreaM2 * BulletProps.LengthInches * 0.0254f; // Convert length to meters
|
||||
float BulletDensityKgPerM3 = BulletProps.GetMassKg() / BulletVolumeM3;
|
||||
|
||||
// Calculate ballistic limit velocity in m/s
|
||||
float BallisticLimitMPS = FMath::Sqrt(Constant * YieldStrengthPa * ThicknessM /
|
||||
(BulletDensityKgPerM3 * CrossSectionAreaM2));
|
||||
|
||||
return BallisticLimitMPS;
|
||||
}
|
||||
|
||||
float UEBMathematicalBallistics::CalculateCriticalRicochetAngle(
|
||||
|
||||
@@ -2,9 +2,110 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#include "EasyBallistics.h"
|
||||
#include "EBBullet.h"
|
||||
#include "Engine/World.h"
|
||||
#include "Engine/Engine.h"
|
||||
#include "EngineUtils.h"
|
||||
|
||||
#define LOCTEXT_NAMESPACE "FEasyBallisticsModule"
|
||||
|
||||
// Console commands for debug control
|
||||
static FAutoConsoleCommand EBDebugToggleCommand(
|
||||
TEXT("eb.debug.toggle"),
|
||||
TEXT("Toggle EasyBallistics debug display on/off"),
|
||||
FConsoleCommandDelegate::CreateStatic([]()
|
||||
{
|
||||
if (GEngine && GEngine->GetWorldFromContextObject(GEngine->GameViewport, EGetWorldErrorMode::LogAndReturnNull))
|
||||
{
|
||||
UWorld* World = GEngine->GetWorldFromContextObject(GEngine->GameViewport, EGetWorldErrorMode::LogAndReturnNull);
|
||||
for (TActorIterator<AEBBullet> ActorItr(World); ActorItr; ++ActorItr)
|
||||
{
|
||||
AEBBullet* Bullet = *ActorItr;
|
||||
if (Bullet)
|
||||
{
|
||||
Bullet->DebugEnabled = !Bullet->DebugEnabled;
|
||||
}
|
||||
}
|
||||
UE_LOG(LogTemp, Warning, TEXT("EasyBallistics debug toggled"));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
static FAutoConsoleCommand EBDebugCategoryCommand(
|
||||
TEXT("eb.debug.category"),
|
||||
TEXT("Toggle specific debug category: eb.debug.category <CategoryName> <0|1>"),
|
||||
FConsoleCommandWithArgsDelegate::CreateStatic([](const TArray<FString>& Args)
|
||||
{
|
||||
if (Args.Num() < 2)
|
||||
{
|
||||
UE_LOG(LogTemp, Warning, TEXT("Usage: eb.debug.category <CategoryName> <0|1>"));
|
||||
UE_LOG(LogTemp, Warning, TEXT("Categories: Trajectory, Impact, Physics, Performance, Ballistics, Spalling, Pooling"));
|
||||
return;
|
||||
}
|
||||
|
||||
FString CategoryName = Args[0];
|
||||
bool bEnabled = FCString::Atoi(*Args[1]) != 0;
|
||||
|
||||
if (GEngine && GEngine->GetWorldFromContextObject(GEngine->GameViewport, EGetWorldErrorMode::LogAndReturnNull))
|
||||
{
|
||||
UWorld* World = GEngine->GetWorldFromContextObject(GEngine->GameViewport, EGetWorldErrorMode::LogAndReturnNull);
|
||||
for (TActorIterator<AEBBullet> ActorItr(World); ActorItr; ++ActorItr)
|
||||
{
|
||||
AEBBullet* Bullet = *ActorItr;
|
||||
if (Bullet)
|
||||
{
|
||||
Bullet->ToggleDebugCategory(CategoryName, bEnabled);
|
||||
}
|
||||
}
|
||||
UE_LOG(LogTemp, Warning, TEXT("EasyBallistics debug category '%s' set to %s"), *CategoryName, bEnabled ? TEXT("ON") : TEXT("OFF"));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
static FAutoConsoleCommand EBDebugClearCommand(
|
||||
TEXT("eb.debug.clear"),
|
||||
TEXT("Clear all EasyBallistics debug display"),
|
||||
FConsoleCommandDelegate::CreateStatic([]()
|
||||
{
|
||||
if (GEngine && GEngine->GetWorldFromContextObject(GEngine->GameViewport, EGetWorldErrorMode::LogAndReturnNull))
|
||||
{
|
||||
UWorld* World = GEngine->GetWorldFromContextObject(GEngine->GameViewport, EGetWorldErrorMode::LogAndReturnNull);
|
||||
for (TActorIterator<AEBBullet> ActorItr(World); ActorItr; ++ActorItr)
|
||||
{
|
||||
AEBBullet* Bullet = *ActorItr;
|
||||
if (Bullet)
|
||||
{
|
||||
Bullet->ClearDebugDisplay();
|
||||
}
|
||||
}
|
||||
UE_LOG(LogTemp, Warning, TEXT("EasyBallistics debug display cleared"));
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
static FAutoConsoleCommand EBDebugInfoCommand(
|
||||
TEXT("eb.debug.info"),
|
||||
TEXT("Print debug information for all bullets"),
|
||||
FConsoleCommandDelegate::CreateStatic([]()
|
||||
{
|
||||
if (GEngine && GEngine->GetWorldFromContextObject(GEngine->GameViewport, EGetWorldErrorMode::LogAndReturnNull))
|
||||
{
|
||||
UWorld* World = GEngine->GetWorldFromContextObject(GEngine->GameViewport, EGetWorldErrorMode::LogAndReturnNull);
|
||||
int32 BulletCount = 0;
|
||||
for (TActorIterator<AEBBullet> ActorItr(World); ActorItr; ++ActorItr)
|
||||
{
|
||||
AEBBullet* Bullet = *ActorItr;
|
||||
if (Bullet)
|
||||
{
|
||||
FString DebugInfo = Bullet->GetDebugInfoString();
|
||||
UE_LOG(LogTemp, Warning, TEXT("Bullet %d: %s"), BulletCount++, *DebugInfo);
|
||||
}
|
||||
}
|
||||
UE_LOG(LogTemp, Warning, TEXT("Total active bullets: %d"), BulletCount);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
void FEasyBallisticsModule::StartupModule()
|
||||
{
|
||||
// This code will execute after your module is loaded into memory; the exact timing is specified in the .uplugin file per-module
|
||||
|
||||
@@ -6,19 +6,31 @@ float AEBBullet::PenetrationTrace(FVector StartLocation, FVector EndLocation, TW
|
||||
FCollisionQueryParams QueryParams;
|
||||
QueryParams.bTraceComplex = TraceComplex;
|
||||
QueryParams.bFindInitialOverlaps = true;
|
||||
QueryParams.AddIgnoredActor(this); // Ignore self
|
||||
QueryParams.AddIgnoredActors(IgnoredActors); // Ignore other ignored actors
|
||||
|
||||
FHitResult Result;
|
||||
TArray<FHitResult> MultiResults;
|
||||
|
||||
switch (PenTraceType) {
|
||||
case(EPenTraceType::PT_BackTrace): {
|
||||
bool Hit = GetWorld()->LineTraceSingleByChannel(Result, EndLocation, StartLocation, CollisionChannel, QueryParams);
|
||||
if (!Hit) return 1.0f;
|
||||
ExitNormal = Result.Normal;
|
||||
ExitLocation = Result.Location;
|
||||
return (1.0f - Result.Time);
|
||||
// Use multi-trace to handle multiple surfaces between start and end
|
||||
bool Hit = GetWorld()->LineTraceMultiByChannel(MultiResults, EndLocation, StartLocation, CollisionChannel, QueryParams);
|
||||
if (!Hit || MultiResults.Num() == 0) return 1.0f;
|
||||
|
||||
// Find the first blocking hit when tracing backwards
|
||||
for (const FHitResult& HitResult : MultiResults) {
|
||||
if (HitResult.bBlockingHit) {
|
||||
ExitNormal = HitResult.Normal;
|
||||
ExitLocation = HitResult.Location;
|
||||
return (1.0f - HitResult.Time);
|
||||
}
|
||||
}
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
case(EPenTraceType::PT_ByComponent): {
|
||||
if (!Component.IsValid()) return 1.0f;
|
||||
bool Hit = Component->LineTraceComponent(Result, EndLocation, StartLocation, QueryParams);
|
||||
if (!Hit) return 1.0f;
|
||||
ExitNormal = Result.Normal;
|
||||
|
||||
@@ -101,18 +101,56 @@ float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEn
|
||||
#endif
|
||||
|
||||
float GrazingAngle = FMath::Pow(dot, GrazingAngleExponent);
|
||||
FVector PenetrationVector = RandomStream.VRandCone(Velocity, penEnterSpread);
|
||||
PenetrationVector = FMath::Lerp(PenetrationVector, -HitResult.Normal, FMath::Lerp(penNormalization, penNormalizationGrazing, GrazingAngle));
|
||||
|
||||
// Improved penetration vector calculation to prevent sideways exits
|
||||
FVector VelocityDirection = Velocity.GetSafeNormal();
|
||||
FVector SurfaceNormal = HitResult.Normal;
|
||||
|
||||
// Calculate the component of velocity that's perpendicular to the surface
|
||||
FVector VelocityParallel = VelocityDirection - FVector::DotProduct(VelocityDirection, SurfaceNormal) * SurfaceNormal;
|
||||
FVector VelocityPerpendicular = VelocityDirection - VelocityParallel;
|
||||
|
||||
// Base penetration direction should be primarily forward through the material
|
||||
FVector BasePenetrationDirection = VelocityPerpendicular.GetSafeNormal();
|
||||
if (BasePenetrationDirection.Size() < 0.1f) {
|
||||
// If velocity is parallel to surface, use surface normal
|
||||
BasePenetrationDirection = -SurfaceNormal;
|
||||
}
|
||||
|
||||
// Add controlled random spread
|
||||
FVector PenetrationVector = RandomStream.VRandCone(BasePenetrationDirection, penEnterSpread);
|
||||
|
||||
// Blend with surface normal based on normalization settings, but limit lateral deviation
|
||||
float NormalizationFactor = FMath::Lerp(penNormalization, penNormalizationGrazing, GrazingAngle);
|
||||
PenetrationVector = FMath::Lerp(PenetrationVector, -SurfaceNormal, NormalizationFactor);
|
||||
|
||||
// Ensure the penetration vector doesn't deviate too much from forward direction
|
||||
float ForwardComponent = FVector::DotProduct(PenetrationVector.GetSafeNormal(), VelocityDirection);
|
||||
if (ForwardComponent < 0.3f) { // If penetration is too sideways, correct it
|
||||
PenetrationVector = FMath::Lerp(PenetrationVector, VelocityDirection, 0.7f);
|
||||
}
|
||||
|
||||
PenetrationVector = PenetrationVector.GetSafeNormal();
|
||||
float PenetrationDistance = FMath::Lerp(MinPenetration, MaxPenetration, RandomStream.FRand()) * FMath::Pow((Velocity.Size() / ((MuzzleVelocityMin + MuzzleVelocityMax) * 0.5f)), 2.0f) * penDepthMultiplier;
|
||||
float PenetrationDepth = -FVector::DotProduct(PenetrationVector, HitResult.Normal) * PenetrationDistance;
|
||||
|
||||
// Calculate angle factor to reduce penetration for grazing impacts
|
||||
float AngleFactor = FMath::Abs(FVector::DotProduct(PenetrationVector.GetSafeNormal(), HitResult.Normal));
|
||||
AngleFactor = FMath::Max(AngleFactor, 0.1f); // Prevent division by zero, minimum 10% effectiveness
|
||||
|
||||
// PenetrationDepth is the actual depth achieved, accounting for impact angle
|
||||
float PenetrationDepth = PenetrationDistance * AngleFactor;
|
||||
|
||||
float BlockTIme = 1.0f;
|
||||
|
||||
if (PenetrationDistance > 0.0f) {
|
||||
if (!neverPenetrate) {
|
||||
BlockTIme = PenetrationTrace(HitResult.Location - (HitResult.Normal * CollisionMargin), HitResult.Location + PenetrationVector * PenetrationDistance, HitResult.Component, PenTraceType, CollisionChannel, exitLoc, exitNormal);
|
||||
}
|
||||
// Improved penetration trace with better start location calculation
|
||||
FVector PenetrationStart = HitResult.Location + HitResult.Normal * 0.1f; // Small offset to avoid self-intersection
|
||||
FVector PenetrationEnd = HitResult.Location + PenetrationVector * PenetrationDistance;
|
||||
|
||||
BlockTIme = PenetrationTrace(PenetrationStart, PenetrationEnd, HitResult.Component, PenTraceType, CollisionChannel, exitLoc, exitNormal);
|
||||
}
|
||||
}
|
||||
|
||||
if (BlockTIme >= 0.999999f) {
|
||||
|
||||
@@ -141,11 +179,51 @@ float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEn
|
||||
//penetration
|
||||
float RemainingEnergy = FMath::Pow(1.0f - BlockTIme, 2.0f);
|
||||
SetActorLocation(exitLoc + exitNormal * CollisionMargin);
|
||||
NewVelocity = RandomStream.VRandCone(PenetrationVector, penExitSpread * (1.0f - RemainingEnergy));
|
||||
NewVelocity = FMath::Lerp(NewVelocity, Velocity.GetSafeNormal(), RemainingEnergy);
|
||||
NewVelocity *= RemainingEnergy * Velocity.Size();
|
||||
|
||||
// Improved exit velocity calculation to maintain forward momentum
|
||||
FVector OriginalDirection = Velocity.GetSafeNormal();
|
||||
|
||||
// Calculate exit direction with controlled spread
|
||||
FVector ExitDirection = RandomStream.VRandCone(PenetrationVector, penExitSpread * (1.0f - RemainingEnergy));
|
||||
|
||||
// Ensure exit direction has significant forward component
|
||||
float ExitForwardComponent = FVector::DotProduct(ExitDirection.GetSafeNormal(), OriginalDirection);
|
||||
if (ExitForwardComponent < 0.5f) {
|
||||
// If exit direction is too sideways, blend more with original direction
|
||||
ExitDirection = FMath::Lerp(ExitDirection, OriginalDirection, 0.6f);
|
||||
}
|
||||
|
||||
// Blend with original velocity direction to maintain trajectory continuity
|
||||
NewVelocity = FMath::Lerp(ExitDirection, OriginalDirection, RemainingEnergy * 0.8f);
|
||||
NewVelocity = NewVelocity.GetSafeNormal() * RemainingEnergy * Velocity.Size();
|
||||
|
||||
Penetration = true;
|
||||
OwnerSafe = false;
|
||||
|
||||
// CRITICAL FIX: Ensure trace continues after penetration
|
||||
// Calculate how much of the original trace distance was used for penetration
|
||||
float PenetrationTraceDistance = (exitLoc - HitResult.Location).Size();
|
||||
float OriginalTraceDistance = (start + (PreviousVelocity + Velocity)*0.5*delta - start).Size();
|
||||
|
||||
if (OriginalTraceDistance > 0.1f) {
|
||||
// Adjust HitResult.Time to account for penetration distance
|
||||
float PenetrationTimeFraction = FMath::Clamp(PenetrationTraceDistance / OriginalTraceDistance, 0.0f, 0.9f);
|
||||
HitResult.Time = FMath::Min(HitResult.Time + PenetrationTimeFraction, 0.95f); // Leave some time for continuation
|
||||
}
|
||||
|
||||
#ifdef WITH_EDITOR
|
||||
// Debug visualization for penetration issues
|
||||
if (DebugEnabled) {
|
||||
// Draw entry point (red)
|
||||
DrawDebugSphere(GetWorld(), HitResult.Location, 2.0f, 8, FColor::Red, false, DebugTrailTime);
|
||||
// Draw exit point (green)
|
||||
DrawDebugSphere(GetWorld(), exitLoc, 2.0f, 8, FColor::Green, false, DebugTrailTime);
|
||||
// Draw penetration path (yellow)
|
||||
DrawDebugLine(GetWorld(), HitResult.Location, exitLoc, FColor::Yellow, false, DebugTrailTime, 0, 1.0f);
|
||||
// Draw exit normal (blue)
|
||||
DrawDebugLine(GetWorld(), exitLoc, exitLoc + exitNormal * 10.0f, FColor::Blue, false, DebugTrailTime, 0, 1.0f);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -37,6 +37,19 @@ public:
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") float DebugArrowSize = 100.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") bool DebugImpactInfo = false;
|
||||
|
||||
// Enhanced Debug Controls
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBarrel|Debug")
|
||||
void SetBulletDebugCategory(const FString& CategoryName, bool bEnabled);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBarrel|Debug")
|
||||
void SetAllBulletDebugCategories(bool bEnabled);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBarrel|Debug")
|
||||
void ClearAllBulletDebugDisplay();
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "EBBarrel|Debug")
|
||||
FString GetBulletDebugInfo() const;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Velocity", meta = (ToolTip = "Bullet inherits barrel velocity, only works with physics enabled or with additional velocity set")) float InheritVelocity = 1.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Velocity", meta = (ToolTip = "Amount of recoil applied to the barrel, only works with physics enabled")) float RecoilMultiplier = 1.0f;
|
||||
|
||||
@@ -43,12 +43,74 @@ public:
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, Category = "State") bool OwnerSafe=false;
|
||||
|
||||
// Enhanced Debug System
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") bool DebugEnabled;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") float DebugTrailTime=1.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") float DebugTrailWidth=0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") FLinearColor DebugTrailColorFast = FLinearColor(0, 1, 0, 1);
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") FLinearColor DebugTrailColorSlow = FLinearColor(1, 0, 0, 1);
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug") bool DebugPooling;
|
||||
|
||||
// Debug Categories
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Categories") bool DebugTrajectory = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Categories") bool DebugImpact = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Categories") bool DebugPhysics = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Categories") bool DebugPerformance = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Categories") bool DebugBallistics = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Categories") bool DebugSpalling = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Categories") bool DebugPooling = false;
|
||||
|
||||
// Trajectory Debug Options
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) bool ShowTrail = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) bool ShowVelocityVectors = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) bool ShowPathPrediction = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) float DebugTrailTime = 1.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) float DebugTrailWidth = 0.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) FLinearColor DebugTrailColorFast = FLinearColor(0, 1, 0, 1);
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) FLinearColor DebugTrailColorSlow = FLinearColor(1, 0, 0, 1);
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) float VelocityVectorScale = 0.01f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Trajectory", meta = (EditCondition = "DebugEnabled && DebugTrajectory")) int32 PathPredictionSteps = 10;
|
||||
|
||||
// Impact Debug Options
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Impact", meta = (EditCondition = "DebugEnabled && DebugImpact")) bool ShowImpactPoints = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Impact", meta = (EditCondition = "DebugEnabled && DebugImpact")) bool ShowPenetrationDepth = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Impact", meta = (EditCondition = "DebugEnabled && DebugImpact")) bool ShowRicochetAngles = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Impact", meta = (EditCondition = "DebugEnabled && DebugImpact")) bool ShowMaterialInfo = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Impact", meta = (EditCondition = "DebugEnabled && DebugImpact")) bool ShowImpactNormals = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Impact", meta = (EditCondition = "DebugEnabled && DebugImpact")) float ImpactDebugTime = 10.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Impact", meta = (EditCondition = "DebugEnabled && DebugImpact")) float ImpactPointSize = 5.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Impact", meta = (EditCondition = "DebugEnabled && DebugImpact")) FLinearColor ImpactColorPenetration = FLinearColor(0, 1, 0, 1);
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Impact", meta = (EditCondition = "DebugEnabled && DebugImpact")) FLinearColor ImpactColorRicochet = FLinearColor(1, 1, 0, 1);
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Impact", meta = (EditCondition = "DebugEnabled && DebugImpact")) FLinearColor ImpactColorStopped = FLinearColor(1, 0, 0, 1);
|
||||
|
||||
// Physics Debug Options
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Physics", meta = (EditCondition = "DebugEnabled && DebugPhysics")) bool ShowDragForces = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Physics", meta = (EditCondition = "DebugEnabled && DebugPhysics")) bool ShowGravityEffect = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Physics", meta = (EditCondition = "DebugEnabled && DebugPhysics")) bool ShowWindEffect = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Physics", meta = (EditCondition = "DebugEnabled && DebugPhysics")) bool ShowAtmosphericData = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Physics", meta = (EditCondition = "DebugEnabled && DebugPhysics")) float PhysicsDebugTime = 5.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Physics", meta = (EditCondition = "DebugEnabled && DebugPhysics")) float ForceVectorScale = 0.001f;
|
||||
|
||||
// Performance Debug Options
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Performance", meta = (EditCondition = "DebugEnabled && DebugPerformance")) bool ShowTraceCount = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Performance", meta = (EditCondition = "DebugEnabled && DebugPerformance")) bool ShowFrameTiming = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Performance", meta = (EditCondition = "DebugEnabled && DebugPerformance")) bool ShowPoolingStats = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Performance", meta = (EditCondition = "DebugEnabled && DebugPerformance")) float PerformanceDebugTime = 3.0f;
|
||||
|
||||
// Ballistics Debug Options
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Ballistics", meta = (EditCondition = "DebugEnabled && DebugBallistics")) bool ShowEnergyCalculations = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Ballistics", meta = (EditCondition = "DebugEnabled && DebugBallistics")) bool ShowMathematicalComparison = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Ballistics", meta = (EditCondition = "DebugEnabled && DebugBallistics")) bool ShowBallisticCoefficient = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Ballistics", meta = (EditCondition = "DebugEnabled && DebugBallistics")) float BallisticsDebugTime = 8.0f;
|
||||
|
||||
// Spalling Debug Options
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Spalling", meta = (EditCondition = "DebugEnabled && DebugSpalling")) bool ShowSpallGeneration = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Spalling", meta = (EditCondition = "DebugEnabled && DebugSpalling")) bool ShowFragmentTrajectories = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Spalling", meta = (EditCondition = "DebugEnabled && DebugSpalling")) bool ShowSpallCones = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Spalling", meta = (EditCondition = "DebugEnabled && DebugSpalling")) float SpallDebugTime = 5.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Spalling", meta = (EditCondition = "DebugEnabled && DebugSpalling")) FLinearColor SpallColorPrimary = FLinearColor(1, 0.5f, 0, 1);
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Spalling", meta = (EditCondition = "DebugEnabled && DebugSpalling")) FLinearColor SpallColorFragment = FLinearColor(0.8f, 0.4f, 0, 1);
|
||||
|
||||
// Debug Display Options
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Display") bool UseWorldSpaceText = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Display") bool UseOnScreenMessages = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Display") float WorldTextScale = 1.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Debug|Display") int32 MaxOnScreenMessages = 10;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "World") FVector Wind;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, SaveGame, Category = "World", meta = (ToolTip = "Select atmosphere model")) EEBAtmosphereType AtmosphereType;
|
||||
@@ -82,8 +144,8 @@ public:
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Shotgun", meta = (EditCondition = "Shotgun")) float ShotSpread=0.01;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Shotgun", meta = (EditCondition = "Shotgun")) float ShotVelocitySpread = 0.01;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight") float MuzzleVelocityMin = 100000.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight") float MuzzleVelocityMax = 100000.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight", meta = (ToolTip = "Minimum muzzle velocity in cm/s (Unreal units)")) float MuzzleVelocityMin = 91440.0; // ~3000 fps in cm/s
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight", meta = (ToolTip = "Maximum muzzle velocity in cm/s (Unreal units)")) float MuzzleVelocityMax = 91440.0; // ~3000 fps in cm/s
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight", meta = (ToolTip = "Maximum bullet spread, in radians", ClampMin = "0")) float Spread = 0.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Flight", meta = (ToolTip = "Spread bias, higher is more accurate on average", ClampMin = "0")) float SpreadBias = 0.0f;
|
||||
// Physics Mode Toggle
|
||||
@@ -162,7 +224,7 @@ public:
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision", meta = (ToolTip = "Allow components to collide, intended for use with trigger volumes. Do not use for actual collisions.")) bool AllowComponentCollisions = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision") TEnumAsByte<ECollisionChannel> TraceChannel;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision") bool TraceComplex;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision") float CollisionMargin=1.0;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision", meta = (ToolTip = "Margin to prevent surface clipping (cm). Lower values improve accuracy but may cause clipping.", ClampMin = "0.01", ClampMax = "5.0")) float CollisionMargin = 0.1;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision", meta = (ToolTip = "Bullets with lower velocity will automatically despawn on impact, never despawn if set to zero or negative")) float DespawnVelocity=100.0f;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Collision") TArray<AActor*> IgnoredActors;
|
||||
|
||||
@@ -170,7 +232,7 @@ public:
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation", meta = (EditCondition = "DoFirstStepImmediately")) bool RandomFirstStepDelta = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation") bool FixedStep = false;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation", meta = (EditCondition = "FixedStep", ClampMin = "0")) float FixedStepSeconds = 0.1;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation") int MaxTracesPerStep = 8;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Simulation", meta = (ToolTip = "Maximum number of surfaces to process per frame. Higher values allow penetrating more walls but may impact performance.", ClampMin = "1", ClampMax = "20")) int MaxTracesPerStep = 12;
|
||||
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Retrace") bool Retrace = true;
|
||||
UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Retrace") bool RetraceOnAnotherChannel = false;
|
||||
@@ -242,12 +304,40 @@ public:
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Mathematical Physics")
|
||||
FMathematicalMaterialProperties GetMaterialProperties(UPhysicalMaterial* Material) const;
|
||||
|
||||
// Enhanced Debug Functions
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Debug")
|
||||
void CreateDebugImpactWidget(FVector Location, bool bRicochet, bool bPenetration, FVector ImpactVelocity, float PenetrationDepth, UPhysicalMaterial* Material);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Debug")
|
||||
void DrawTrajectoryDebug(FVector StartLocation, FVector EndLocation, FVector CurrentVelocity, float DeltaTime);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Debug")
|
||||
void DrawImpactDebug(FVector ImpactLocation, FVector ImpactVelocity, FVector ImpactNormal, bool bRicochet, bool bPenetration, float PenetrationDepth, UPhysicalMaterial* Material);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Debug")
|
||||
void DrawPhysicsDebug(FVector Location, FVector DragForce, FVector GravityForce, FVector WindForce, float AirDensity, float SpeedOfSound);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Debug")
|
||||
void DrawBallisticsDebug(FVector Location, FVector BulletVelocity, float KineticEnergy, float DragCoefficient, float MachNumber);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Debug")
|
||||
void DrawSpallDebug(FVector ImpactLocation, FVector ImpactVelocity, FVector ImpactNormal, TArray<FVector> FragmentVelocities);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Debug")
|
||||
void DrawPerformanceDebug(FVector Location, int32 TraceCount, float FrameTime, int32 PooledBullets);
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Debug")
|
||||
void ClearDebugDisplay();
|
||||
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Debug")
|
||||
void ToggleDebugCategory(const FString& CategoryName, bool bEnabled);
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "EBBullet|Debug")
|
||||
class UEBBarrel* GetFiringBarrel() const { return FiringBarrel; }
|
||||
|
||||
UFUNCTION(BlueprintPure, Category = "EBBullet|Debug")
|
||||
FString GetDebugInfoString() const;
|
||||
|
||||
// Spalling Functions
|
||||
UFUNCTION(BlueprintCallable, Category = "EBBullet|Spalling")
|
||||
void GenerateSpallFragments(FVector ImpactLocation, FVector ImpactVelocity, FVector ImpactNormal, UPhysicalMaterial* Material, AActor* HitActor);
|
||||
@@ -286,6 +376,9 @@ private:
|
||||
TArray<AActor*> GetAttachedActorsRecursive(AActor* Actor, uint16 Depth = 0, TArray<AActor*> VisitedActors = TArray<AActor*>()) const;
|
||||
|
||||
float PenetrationTrace(FVector start, FVector end, TWeakObjectPtr<UPrimitiveComponent,FWeakObjectPtr> comp, EPenTraceType penType, TEnumAsByte<ECollisionChannel> channel, FVector &exitLoc, FVector &exitNormal);
|
||||
|
||||
// Helper function to process multiple hits in sequence (for close walls)
|
||||
bool ProcessSequentialHits(TArray<FHitResult>& AllHits, FVector& CurrentVelocity, float DeltaTime, int32& ProcessedHitCount);
|
||||
|
||||
float GetCurveValue(const UCurveFloat* curve, float in, float deflt) const;
|
||||
|
||||
@@ -304,6 +397,15 @@ private:
|
||||
|
||||
class UEBBarrel* FiringBarrel;
|
||||
|
||||
// Debug tracking variables
|
||||
TArray<FVector> DebugTrajectoryPoints;
|
||||
TArray<float> DebugTrajectoryTimes;
|
||||
TArray<FVector> DebugImpactLocations;
|
||||
TArray<float> DebugImpactTimes;
|
||||
int32 DebugTraceCounter;
|
||||
float DebugFrameStartTime;
|
||||
static int32 DebugMessageCounter;
|
||||
|
||||
float GetAltitude(UWorld* World, FVector Location) const;
|
||||
float GetAltitudePressure(float AltitudeMeter) const;
|
||||
float GetAltitudeTemperature(float AltitudeMeter) const;
|
||||
|
||||
@@ -111,10 +111,19 @@ struct FMathematicalBulletProperties
|
||||
{
|
||||
return SectionalDensity;
|
||||
}
|
||||
// SD = Weight(grains) / (7000 * Diameter^2(inches))
|
||||
// Traditional SD = Weight(grains) / (7000 * Diameter^2(inches)) - dimensionless
|
||||
return GrainWeight / (7000.0f * DiameterInches * DiameterInches);
|
||||
}
|
||||
|
||||
// Calculate sectional density in proper SI units (kg/m²) for mathematical ballistics
|
||||
float GetSectionalDensityKgPerM2() const
|
||||
{
|
||||
float MassKg = GetMassKg();
|
||||
float DiameterM = DiameterInches * 0.0254f; // Convert inches to meters
|
||||
float CrossSectionM2 = 3.14159f * (DiameterM/2.0f) * (DiameterM/2.0f);
|
||||
return MassKg / CrossSectionM2;
|
||||
}
|
||||
|
||||
// Calculate mass in kilograms
|
||||
float GetMassKg() const
|
||||
{
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
// Copyright 2016 Mookie. All Rights Reserved.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CoreMinimal.h"
|
||||
|
||||
/**
|
||||
* Centralized unit conversion utilities for EasyBallistics
|
||||
* This class ensures consistent unit conversions throughout the plugin
|
||||
*/
|
||||
class EASYBALLISTICS_API FEBUnitConversions
|
||||
{
|
||||
public:
|
||||
// === VELOCITY CONVERSIONS ===
|
||||
|
||||
/** Convert feet per second to meters per second */
|
||||
static float FPSToMPS(float FPS) { return FPS * 0.3048f; }
|
||||
|
||||
/** Convert meters per second to feet per second */
|
||||
static float MPSToFPS(float MPS) { return MPS / 0.3048f; }
|
||||
|
||||
/** Convert centimeters per second (Unreal units) to meters per second */
|
||||
static float CMPSToMPS(float CMPS) { return CMPS / 100.0f; }
|
||||
|
||||
/** Convert meters per second to centimeters per second (Unreal units) */
|
||||
static float MPSToCMPS(float MPS) { return MPS * 100.0f; }
|
||||
|
||||
/** Convert centimeters per second to kilometers per hour */
|
||||
static float CMPSToKMH(float CMPS) { return (CMPS / 100.0f) * 3.6f; }
|
||||
|
||||
/** Convert feet per second to centimeters per second (Unreal units) */
|
||||
static float FPSToCMPS(float FPS) { return FPS * 30.48f; }
|
||||
|
||||
/** Convert centimeters per second (Unreal units) to feet per second */
|
||||
static float CMPSToFPS(float CMPS) { return CMPS / 30.48f; }
|
||||
|
||||
// === DISTANCE CONVERSIONS ===
|
||||
|
||||
/** Convert inches to centimeters */
|
||||
static float InchesToCM(float Inches) { return Inches * 2.54f; }
|
||||
|
||||
/** Convert centimeters to inches */
|
||||
static float CMToInches(float CM) { return CM / 2.54f; }
|
||||
|
||||
/** Convert meters to centimeters */
|
||||
static float MetersToCM(float Meters) { return Meters * 100.0f; }
|
||||
|
||||
/** Convert centimeters to meters */
|
||||
static float CMToMeters(float CM) { return CM / 100.0f; }
|
||||
|
||||
/** Alias for CMToMeters for compatibility */
|
||||
static float CMToM(float CM) { return CMToMeters(CM); }
|
||||
|
||||
/** Convert feet to meters */
|
||||
static float FeetToMeters(float Feet) { return Feet * 0.3048f; }
|
||||
|
||||
/** Convert meters to feet */
|
||||
static float MetersToFeet(float Meters) { return Meters / 0.3048f; }
|
||||
|
||||
// === MASS CONVERSIONS ===
|
||||
|
||||
/** Convert grains to kilograms */
|
||||
static float GrainsToKG(float Grains) { return Grains * 0.0647989f / 1000.0f; }
|
||||
|
||||
/** Convert kilograms to grains */
|
||||
static float KGToGrains(float KG) { return KG * 1000.0f / 0.0647989f; }
|
||||
|
||||
/** Convert grains to grams */
|
||||
static float GrainsToGrams(float Grains) { return Grains * 0.0647989f; }
|
||||
|
||||
/** Convert grams to grains */
|
||||
static float GrainsToGrains(float Grams) { return Grams / 0.0647989f; }
|
||||
|
||||
// === DENSITY CONVERSIONS ===
|
||||
|
||||
/** Convert g/cm³ to kg/m³ */
|
||||
static float GPerCM3ToKGPerM3(float GPerCM3) { return GPerCM3 * 1000.0f; }
|
||||
|
||||
/** Convert kg/m³ to g/cm³ */
|
||||
static float KGPerM3ToGPerCM3(float KGPerM3) { return KGPerM3 / 1000.0f; }
|
||||
|
||||
// === PRESSURE CONVERSIONS ===
|
||||
|
||||
/** Convert MPa to Pa */
|
||||
static float MPaToPa(float MPa) { return MPa * 1000000.0f; }
|
||||
|
||||
/** Convert Pa to MPa */
|
||||
static float PaToMPa(float Pa) { return Pa / 1000000.0f; }
|
||||
|
||||
/** Convert PSI to MPa */
|
||||
static float PSIToMPa(float PSI) { return PSI * 0.00689476f; }
|
||||
|
||||
/** Convert MPa to PSI */
|
||||
static float MPaToPSI(float MPa) { return MPa / 0.00689476f; }
|
||||
|
||||
// === ENERGY CONVERSIONS ===
|
||||
|
||||
/** Convert foot-pounds to Joules */
|
||||
static float FtLbsToJoules(float FtLbs) { return FtLbs * 1.35582f; }
|
||||
|
||||
/** Convert Joules to foot-pounds */
|
||||
static float JoulesToFtLbs(float Joules) { return Joules / 1.35582f; }
|
||||
|
||||
// === ANGLE CONVERSIONS ===
|
||||
|
||||
/** Convert degrees to radians */
|
||||
static float DegreesToRadians(float Degrees) { return Degrees * PI / 180.0f; }
|
||||
|
||||
/** Convert radians to degrees */
|
||||
static float RadiansToDegrees(float Radians) { return Radians * 180.0f / PI; }
|
||||
|
||||
// === AREA CONVERSIONS ===
|
||||
|
||||
/** Convert square centimeters to square meters */
|
||||
static float CM2ToM2(float CM2) { return CM2 * 0.0001f; }
|
||||
|
||||
/** Convert square meters to square centimeters */
|
||||
static float M2ToCM2(float M2) { return M2 * 10000.0f; }
|
||||
|
||||
/** Convert square inches to square centimeters */
|
||||
static float Inch2ToCM2(float Inch2) { return Inch2 * 6.4516f; }
|
||||
|
||||
/** Convert square centimeters to square inches */
|
||||
static float CM2ToInch2(float CM2) { return CM2 / 6.4516f; }
|
||||
|
||||
// === HELPER FUNCTIONS ===
|
||||
|
||||
/** Calculate kinetic energy in Joules from mass (kg) and velocity (m/s) */
|
||||
static float CalculateKineticEnergyJoules(float MassKG, float VelocityMPS)
|
||||
{
|
||||
return 0.5f * MassKG * VelocityMPS * VelocityMPS;
|
||||
}
|
||||
|
||||
/** Calculate sectional density in kg/m² from mass (kg) and diameter (m) */
|
||||
static float CalculateSectionalDensityKGPerM2(float MassKG, float DiameterM)
|
||||
{
|
||||
float RadiusM = DiameterM / 2.0f;
|
||||
float CrossSectionM2 = PI * RadiusM * RadiusM;
|
||||
return MassKG / CrossSectionM2;
|
||||
}
|
||||
|
||||
/** Calculate cross-sectional area in m² from diameter in m */
|
||||
static float CalculateCrossSectionM2(float DiameterM)
|
||||
{
|
||||
float RadiusM = DiameterM / 2.0f;
|
||||
return PI * RadiusM * RadiusM;
|
||||
}
|
||||
|
||||
// === VALIDATION FUNCTIONS ===
|
||||
|
||||
/** Validate that a velocity value is reasonable for ballistics (returns true if valid) */
|
||||
static bool IsValidBallisticVelocity(float VelocityMPS)
|
||||
{
|
||||
return VelocityMPS >= 0.0f && VelocityMPS <= 2000.0f; // 0 to 2000 m/s is reasonable range
|
||||
}
|
||||
|
||||
/** Validate that a mass value is reasonable for bullets (returns true if valid) */
|
||||
static bool IsValidBulletMass(float MassKG)
|
||||
{
|
||||
return MassKG >= 0.001f && MassKG <= 0.1f; // 1g to 100g is reasonable range
|
||||
}
|
||||
|
||||
/** Validate that a diameter value is reasonable for bullets (returns true if valid) */
|
||||
static bool IsValidBulletDiameter(float DiameterCM)
|
||||
{
|
||||
return DiameterCM >= 0.1f && DiameterCM <= 5.0f; // 1mm to 50mm is reasonable range
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user