TwoWallAdjust gets stuck on corners less than 90 degrees

Hello. I have encountered an issue with the TwoWallAdjust function available through the MovementComponent class.

The issue is that in some cases the function results in the Pawn freezing mid air between two walls. Once stuck there’s no way of getting out with the function always returning a Delta of of near 0.

I made a Gif album which demonstrates the problem:

(The gifs can be slow once you load the album. Let them play once and they will play smoothly thereafter)

Pay attention to the Blue numbers. They are the size of the Delta vector that the TwoWallAdjust function returns. I repeat the function 5 times to account for collisions with more than two walls. Notice how in the normal case (The 90 Degree wall) only the first call to the function returns a change in Delta. The 4 other calls after that all return 0.0 meaning the the collision has been solved and the capsule collision slides down the wall normally. In the cases in which the capsule gets stuck, the function keeps returning new Delta Values for every call, which leads me to believe that the new slide angle it finds keep pushing the capsule into the corner, making it appear stuck and floating mid air.

Also, the Red number is the size of the Velocity vector.

Here’s the code for the TwoWallAdjust function:

void UMovementComponent::TwoWallAdjust(FVector& OutDelta, const FHitResult& Hit, const FVector& OldHitNormal) const
{
	FVector Delta = OutDelta;
	const FVector HitNormal = Hit.Normal;

	if ((OldHitNormal | HitNormal) <= 0.f) //90 or less corner, so use cross product for direction
	{
		const FVector DesiredDir = Delta;
		FVector NewDir = (HitNormal ^ OldHitNormal);
		NewDir = NewDir.GetSafeNormal();
		Delta = (Delta | NewDir) * (1.f - Hit.Time) * NewDir;
		if ((DesiredDir | Delta) < 0.f)
		{
			Delta = -1.f * Delta;
		}
	}
	else //adjust to new wall
	{
		const FVector DesiredDir = Delta;
		Delta = ComputeSlideVector(Delta, 1.f - Hit.Time, HitNormal, Hit);
		if ((Delta | DesiredDir) <= 0.f)
		{
			Delta = FVector::ZeroVector;
		}
		else if ( FMath::Abs((HitNormal | OldHitNormal) - 1.f) < KINDA_SMALL_NUMBER )
		{
			// we hit the same wall again even after adjusting to move along it the first time
			// nudge away from it (this can happen due to precision issues)
			Delta += HitNormal * 0.01f;
		}
	}

	OutDelta = Delta;
}

How Can I fix this issue? I’ve tried looking elsewhere online, but none seems to give a clear answer on how to fix this issue.

I know it has something to do with this part of the function:

if ((OldHitNormal | HitNormal) <= 0.f) //90 or less corner, so use cross product for direction
    	{
    		const FVector DesiredDir = Delta;
    		FVector NewDir = (HitNormal ^ OldHitNormal);
    		NewDir = NewDir.GetSafeNormal();
    		Delta = (Delta | NewDir) * (1.f - Hit.Time) * NewDir;
    		if ((DesiredDir | Delta) < 0.f)
    		{
    			Delta = -1.f * Delta;
    		}
    	}

It seems to do it’s job most of the time, but there’s something in it that goes wrong. I don’t know enough about trigonometry and vector math to see the issue myself.

Any suggestions or solutions would be much appreciated!

Best Regards

-Headstub

I managed to find a better case to show off the problem. I made my own copy of TwoWallAdjust and created a few PrintStrings to make it easier to debug what’s happening.

Here’s the link to the video: Unreal Collision Bug A001 - YouTube

Notice how everything is working fine and as intended when I first slide along the wall with a “Penetration Depth” of 0.0. It’s not until I hit the corner at a specific angle that the pawn gets stuck and the “Penetration Depth” changes to 10.021163. After that point, no matter when I do the “Penetration Depth” stays the same and the player seems permanently stuck. Sometimes it works to jump to get unstuck, but that’s only works in some cases.

The “Penetration Depth” is taken from the HitResult after the SafeMoveUpdatedComponent. I’m starting to think that the TwoWallAdjust is working as intended and that there’s instead some collision error happening making the player become stuck inside the wall. I’m not sure if this is due to me using the function wrong or an inherent problem with Unreal’s collision. I managed to do the same thing with the Character Blueprint, although it never got 100% stuck.

Okay so I managed to fix it. The problem was that during the TwoWallAdjust adjustment the capsule collision could clip into the wall due to a collision error, giving a Hit.PenetrationDepth of more than 0.0. This lead to SafeMoveUpdatedComponent not being allowed to move the player since it could not resolve the collision error. As a result the player got “stuck” in the corner with no way of getting moved out of the wall.

The fix was actually really simple. All I had to do was to call an extra ResolvePenetration() if Hit.PenetrationDepth != 0.

Here’s it in code:

		if (Hit.PenetrationDepth != 0.f)
		{
			ResolvePenetration(SlideDelta, Hit, Rotation);
		}

I’ve spent so many hour trying to fix this by trial and error and reading other posts, only to find I just had to add 4 lines of code…:stuck_out_tongue: On the positive side, I now have a very thorough understanding of how TwoWallAdjust, SafeMoveUpdatedComponent and ResolvePenetration works.

Hope this helps someone else having the same problem!

-Headstub

PS. For anyone else having this problem, you need to do the ResolvePenetration before you do the TwoWallAjust and SafeMoveUpdatedComponent. If you do it afterwards there are still cases where you become 100 % stuck. By doing it before I’ve have yet to completely 100% stuck myself. My custom MovementComponent now behaves really similar to the CharacterMovementComponent when exposed to the same sharp corner cases.