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) {
|
if (MaterialDensityControlsPenetrationDepth) {
|
||||||
penDepthMultiplier /= PhysMaterial->Density;
|
float SafeDensity = FMath::Max(PhysMaterial->Density, 0.001f);
|
||||||
|
penDepthMultiplier /= SafeDensity;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MaterialRestitutionControlsRicochet) {
|
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;
|
float dot = FVector::DotProduct(Velocity.GetSafeNormal(), HitResult.Normal) + 1.0f;
|
||||||
FVector cross = FVector::CrossProduct(Velocity.GetSafeNormal(), HitResult.Normal);
|
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
|
#ifdef WITH_EDITOR
|
||||||
if (DebugEnabled) {
|
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);
|
DrawDebugLine(GetWorld(), start, HitResult.Location, DebugColor, false, DebugTrailTime, 0, DebugTrailWidth);
|
||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
@ -103,32 +139,43 @@ float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEn
|
|||||||
float GrazingAngle = FMath::Pow(dot, GrazingAngleExponent);
|
float GrazingAngle = FMath::Pow(dot, GrazingAngleExponent);
|
||||||
FVector PenetrationVector = RandomStream.VRandCone(Velocity, penEnterSpread);
|
FVector PenetrationVector = RandomStream.VRandCone(Velocity, penEnterSpread);
|
||||||
PenetrationVector = FMath::Lerp(PenetrationVector, -HitResult.Normal, FMath::Lerp(penNormalization, penNormalizationGrazing, GrazingAngle));
|
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 PenetrationDepth = -FVector::DotProduct(PenetrationVector, HitResult.Normal) * PenetrationDistance;
|
||||||
|
|
||||||
float BlockTIme = 1.0f;
|
float BlockTime = 1.0f;
|
||||||
|
|
||||||
if (PenetrationDistance > 0.0f) {
|
if (PenetrationDistance > 0.0f) {
|
||||||
if (!neverPenetrate) {
|
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
|
//no pen
|
||||||
SetActorLocation(HitResult.Location + HitResult.Normal * CollisionMargin);
|
SetActorLocation(HitResult.Location + HitResult.Normal * CollisionMargin);
|
||||||
|
|
||||||
float ricThreshold = 1.0f;
|
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)) {
|
if (!neverRicochet && RandomStream.FRand() * ricThreshold < FMath::Lerp(RicochetProbability * ricProbMultiplier, RicochetProbabilityGrazing * ricProbMultiplier, GrazingAngle)) {
|
||||||
//bounce
|
//bounce
|
||||||
FVector bounceAngle = flat * dot * (1.0f - ricFriction);
|
FVector bounceAngle = flat * dot * (1.0f - ricFriction);
|
||||||
bounceAngle += HitResult.Normal * (1.0f - dot) * ricRestitution;
|
bounceAngle += HitResult.Normal * (1.0f - dot) * ricRestitution;
|
||||||
bounceAngle = RandomStream.VRandCone(bounceAngle, ricSpread) * bounceAngle.Size();
|
float bounceSize = bounceAngle.Size();
|
||||||
|
if (bounceSize > SMALL_NUMBER)
|
||||||
NewVelocity = bounceAngle * Velocity.Size();
|
{
|
||||||
|
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;
|
Ricochet = true;
|
||||||
OwnerSafe = false;
|
OwnerSafe = false;
|
||||||
}
|
}
|
||||||
@ -139,7 +186,7 @@ float AEBBullet::Trace(FVector start, FVector PreviousVelocity, float delta, TEn
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//penetration
|
//penetration
|
||||||
float RemainingEnergy = FMath::Pow(1.0f - BlockTIme, 2.0f);
|
float RemainingEnergy = FMath::Pow(1.0f - BlockTime, 2.0f);
|
||||||
SetActorLocation(exitLoc + exitNormal * CollisionMargin);
|
SetActorLocation(exitLoc + exitNormal * CollisionMargin);
|
||||||
NewVelocity = RandomStream.VRandCone(PenetrationVector, penExitSpread * (1.0f - RemainingEnergy));
|
NewVelocity = RandomStream.VRandCone(PenetrationVector, penExitSpread * (1.0f - RemainingEnergy));
|
||||||
NewVelocity = FMath::Lerp(NewVelocity, Velocity.GetSafeNormal(), 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
|
#ifdef WITH_EDITOR
|
||||||
if (DebugEnabled) {
|
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);
|
DrawDebugLine(GetWorld(), start, start + TraceDistance, Color.ToFColor(true), false, DebugTrailTime, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user