Better Surface Orientation

Surface collision inside geometry

There is a slight problem with the ComputeSurfaceInfo() function such that sometimes when there are very close or overlapping geometry the character has an incorrect rotation.

bottom hit is actually detecting the normal (cyan line) from the top edge of the object under the higher one

The reason for this is that the shape sweep we are using is happening from within the geometry.

Even though the Shape Sweep is technically colliding with the "top edge of the object under the higher one" it doesn't make visible sense.

More visually accurate collision

It would make much more visible sense if we did the collision starting from the player then reaching out to see what we collide with first, which would be the "side face of the object higher up" the same as the other collision .

reaching out from the player would hit the wall first before the object underneath

Luckily we don't have to change anything about the Shape Sweep, as it is still a very good way of detecting multiple simultaneous collisions, however we just need to do a little extra when adding up the surface normals.

Extra Sweep per collision

Since we can't trust the hits from our Shape Sweep to be visually correct, we'll perform an additional smaller sweep from the player to the collision point to check if we should visually collide with anything before the original hit.

If we do, then that means we should take that surface's normal when collecting the normals instead

the original (x'd out) surface hit will be ignored because we collided with a surface (green) before reaching it

Doing a raw raycast might still produce the issue because of how thin it would be, so instead we'll use another small Shape Sweep.

.cpp

// ZCCharacterMovementComponent.cpp

void UZCCharacterMovementComponent::ComputeSurfaceInfo()
{
	// ...
	const FVector Start = UpdatedComponent->GetComponentLocation();
	const FCollisionShape CollisionShape = FCollisionShape::MakeSphere(6); //reasonably smol sphere

	for (const FHitResult& WallHit : CurrentWallHits)
	{
		const FVector End = Start + (WallHit.ImpactPoint - Start).GetSafeNormal() * 120; //magic number here is just making sure we make it to the surface
		FHitResult AssistHit;
		GetWorld()->SweepSingleByChannel(AssistHit, Start, End, FQuat::Identity, ECC_WorldStatic, CollisionShape, ClimbQueryParams);

		CurrentClimbingPosition += AssistHit.ImpactPoint;
		CurrentClimbingNormal += AssistHit.Normal;
	}
	// ...
}
(green) lines show the extra shape sweep pass detecting the surface

Last updated