Edge grabbing problem when turning inside corners

I am currently developing a edge grab mechanism through C++, and I have hit a dead end.

Right now, the system largely works. I can turn corners and round surfaces and the edge detection is not bad either (the reason why it looks too low in the video is because my animation sucks, but I made that in a few minutes).

The problems start when I turn inside corners that are rather sharp (in the range of 90 degrees or less). Then the character suddenly gets stuck in the wall, travels through the wall to attach himself to some nonsensical place, and so on. I have attached a video for demonstration (the funky corner stuff is towards the end, though the screen recorder slowed the footage down a fair bit making me unable to reproduce the weird “travelling through walls” bug). It should be sufficient in pointing out what my problem is.

My code is as follows:
The trace code


FEdgegrabData UBK_PlayerState_Edgegrabbing::DetectLedge(float Value)
{
	//The final hitresults to store our information in
	FHitResult TraceNormal;
	FHitResult TraceLocation;

	const TArray<AActor*> ActorsToIgnore; //main actors to ignore; same for all traces
	bool bCanMove = true; //we assume that there is a ledge, until a trace proves otherwise
	float ValueAbs = Value / abs(Value); //make sure trace is either -1 or 1, so we can use the direction vectors properly for tracing

	FVector SocketLocation; //Set our active socket for tracing
	if (ValueAbs < 0.f)
	{
		SocketLocation = Owner->GetMesh()->GetSocketLocation("Socket_EdgeGrab_Right");
	}
	else
	{
		SocketLocation = Owner->GetMesh()->GetSocketLocation("Socket_EdgeGrab_Left");
	}

	//////////////////////////////////////////////////////////////////////////////////////////////
	//
	//  First, let's trace the sides
	//
	//////////////////////////////////////////////////////////////////////////////////////////////

	//attempts to get the normal of the grabbed wall right in front of the player.

	FHitResult PhaseAHitResultA;
	FHitResult ConfirmationHit;

	FVector PhaseATraceStart = SocketLocation + (Owner->GetActorForwardVector() * -32.f) + (Owner->GetActorRightVector() * DetectionOffset * ValueAbs);
	PhaseATraceStart.Z = EdgeData.TraceLocation.ImpactPoint.Z - DetectionOffset;
	FVector PhaseATraceEnd = PhaseATraceStart + (Owner->GetActorForwardVector() * TraceOffset.Y);

	bool bPhaseAResultA = Owner->LineTraceSingle(this, PhaseATraceStart, PhaseATraceEnd, ECC_Visibility, false, ActorsToIgnore, EDrawDebugTraceInternal::ForDuration, PhaseAHitResultA, true);
	
	TraceNormal = PhaseAHitResultA;

	//If we get a hit, perform the second trace to check whether the ledge is valid
	if (bPhaseAResultA)
	{
		PhaseATraceEnd = PhaseAHitResultA.ImpactPoint + (PhaseAHitResultA.ImpactNormal * -DetectionOffset); //the minimum grabbing space that should be there.
		PhaseATraceEnd.Z += TraceOffset.Z;
		PhaseATraceStart = PhaseATraceEnd + (Owner->GetActorForwardVector() * -TraceOffset.Y);

		bool bPhaseAResultB = Owner->LineTraceSingle(this, PhaseATraceStart, PhaseATraceEnd, ECC_Visibility, false, ActorsToIgnore, EDrawDebugTraceInternal::ForDuration, ConfirmationHit, true);

		if (!bPhaseAResultB)
		{
			//get the up-normal of the ledge, so we can use it later to alter the Zposition of the player
			PhaseATraceEnd = PhaseAHitResultA.ImpactPoint + (PhaseAHitResultA.ImpactNormal * -DetectionOffset);
			PhaseATraceStart = PhaseATraceEnd;

			PhaseATraceEnd.Z -= TraceOffset.Z;
			PhaseATraceStart.Z += TraceOffset.Z + DetectionOffset;

			bool bPhaseAResultC = Owner->LineTraceSingle(this, PhaseATraceStart, PhaseATraceEnd, ECC_Visibility, false, ActorsToIgnore, EDrawDebugTraceInternal::ForDuration, TraceLocation, true);
		}
		else
		{
			bCanMove = false;
		}
	}
	else
	{
		//////////////////////////////////////////////////////////////////////////////////////////////
		//
		//  Next, let's trace for the inside and outside corner
		//
		//////////////////////////////////////////////////////////////////////////////////////////////

		//first let's check whether we have run into an inside corner
		FHitResult PhaseBHitResultA;
		FVector PhaseBTraceStart = Owner->GetActorLocation();
		PhaseBTraceStart.Z = EdgeData.TraceLocation.ImpactPoint.Z - DetectionOffset;
		FVector PhaseBTraceEnd = PhaseBTraceStart + (Owner->GetActorRightVector() * (TraceOffset.X + DetectionOffset * 2.f) * ValueAbs);

		bool bPhaseBResultA = Owner->LineTraceSingle(this, PhaseBTraceStart, PhaseBTraceEnd, ECC_Visibility, false, ActorsToIgnore, EDrawDebugTraceInternal::ForDuration, PhaseBHitResultA, true);
		TraceNormal = PhaseBHitResultA;

		//if we get another hit, perform the second trace of phase B to check whether the ledge is valid
		//we can also assume that if this is true, there won't be an outside corner
		if (bPhaseBResultA)
		{
			FHitResult PhaseBHitResultB;
			PhaseBTraceEnd = PhaseBHitResultA.ImpactPoint + (PhaseBHitResultA.ImpactNormal * -DetectionOffset);
			PhaseBTraceStart = PhaseATraceEnd;

			PhaseBTraceEnd.Z -= TraceOffset.Z;
			PhaseBTraceStart.Z += TraceOffset.Z + DetectionOffset;

			bool bPhaseBResultB = Owner->LineTraceSingle(this, PhaseBTraceStart, PhaseBTraceEnd, ECC_Visibility, false, ActorsToIgnore, EDrawDebugTraceInternal::ForDuration, PhaseBHitResultB, true);
			TraceLocation = PhaseBHitResultB;

			if (!bPhaseBResultB)
			{
				bCanMove = false;
			}
		}
		else
		{
			//////////////////////////////////////////////////////////////////////////////////////////////
			//
			//  if the result was false, let's check for an outside corner, as the final step in our trace for ledges
			//
			//////////////////////////////////////////////////////////////////////////////////////////////

			FHitResult PhaseCHitResultA;
			FVector PhaseCTraceStart = SocketLocation + (Owner->GetActorRightVector() * TraceOffset.X * ValueAbs) + (Owner->GetActorForwardVector() * DetectionOffset);
			PhaseCTraceStart.Z = EdgeData.TraceLocation.ImpactPoint.Z - DetectionOffset;
			FVector PhaseCTraceEnd = PhaseCTraceStart + (Owner->GetActorRightVector() * (TraceOffset.X + 1.f) * (ValueAbs*-1));

			bool bPhaseCResultA = Owner->LineTraceSingle(this, PhaseCTraceStart, PhaseCTraceEnd, ECC_Visibility, false, ActorsToIgnore, EDrawDebugTraceInternal::ForDuration, PhaseCHitResultA, true);
			TraceNormal = PhaseCHitResultA;

			if (bPhaseCResultA)
			{
				FHitResult PhaseCHitResultB;
				PhaseCTraceEnd = PhaseCHitResultA.ImpactPoint + (PhaseCHitResultA.ImpactNormal * -DetectionOffset);
				PhaseCTraceStart = PhaseCTraceEnd;

				PhaseCTraceEnd.Z -= TraceOffset.Z;
				PhaseCTraceStart.Z += TraceOffset.Z + DetectionOffset;

				bool bPhaseBResultB = Owner->LineTraceSingle(this, PhaseCTraceStart, PhaseCTraceEnd, ECC_Visibility, false, ActorsToIgnore, EDrawDebugTraceInternal::ForDuration, PhaseCHitResultB, true);
				TraceLocation = PhaseCHitResultB;

				//the chance of this being false is small, but not impossible, so we need to keep this one in
				if (!bPhaseBResultB)
				{
					bCanMove = false;
				}
			}
			else
			{
				bCanMove = false; //the final check failed, so player cannot move.
			}
		}
	}

	FEdgegrabData Result;

	Result.bCanMove = bCanMove;
	Result.TraceLocation = TraceLocation;
	Result.TraceNormal = TraceNormal;

	return Result;
}

