ComputePenetration not working correctly between Capsule & ConvexMesh

It appears that ComputePenetration doesn’t return the correct decollision direction between a CapsuleCollider and a convex MeshCollider.

Notice the white debug line representing the decollision direction. Here I move a convex cube MeshCollider to make it intersect with a sphere and a capsule. With the sphere, the decoll direction is accurate and moves when moving the cube, but with the capsule, the direction seems to “snap” at 90 degree angles. The problem doesn’t happen if I switch my cube MeshCollider to non-convex

Unity 2202.1.2

Code

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ComputePenetrationCapsuleTester : MonoBehaviour
{
    public CapsuleCollider CapsuleCollider;

    private Transform _transform;
    private Collider[] tmpColliders = new Collider[300];

    public Vector3 CharacterTransformToCapsuleCenter { get; private set; }
    public Vector3 CharacterTransformToCapsuleBottom { get; private set; }
    public Vector3 CharacterTransformToCapsuleTop { get; private set; }
    public Vector3 CharacterTransformToCapsuleBottomHemi { get; private set; }
    public Vector3 CharacterTransformToCapsuleTopHemi { get; private set; }
    public Vector3 CapsuleDirection
    {
        get
        {
            switch(CapsuleCollider.direction)
            {
                case 0:
                    return _transform.right;
                case 1:
                    return _transform.up;
                case 2:
                    return _transform.forward;
            }

            return _transform.up;
        }
    }

    void Awake()
    {
        _transform = this.transform;
        SetCapsuleDimensions(CapsuleCollider.radius, CapsuleCollider.height, CapsuleCollider.center.y);
    }

    public void SetCapsuleDimensions(float radius, float height, float yOffset)
    {
        height = Mathf.Max(height, (radius * 2f) + 0.01f); // Safety to prevent invalid capsule geometries

        CapsuleCollider.radius = radius;
        CapsuleCollider.height = Mathf.Clamp(height, CapsuleCollider.radius * 2f, height);
        CapsuleCollider.center = new Vector3(0f, yOffset, 0f);

        Vector3 capsuleDirection = CapsuleDirection;
        float halfHeight = CapsuleCollider.height * 0.5f;

        CharacterTransformToCapsuleCenter = CapsuleCollider.center;
        CharacterTransformToCapsuleBottom = CapsuleCollider.center + (-capsuleDirection * halfHeight);
        CharacterTransformToCapsuleTop = CapsuleCollider.center + (capsuleDirection * halfHeight);
        CharacterTransformToCapsuleBottomHemi = CapsuleCollider.center + (-capsuleDirection * halfHeight) + (capsuleDirection * CapsuleCollider.radius);
        CharacterTransformToCapsuleTopHemi = CapsuleCollider.center + (capsuleDirection * halfHeight) + (-capsuleDirection * CapsuleCollider.radius);
    }

    void Update()
    {
        SetCapsuleDimensions(CapsuleCollider.radius, CapsuleCollider.height, CapsuleCollider.center.y);

        int nbHits = Physics.OverlapCapsuleNonAlloc(
            _transform.position + (_transform.rotation * CharacterTransformToCapsuleBottomHemi),
            _transform.position + (_transform.rotation * CharacterTransformToCapsuleTopHemi),
            CapsuleCollider.radius,
            tmpColliders,
            -1,
            QueryTriggerInteraction.Collide);

        for (int i = 0; i < nbHits; i++)
        {
            Collider otherCollider = tmpColliders[i];

            if (otherCollider != CapsuleCollider)
            {
                if (Physics.ComputePenetration(
                    CapsuleCollider, _transform.position, _transform.rotation,
                    otherCollider, otherCollider.transform.position, otherCollider.transform.rotation,
                    out Vector3 decollideDirection, out float overlapDistance))
                {
                    Debug.DrawRay(_transform.position, decollideDirection * overlapDistance);
                }
                else
                {
                    Debug.Log("ComputePenetration failed");
                }
            }
        }
    }

    private void OnDrawGizmos()
    {
        _transform = this.transform;
        Gizmos.color = Color.yellow;
        Gizmos.DrawSphere(_transform.position + (_transform.rotation * CharacterTransformToCapsuleBottomHemi), CapsuleCollider.radius);
        Gizmos.DrawSphere(_transform.position + (_transform.rotation * CharacterTransformToCapsuleTopHemi), CapsuleCollider.radius);
    }
}
2 Likes

I’ve run into, tested, and confirmed the same thing (2021.3.21f1, 2022.3.6f1). It only happens with capsules and convex mesh colliders. This makes it very difficult to implement a smooth and accurate custom character controller.

9638036--1370048--convexwrex.gif

My depenetration code here is almost identical, though it also lets you use a Sphere Collider in place of the Capsule through the same code. The sphere collider produces the correct, desired results depenetrating in all cases; it is only the capsule that fails like this, and it specifically only fails with convex mesh colliders. It would be great to see this fixed, or to be informed if we’re somehow misusing this method in a way that only produces this effect in this Capsule x Convex Mesh scenario.
Code

void Update()
{
    if (doDepens && (_collider != null))
    {
        CapsuleCollider cCol = _collider as CapsuleCollider;
        SphereCollider sCol = _collider as SphereCollider;

        Vector3 localOffset = cCol == null ? sCol.center : cCol.center;

        int count = Physics.OverlapBoxNonAlloc(transform.TransformPoint(localOffset), _collider.bounds.extents, cols);

        for (int i = 0; i < count; i++)
        {
            if (cols[i] == _collider) { continue; }

            if (Physics.ComputePenetration(_collider, transform.TransformPoint(localOffset), transform.rotation,
                cols[i], cols[i].transform.position, cols[i].transform.rotation,
                out Vector3 dir, out float mag))
            {
                Debug.DrawRay(transform.position, dir * mag, Color.yellow, 2.5f);
                transform.position += dir * mag;
            }
        }
    }
}
1 Like

Has anyone reported this bug to Unity? I would like to vote the issue as I also have problems with this method.

I found this thread by accident, but have also experienced this. I was going crazy trying to figure out what was happening, but ended up giving up and moving on to something else.

For what it’s worth, this is fixed in 2023. I’m not sure if the fix was ever backported to current 2021, 2022 versions, but if you can upgrade your project’s Unity version, that might be the move.

1 Like