Help fixing jittering in my character collision

Started by
17 comments, last by ruben67 1 year, 10 months ago

I've been stuck trying to implement continuous collision detection/response for my character controller for a while now, and been trying different methods. I recently tried a method described in this video at around 51:34:

Here's my simplified implementation in pseudocode:

Move(movement)
{
	// This is the position we want to move to
	targetPosition = currentPosition + movement
	
    // Find all surfaces obstructing the movement
    // Treat each contact as a planar constraint (normal of contact = normal of plane)
	constraints = CapsuleSweep(currentPosition to targetPosition)
	
	while (all constraints are not satisfied)
	{
		for (c in constraints)
		{
			if (c is already satisfied)
			{
				Continue the for-loop to the next constraint
			}
			else
			{
				projectedPosition = projection of capsule onto c
				
				// Move the capsule slightly further away from c to create breathing room for any sweeps in the next frame
				projectedPosition += tolerance
				
				// Do another sweep to potentially find new constraints
				newConstraints = CapsuleSweep(targetPosition to projectedPosition)
				
				if (any new constraints are found)
				{
					constraints += newConstraints
					
					Break out of the for loop to start again with the updated list of constraints
				}
				else
				{
					// Update targetPosition because we now want to try to solve this new position
					targetPosition = projectedPosition
					
					Continue the for-loop to the next constraint
				}
				
			}
		}
	}
	
	// After all constraints are satisfied, update the actual position of the character
	// Remember that targetPosition doesn't necessarily refer to the initial position we wanted to move to at the start of the frame as it gets updated to satisfy all constraints
	currentPosition = targetPosition
}

It works great except that it starts to jitter when stuck in a corner or when walking into a shrinking corridor. The jittering sometimes even causes it to go through walls. Here are some videos showing it: https://imgur.com/a/pssrWE1​

The thing is that I know what's causing the jittering - it's the fact that I add a tolerance after solving each constraint, since any future sweeps won't pick up a wall if the sweep starts exactly on the edge of the wall. I'll try to explain why this causes jittering. Consider this image:

It's really messy but couldn't show it in a easier way. So imagine that we want to move from the blue circle to the red one. We then project onto the wall that was in the way (shown by the first purple arrow). We then find the new wall and project out of that one too. This continues until we reach the green circle (for simplicity only two of the steps are shown). Because we are adding a small tolerance each time though, the final position will be slightly off the initial position. This causes the jittering. In some rare cases the green circle will also be close enough to the opposing wall that a future sweep might miss it, meaning that we go through the wall.

If I didn't add any extra tolerance the final position would lie perfectly close to both walls, meaning no jitter, but then we would go through the walls on the next frame because the sweep wouldn't detect them.

If I bring the tolerance down the jittering decreases somewhat, but the issue with going through walls remains.

I have no idea on how to fix this, so some help would be greatly appreciated. (and sorry for a lot of text)

Advertisement

Erin talks about character movement here (slide 57):

https://box2d.org/files/ErinCatto_UnderstandingConstraints_GDC2014.pdf

Give this a try and can help you if you get stuck with this. Note that your tolerance is not necessarily needed for position projection. Some engine need to do this for consecutive sweeps to avoid getting stuck, but again this is not necessary with this approach.​

ruben67 said:
The thing is that I know what's causing the jittering - it's the fact that I add a tolerance after solving each constraint, since any future sweeps won't pick up a wall if the sweep starts exactly on the edge of the wall.

Could you instead of adding tolerance to the resolution of the collision, add it to the walls? (Edit: I meant: Add the tolerance to the collision detection, not any actual surface, so the contact is detected in the next frame)

The tolerance seems very wrong to me. I can not imagine a robust simulation if tolerances can push the player even backwards it's starting position. You could just as well jitter the player around randomly until no collision is found, which does not work for the same reasons.
The problem i see is: Your measure of error (e.g. penetration) does not agree with the result of reducing the error (resolving penetration + tolerance), sow how should the solver ever converge, in case multiple such errors affect each other?

ruben67 said:
If I didn't add any extra tolerance the final position would lie perfectly close to both walls, meaning no jitter, but then we would go through the walls on the next frame because the sweep wouldn't detect them.

I do not understand the reason of not detecting contact in the next frame. You might explain in more detail. I'm convinced there should be a better option than using a tolerance. But i'm no expert, and if this is common practice, there might be some detail missing to make it work.

ruben67 said:
I've been stuck trying to implement continuous collision detection/response for my character controller for a while now, and been trying different methods. I recently tried a method described in this video at around 51:34:

The video is about multi body physics. But for a character controller that's eventually overkill.
If you just want to prevent going through static walls, then you could go the easy and robust route of finding the closest collision, advancing time to that point and resolving it, and continue until we are done with the timestep.

@dirk gregorius These are the same slides he showed in the video, so that is the algorithm I tried to implement. But your link had more detailed explanations. Thanks!

Reading this I realize I misunderstood the method slightly, so I'll fix that. Still, I feel like I would need to add some tolerance since the capsulecast in unity doesn't find surfaces that you are perfectly up against. (It kinda does but sets the contact point to the zero vector and the contact normal to the direction you are casting in, making it useless). Could you please explain how it is not needed?

Thanks for the reply!

@joej He goes on to talk about character collision towards the end, which is what I was referring to.

What I mean about not detecting contact is that if the character is perfectly flush against a wall, and I do a capsulecast against it, it wont detect the wall. So I add a small gap between the character and the wall.

But yeah I see your point about never converging.

You need to sweep forward and then collect all collisions there. Not just the sweep contact. Then solve the contact together. Otherwise you ping-pong. This is what you are seeing,,,

And sorry, I though you were looking at a different publication by Erin. I need to read more closely ?

ruben67 said:
What I mean about not detecting contact is that if the character is perfectly flush against a wall, and I do a capsulecast against it, it wont detect the wall. So I add a small gap between the character and the wall.

So maybe you could use an enlarged version of the capsule only for collision detection, so the wall is still found?

Just out of curiosity, why don't you use Unitys rigid body simulators / collision stuff for your controller?

@dirk gregorius Sorry I dont understand the difference. I'm already collecting all contacts found from the sweep and then solving them all. My issue is that the sweep doesn't pick up collision with a surface I'm perfectly flush against.

I realize now though that I can just begin the sweep slightly away from the wall instead of actually moving the character…

@joej Yes that could work. Just realized I could also start the sweep slightly behind.

And as far as I know a kinematic rigidbody only detects collision but doesnt handle them, so at that point might as well just do the whole thing myself. Detecting collision is just a matter of doing a shape cast.

This topic is closed to new replies.

Advertisement