The input part


void UBK_PlayerState_Edgegrabbing::MoveRight(float Value)
{
	FEdgegrabData EdgeData;

	if (Value != 0.f)
	{
		EdgeData = DetectLedge(Value);

		if (EdgeData.bCanMove)
		{
			//first get the opposite socket location, which we need later to determine the angle.
			FVector SocketLoc;

			if (Value < 0.f) //-1 is left, 1 is right
			{
				SocketLoc = Owner->GetMesh()->GetSocketLocation("Socket_EdgeGrab_Right"); 
			}
			else
			{
				SocketLoc = Owner->GetMesh()->GetSocketLocation("Socket_EdgeGrab_Left");
			}
			
			FRotator NewRotation = EdgeData.TraceNormal.ImpactNormal.Rotation();
			NewRotation.Yaw += 180.f;

			FVector NewLocation = EdgeData.TraceLocation.ImpactPoint + Owner->GetActorRightVector()*(Value*10.f);// -(Owner->GetActorForwardVector() * TraceOffset.X);
			FVector PawnAndSocketOffset = SocketLoc - Owner->GetActorLocation();
			NewLocation -= PawnAndSocketOffset;

			Owner->SetActorRotation(FMath::RInterpTo(Owner->GetActorRotation(), NewRotation, GetWorld()->GetDeltaSeconds(), 20.f));
			Owner->SetActorLocation(FMath::VInterpTo(Owner->GetActorLocation(), NewLocation, GetWorld()->GetDeltaSeconds(), 20.f));
		}
	}
}

I am using socket locations for traces, to have something a bit more flexible (and since the placement is going to rely on the animation rather than the capsule component location, I figured this was better).

I can’t quite analyze the code right now, I may do so once I get home, though I haven’t been active with UE4 lately.

My suggestion: check my video, don’t go for sockets, I mean, having to setup sockets over and over again for every different item isn’t quite what you’d want - I think. Just retrieve the relevant vertexes and use them as ‘sockets’ or better yet, as direction vectors. When you reach the end of the vector - or close to - find the next one, if there is none you’ve hit an edge.

Are you the author of the code, or you’ve based it on someone else’s?

I’m the author. The initial inspiration was through one of the overgrowth videos I found in 's thread, but that’s about it. Since 's thread used BP, I pretty much ignored it since I wanted to do it with C++.

The sockets are purely there as a means of getting the initial positions (I’m using 2, one for each hand). Using these as a source, I use direction vectors to do the tracing and movement. It’s easier than using an offset, but yeah, it’s not perfect. I eventually want to make something that’s a bit easier to update.