// Copyright 2020 Mookie. All Rights Reserved. #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) { return; } UWorld* World = GetWorld(); if (!World) { 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 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, VelocityKMH, GetEffectiveMass(), GetEffectiveDiameter(), KineticEnergy, PenetrationDepth ); // Fix color conversion for display uint8 R = FMath::Clamp(FMath::RoundToInt(ImpactColor.R * 255.0f), 0, 255); uint8 G = FMath::Clamp(FMath::RoundToInt(ImpactColor.G * 255.0f), 0, 255); uint8 B = FMath::Clamp(FMath::RoundToInt(ImpactColor.B * 255.0f), 0, 255); FColor DisplayColor(R, G, B); // Display debug information if (UseOnScreenMessages && GEngine) { // Manage message count if (DebugMessageCounter > MaxOnScreenMessages) { DebugMessageCounter = 0; } GEngine->AddOnScreenDebugMessage(DebugMessageCounter++, ImpactDebugTime, DisplayColor, DebugInfo); } if (UseWorldSpaceText) { DrawDebugString(World, Location + FVector(0, 0, 50), DebugInfo, nullptr, DisplayColor, 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; // Fix color conversion - ensure valid range [0-255] uint8 R = FMath::Clamp(FMath::RoundToInt(TrailColor.R * 255.0f), 0, 255); uint8 G = FMath::Clamp(FMath::RoundToInt(TrailColor.G * 255.0f), 0, 255); uint8 B = FMath::Clamp(FMath::RoundToInt(TrailColor.B * 255.0f), 0, 255); uint8 A = FMath::Clamp(FMath::RoundToInt(TrailColor.A * 255.0f), 0, 255); float LineThickness = DebugTrailWidth > 0 ? DebugTrailWidth : 1.0f; DrawDebugLine(World, DebugTrajectoryPoints[i-1], DebugTrajectoryPoints[i], FColor(R, G, B, A), 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::DrawXRayTrajectory(FVector EntryPoint, FVector ExitPoint, FVector PenetrationDirection, float PenetrationDepth, UPhysicalMaterial* Material) { if (!DebugEnabled || !DebugTrajectory || !ShowXRayTrajectory) { return; } UWorld* World = GetWorld(); if (!World) { return; } // Draw entry point marker uint8 XR = FMath::Clamp(FMath::RoundToInt(XRayTrajectoryColor.R * 255.0f), 0, 255); uint8 XG = FMath::Clamp(FMath::RoundToInt(XRayTrajectoryColor.G * 255.0f), 0, 255); uint8 XB = FMath::Clamp(FMath::RoundToInt(XRayTrajectoryColor.B * 255.0f), 0, 255); uint8 XA = FMath::Clamp(FMath::RoundToInt(XRayTrajectoryColor.A * 255.0f), 0, 255); FColor XRayColor(XR, XG, XB, XA); DrawDebugSphere(World, EntryPoint, 3.0f, 8, XRayColor, false, DebugTrailTime); // Draw internal trajectory path (X-ray view) FVector InternalTrajectory = EntryPoint + PenetrationDirection * PenetrationDepth; // Draw dashed line to represent path through material int32 DashCount = FMath::Max(1, FMath::RoundToInt(PenetrationDepth / 5.0f)); // Dash every 5cm for (int32 i = 0; i < DashCount; i++) { float StartRatio = (float)i / DashCount; float EndRatio = FMath::Min(1.0f, (float)(i + 0.7f) / DashCount); // 70% dash, 30% gap FVector DashStart = FMath::Lerp(EntryPoint, InternalTrajectory, StartRatio); FVector DashEnd = FMath::Lerp(EntryPoint, InternalTrajectory, EndRatio); DrawDebugLine(World, DashStart, DashEnd, XRayColor, false, DebugTrailTime, 0, 2.0f); } // Draw exit point if it exists if (!ExitPoint.IsNearlyZero() && !EntryPoint.Equals(ExitPoint, 1.0f)) { uint8 PR = FMath::Clamp(FMath::RoundToInt(PenetrationTrajectoryColor.R * 255.0f), 0, 255); uint8 PG = FMath::Clamp(FMath::RoundToInt(PenetrationTrajectoryColor.G * 255.0f), 0, 255); uint8 PB = FMath::Clamp(FMath::RoundToInt(PenetrationTrajectoryColor.B * 255.0f), 0, 255); uint8 PA = FMath::Clamp(FMath::RoundToInt(PenetrationTrajectoryColor.A * 255.0f), 0, 255); FColor PenetrationColor(PR, PG, PB, PA); DrawDebugSphere(World, ExitPoint, 3.0f, 8, PenetrationColor, false, DebugTrailTime); // Draw penetration path from calculated internal point to actual exit DrawDebugLine(World, InternalTrajectory, ExitPoint, PenetrationColor, false, DebugTrailTime, 0, 2.0f); } // Add material and penetration info if (Material && UseWorldSpaceText) { FString MaterialInfo = FString::Printf(TEXT("Material: %s\nPenetration: %.1f cm\nPath Length: %.1f cm"), *Material->GetName(), PenetrationDepth, FVector::Dist(EntryPoint, ExitPoint)); DrawDebugString(World, EntryPoint + FVector(0, 0, 25), MaterialInfo, nullptr, XRayColor, DebugTrailTime, false, WorldTextScale); } } 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; } // Fix color conversion uint8 R = FMath::Clamp(FMath::RoundToInt(ImpactColor.R * 255.0f), 0, 255); uint8 G = FMath::Clamp(FMath::RoundToInt(ImpactColor.G * 255.0f), 0, 255); uint8 B = FMath::Clamp(FMath::RoundToInt(ImpactColor.B * 255.0f), 0, 255); FColor DebugColor(R, G, B); // 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 FragmentVelocities) { if (!DebugEnabled || !DebugSpalling) { return; } UWorld* World = GetWorld(); if (!World) { return; } // Fix color conversion for spall colors uint8 PR = FMath::Clamp(FMath::RoundToInt(SpallColorPrimary.R * 255.0f), 0, 255); uint8 PG = FMath::Clamp(FMath::RoundToInt(SpallColorPrimary.G * 255.0f), 0, 255); uint8 PB = FMath::Clamp(FMath::RoundToInt(SpallColorPrimary.B * 255.0f), 0, 255); FColor PrimaryColor(PR, PG, PB); uint8 FR = FMath::Clamp(FMath::RoundToInt(SpallColorFragment.R * 255.0f), 0, 255); uint8 FG = FMath::Clamp(FMath::RoundToInt(SpallColorFragment.G * 255.0f), 0, 255); uint8 FB = FMath::Clamp(FMath::RoundToInt(SpallColorFragment.B * 255.0f), 0, 255); FColor FragmentColor(FR, FG, FB); // 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 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() ); }