Fix penetration/ricochet bugs: division by zero, edge cases, incidence angle
P0 - Division by zero fixes: - Clamp PhysMaterial->Density to min 0.001 before division - Clamp MuzzleVelocity averages to min 1.0 in all divisions - Clamp PhysMaterial->Restitution to [0, 1] P1 - Edge case guards: - Stop bullet immediately when velocity is near-zero (prevents NaN) - Handle near-zero cross product at very shallow grazing angles - Handle zero-length bounceAngle in ricochet calculation P2 - Improvements: - Fix typo: BlockTIme -> BlockTime - Add incidence angle factor to penetration depth: grazing shots penetrate less (5% at ~5deg) while head-on shots get full depth Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
ba6b35b3d9
commit
58df608550
@ -80,22 +80,58 @@ float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEn
|
||||
}
|
||||
|
||||
if (MaterialDensityControlsPenetrationDepth) {
|
||||
penDepthMultiplier /= PhysMaterial->Density;
|
||||
float SafeDensity = FMath::Max(PhysMaterial->Density, 0.001f);
|
||||
penDepthMultiplier /= SafeDensity;
|
||||
}
|
||||
|
||||
if (MaterialRestitutionControlsRicochet) {
|
||||
RicochetRestitution *= PhysMaterial->Restitution;
|
||||
RicochetRestitution *= FMath::Clamp(PhysMaterial->Restitution, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Guard: if bullet has near-zero velocity, stop it immediately
|
||||
if (Velocity.SizeSquared() < SMALL_NUMBER)
|
||||
{
|
||||
SetActorLocation(HitResult.Location + HitResult.Normal * CollisionMargin);
|
||||
FVector Impulse = Velocity * Mass * ImpulseMultiplier;
|
||||
if (AddImpulse && HitResult.Component->IsSimulatingPhysics()) {
|
||||
HitResult.Component->AddImpulseAtLocation(Impulse, HitResult.Location, HitResult.BoneName);
|
||||
}
|
||||
if (HasAuthority()) {
|
||||
OnImpact(false, false, HitResult.Location, Velocity, HitResult.Normal, GetActorLocation(), FVector::ZeroVector, Impulse, 0.0f, HitResult.GetActor(), HitResult.Component.Get(), HitResult.BoneName, PhysMaterial, HitResult, fireEventID);
|
||||
} else {
|
||||
OnNetPredictedImpact(false, false, HitResult.Location, Velocity, HitResult.Normal, GetActorLocation(), FVector::ZeroVector, Impulse, 0.0f, HitResult.GetActor(), HitResult.Component.Get(), HitResult.BoneName, PhysMaterial, HitResult, fireEventID);
|
||||
}
|
||||
Velocity = FVector::ZeroVector;
|
||||
Deactivate();
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float dot = FVector::DotProduct(Velocity.GetSafeNormal(), HitResult.Normal) + 1.0f;
|
||||
FVector cross = FVector::CrossProduct(Velocity.GetSafeNormal(), HitResult.Normal);
|
||||
FVector flat = HitResult.Normal.RotateAngleAxis(-90.0f, cross);
|
||||
|
||||
// Guard: near-zero cross product at very shallow grazing angles
|
||||
FVector flat;
|
||||
if (cross.SizeSquared() < SMALL_NUMBER)
|
||||
{
|
||||
// Bullet nearly parallel to surface: project velocity onto surface plane
|
||||
flat = FVector::VectorPlaneProject(Velocity.GetSafeNormal(), HitResult.Normal).GetSafeNormal();
|
||||
if (flat.IsNearlyZero())
|
||||
{
|
||||
flat = FMath::Abs(HitResult.Normal.Z) < 0.9f
|
||||
? FVector::CrossProduct(HitResult.Normal, FVector::UpVector).GetSafeNormal()
|
||||
: FVector::CrossProduct(HitResult.Normal, FVector::RightVector).GetSafeNormal();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
flat = HitResult.Normal.RotateAngleAxis(-90.0f, cross);
|
||||
}
|
||||
|
||||
#ifdef WITH_EDITOR
|
||||
if (DebugEnabled) {
|
||||
FColor DebugColor = FColor::MakeRedToGreenColorFromScalar(Velocity.Size() / MuzzleVelocityMax);
|
||||
FColor DebugColor = FColor::MakeRedToGreenColorFromScalar(Velocity.Size() / FMath::Max(MuzzleVelocityMax, 1.0f));
|
||||
DrawDebugLine(GetWorld(), start, HitResult.Location, DebugColor, false, DebugTrailTime, 0, DebugTrailWidth);
|
||||
};
|
||||
#endif
|
||||
@ -103,32 +139,43 @@ float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEn
|
||||
float GrazingAngle = FMath::Pow(dot, GrazingAngleExponent);
|
||||
FVector PenetrationVector = RandomStream.VRandCone(Velocity, penEnterSpread);
|
||||
PenetrationVector = FMath::Lerp(PenetrationVector, -HitResult.Normal, FMath::Lerp(penNormalization, penNormalizationGrazing, GrazingAngle));
|
||||
float PenetrationDistance = FMath::Lerp(MinPenetration, MaxPenetration, RandomStream.FRand()) * FMath::Pow((Velocity.Size() / ((MuzzleVelocityMin + MuzzleVelocityMax) * 0.5f)), 2.0f) * penDepthMultiplier;
|
||||
float AvgMuzzleVelocity = FMath::Max((MuzzleVelocityMin + MuzzleVelocityMax) * 0.5f, 1.0f);
|
||||
// Incidence angle factor: head-on (dot=2) -> full depth, grazing (dot=0) -> minimal depth
|
||||
float IncidenceFactor = FMath::Clamp(dot * 0.5f, 0.05f, 1.0f);
|
||||
float PenetrationDistance = FMath::Lerp(MinPenetration, MaxPenetration, RandomStream.FRand()) * FMath::Pow((Velocity.Size() / AvgMuzzleVelocity), 2.0f) * penDepthMultiplier * IncidenceFactor;
|
||||
float PenetrationDepth = -FVector::DotProduct(PenetrationVector, HitResult.Normal) * PenetrationDistance;
|
||||
|
||||
float BlockTIme = 1.0f;
|
||||
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);
|
||||
BlockTime = PenetrationTrace(HitResult.Location - (HitResult.Normal * CollisionMargin), HitResult.Location + PenetrationVector * PenetrationDistance, HitResult.Component, PenTraceType, CollisionChannel, exitLoc, exitNormal);
|
||||
}
|
||||
}
|
||||
|
||||
if (BlockTIme >= 0.999999f) {
|
||||
if (BlockTime >= 0.999999f) {
|
||||
|
||||
//no pen
|
||||
SetActorLocation(HitResult.Location + HitResult.Normal * CollisionMargin);
|
||||
|
||||
float ricThreshold = 1.0f;
|
||||
if (SpeedControlsRicochetProbability) { ricThreshold *= Velocity.Size() / MuzzleVelocityMax; };
|
||||
if (SpeedControlsRicochetProbability) { ricThreshold *= Velocity.Size() / FMath::Max(MuzzleVelocityMax, 1.0f); };
|
||||
|
||||
if (!neverRicochet && RandomStream.FRand() * ricThreshold < FMath::Lerp(RicochetProbability * ricProbMultiplier, RicochetProbabilityGrazing * ricProbMultiplier, GrazingAngle)) {
|
||||
//bounce
|
||||
FVector bounceAngle = flat * dot * (1.0f - ricFriction);
|
||||
bounceAngle += HitResult.Normal * (1.0f - dot) * ricRestitution;
|
||||
bounceAngle = RandomStream.VRandCone(bounceAngle, ricSpread) * bounceAngle.Size();
|
||||
|
||||
NewVelocity = bounceAngle * Velocity.Size();
|
||||
float bounceSize = bounceAngle.Size();
|
||||
if (bounceSize > SMALL_NUMBER)
|
||||
{
|
||||
bounceAngle = RandomStream.VRandCone(bounceAngle, ricSpread) * bounceSize;
|
||||
NewVelocity = bounceAngle * Velocity.Size();
|
||||
}
|
||||
else
|
||||
{
|
||||
// bounceAngle is zero (head-on + high friction + low restitution): reflect off normal
|
||||
NewVelocity = RandomStream.VRandCone(HitResult.Normal, ricSpread) * Velocity.Size() * ricRestitution;
|
||||
}
|
||||
Ricochet = true;
|
||||
OwnerSafe = false;
|
||||
}
|
||||
@ -139,7 +186,7 @@ float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEn
|
||||
}
|
||||
else {
|
||||
//penetration
|
||||
float RemainingEnergy = FMath::Pow(1.0f - BlockTIme, 2.0f);
|
||||
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);
|
||||
@ -189,7 +236,7 @@ float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEn
|
||||
|
||||
#ifdef WITH_EDITOR
|
||||
if (DebugEnabled) {
|
||||
FLinearColor Color = GetDebugColor(Velocity.Size() / ((MuzzleVelocityMin + MuzzleVelocityMax)*0.5f));
|
||||
FLinearColor Color = GetDebugColor(Velocity.Size() / FMath::Max((MuzzleVelocityMin + MuzzleVelocityMax)*0.5f, 1.0f));
|
||||
DrawDebugLine(GetWorld(), start, start + TraceDistance, Color.ToFColor(true), false, DebugTrailTime, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user