Capsule rigidbody very noticeably penetrates ground even with continuous detection

When my capsule collider rigidbody (height 2, radius 0.5) vertically collides with a static box collider at a speed of 41, it penetrates it by 0.35, over 1/6th of the collider’s height! Due to the shape of my character mesh, this is very noticeable to the player as a big portion clips below the ground for a couple frames.

I’ve tried changing the fixed timestep, max depenetration velocity, default contact offset, solver iterations, etc. Nothing changes it. I even tried scaling up the entire game by 10x, but I guess since the speed was 10x faster, the penetration distance also scaled up to match.

Is this in line with what can be expected of Unity physics, or is there something weird going on in my project?

Why would there be any penetration with continuous collision detection? And why doesn’t the engine attempt to depenetrate in a single physics step? I could understand how that might not be possible if there’s many colliders competing for the same space, but in the case of one rigidbody colliding with one static collider, I expected better. It’s taking it 2-4 steps to fully depenetrate the colliders.

Has anyone else needed a much smaller margin of error for penetrations? How did you deal with it?

The only thing I can think of at this point is using either the contact point data in the collision events or Physics.ComputePenetration to modify the transform myself. But I thought I’d check here to see if there’s a less hacky way and ask what the unintended consequences might be if I modify the position myself. I could potentially just adjust the visible mesh and not the rigidbody game object if changing the position would mess up the physics too much.

1 Like

With continuous collision detection there shouldn’t be an issue.

Try creating an empty scene and place a rigidbody capsule with continuous collision detection enabled above a cube. The falling capsule shouldn’t penetrate the cube at all regardless of how high it falls from.

Only when switching the collision detection mode to discrete is when you’ll start to see penetration.

Is there any script moving capsule collider? Since you mentioned player character I assume there is.

Are you using position extrapolation ?

There was a script moving it, but I’ve reproduced the issue without it.

I attached the repro project. I’m using version 2021.3.15f1.

Hit play and the capsule should fall onto the cube printing some of the contact info to the console. For me it prints

impulse: (0.00, 29.04, 0.00)
contacts[0].separation: -0.3852639

EDIT:
Just realized physics settings won’t carry over in the unity package. The issue still reproduces fine with default settings, but my game was using
Gravity: (0, -23, 0)
Default Max Depenetration Velocity: 500
Fixed timestep: 0.01

9687149–1381793–PenetrationTest.unitypackage (3.61 KB)

I didn’t test your project but I did take another look at the issue and you’re correct the penetration is much further than I had first thought. You can also test visually by freezing time with Time.timeScale=0 from within OnCollisionEnter.

But good news! - I was able to prevent penetration by using Continuous Speculative for the collision detection mode.

Thanks, speculative does not suffer from this problem! Though it comes with some drawbacks. After some testing, I noticed the issue mentioned in the docs does show up in egregious ways in some scenarios in my game. The game is kind of about physics, so it’s important for the physics to not behave in surprising ways.

Based on the documentation, sweep-based detection should be better than speculative in my situation because my rigidbody never rotates. But I found the reason Continuous and Continuous Dynamic are not behaving the way I had expected. To improve performance, PhysX only uses sweeps in situations where it might be necessary to prevent tunneling . There’s a parameter to change that behavior, but I don’t think there’s any way to set that parameter from Unity.

It’s kind of wild that there’s not a good way to solve this problem in Unity given how bad the “allow penetrations but depenetrate over several steps” strategy looks at times. Would really appreciate if any staff seeing this would forward the feature request from [mention|EvlJ4SQCzwByHi0aJazChQ==] in this thread to the proper channels. Or if there’s a way for me to file the feature request myself let me know.

But anyway, thanks @zulo3d for looking into this!

It’s easy to get lost in the weeds with this kind of detail, but ask yourself if players will really notice it, given they see a character with legs and feet, and not a capsule?

I solved my problem, although it’s not pretty. Would be much better for Unity to expose the PxSceneDesc::ccdThreshold parameter. But for posterity, I’ll record my solution here.

The short version is: after each physics step, I use OverlapCapsule to get the colliders the player is in contact with and Physics.ComputePenetration to push them out.

Here’s the code:

public class Player : MonoBehaviour
{
    private const int MAX_COLLIDERS = 8;
    private Collider[] collidersBuffer = new Collider[MAX_COLLIDERS];
    private readonly WaitForFixedUpdate waitForFixedUpdate = new WaitForFixedUpdate();
    private CapsuleCollider capsule;
    private LayerMask ignorePlayerLayerMask;

    public void Awake()
    {
        capsule = GetComponent<CapsuleCollider>();
        ignorePlayerLayerMask = ~LayerMask.GetMask("Player");
    }

    public void OnEnable()
    {
        StartCoroutine(AfterPhysicsStep());
    }

    public IEnumerator AfterPhysicsStep()
    {
        while (enabled)
        {
            yield return waitForFixedUpdate;

            Vector3 pointOffset = new Vector3(0f, capsule.height / 2f - capsule.radius, 0f);
            int colliderCount = Physics.OverlapCapsuleNonAlloc(
                transform.TransformPoint(capsule.center + pointOffset),
                transform.TransformPoint(capsule.center - pointOffset),
                capsule.radius,
                collidersBuffer,
                ignorePlayerLayerMask
            );
            for (int i = 0; i < colliderCount; i++)
            {
                if (Physics.ComputePenetration(
                    capsule,
                    transform.position,
                    transform.rotation,
                    collidersBuffer[i],
                    collidersBuffer[i].transform.position,
                    collidersBuffer[i].transform.rotation,
                    out Vector3 direction,
                    out float distance
                ))
                {
                    transform.position += direction * distance;
                }
            }
        }
    }
}

For the record, I am seeing this too.
Capsule collider penetrates the ground by up to 70% of it’s size, super strange.