When capsule collider height equals diameter it produces NaNs

If you flatten a capsule collider so it’s height equals its diameter (2x it’s radius). i.e. the capsule is actually a sphere

In this case, Vertex0 and Vertex1 are equal which should still be in theory fine but unfortunately causes NaNs within the physics simulation/queries when doing convex convex collisions.

I first spotted this when I made a fork of physics a couple of weeks ago to use as a small spatial library and ran into this issue, patched it and totally forgot about it.

However someone reported NaNs in our physics at work earlier this week so I had a bit of a dive and after some debugging, noticed the problem again. Thankfully as mentioned I had already run into this a couple of weeks ago so was easy to realize problem and patch once i spotted it.

This picture is within a spherecast hitting the capsule, hopefully it explains the problem enough.

My workaround for this in baking is to check for it, and replace it with a sphere.

case ShapeType.Capsule:
{
    res.CapsuleProperties = shape.GetCapsuleProperties()
        .BakeToBodySpace(localToWorld, shapeToWorld)
        .ToRuntime();

    // If 2*radius == height then switch to a sphere to avoid nans
    if (res.CapsuleProperties.Vertex0.Equals(res.CapsuleProperties.Vertex1))
    {
        res.ShapeType = ShapeType.Sphere;
        res.SphereProperties =
            shape.GetCapsuleSphereProperties(out orientation).BakeToBodySpace(localToWorld, shapeToWorld, ref orientation, bakeUniformScale);
    }
    break;
}

internal SphereGeometry GetCapsuleSphereProperties(out EulerAngles orientation)
{
    orientation = m_PrimitiveOrientation;
    return new SphereGeometry
    {
        Center = m_PrimitiveCenter,
        Radius = m_Capsule.Radius,
    };
}

i don’t know if this issue occurs with the regular CapsuleCollider or it’s limited to the older PhysicsShapeAuthoring. If it’s not limited to PhysicsShapeAuthoring then a better solution to fix it at runtime would be preferred though.

Hi! We encountered that exact issue just recently internally. I’ll make sure it’s all good in future releases.
Thanks for calling it out.

Hey! Thank you very much for reporting this issue. Indeed, there is a division by zero when calculating the distance. So I think that instead of changing the collider to a sphere, it would be better to avoid that division. We will fix this problem in a future release!

So the fix would be something like this:

...
// Point-segment distance
float3 edgeA = capsuleVertex1 - capsuleVertex0;
float dot = math.dot(edgeA, centerB - capsuleVertex0);
float edgeLengthSquared = math.lengthsq(edgeA);

// If the capsule is degenerate (becomes a sphere), just use vertex0 as the point
if (edgeLengthSquared < distanceEpsSq)
{
    return PointPoint(capsuleVertex0, centerB, capsuleRadius, capsuleRadius + sphereRadius);
}

...

Is it expected to ever get nans out of physics?

We had a crash on a server yesterday from within physics on a really weird location

* thread #1, name = 'N', stop reason = signal SIGSEGV: address not mapped to object
  * frame #0: 0x00007c540b50a3d8 lib_burst_generated.so`Unity.Physics.RaycastQueries.RayCapsule [inlined] Unity.Physics.RaycastQueries.RaySphere(rayOrigin=<unavailable>, rayDisplacement=([0] = -2.38735934E-26, [1] = 4.46005275E-41, [2] = -7.77432646E-23), sphereCenter=<unavailable>, sphereRadius=<unavailable>, fraction=0x0000000000000000, normal=<unavailable>) at Raycast.cs:179
    frame #1: 0x00007c540b50a358 lib_burst_generated.so`Unity.Physics.RaycastQueries.RayCapsule(rayOrigin=<unavailable>, rayDisplacement=([0] = -2.38735934E-26, [1] = 4.46005275E-41, [2] = -7.77432646E-23), vertex0=<unavailable>, vertex1=<unavailable>, radius=<unavailable>, fraction=0x0000000000000000, normal=0x0000000000000000) at Raycast.cs:235

Which if we believe the line numbers, points to

float t0 = (sqrtDiscriminant - b) * invDenom;

which seems improbable.

This is in the RaySphere method

    static class RaycastQueries
    {
        #region Ray vs primitives
        public static bool RaySphere(

And i’ve been testing it today with unusual data to see if I could repro it, and while I haven’t, I have been able to produce a few nans. For example, it’s possible to get nans out on the normal when returning true if your ray is a point

and it’s possible to generate infinity/nans if your sphere happens to have 0 radius

This is the case that more fits our crash but they don’t seem to propagate anywhere so I can’t really see how it could be causing it so going to be testing more. Just bringing it up more as a question though.

It’s always possible to feed in physically non-viable data and get physically non-viable answers. A sphere with zero radius has no volume. So, from a physics perspective, it doesn’t exist.
Same for a ray cast with zero ray length. The ray physically doesn’t exist in this case. A distance query would make more sense in this situation, or ensuring that the ray is actually that: a ray.

I would consider such cases corner cases which are “garbage in, garbage out”. Other cases, such as a capsule with zero cylindrical height (see the initial issue you pointed out) do make physical sense and we will make sure we get physically meaningful outputs in these cases in the engine.
We are wrapping that fix up as we speak, ramping up the unit test coverage at the same time.

It’s more of an ideological question I guess.

For example i can totally think of commonly written code that causes a raycast length of be 0. Simply checking the distance between objects, for example doing a simple los check, and if the 2 objects happen to be on top of each other.

In this case, should it be the responsibility of the physics library to handle it gracefully in 1 place, or for the user to handle it (in potentially multiple places.)

I think it might even be valid to argue due to teleporting mechanics etc, a user might always need to add a check ray for 0 length before casting, but if this was the case would it make more sense for the library to handle it?

Just thinking out loud.

I feel at the very least, it’s unexpected that valid, unchecked input values can result in unexpected nan’s or infinities without runtime warnings or at least documentation warning of bad cases.

Yes, it is surely ideological, but I think it also depends on the API itself whether it makes more sense to perform safety checks inside or rely on the user doing them.

Looking at the raycast API for example, we can see that users just need to provide a start and end point in the RaycastInput denoting the ray. So, as you say, it’s easily possible that in the general case the ray could have zero length. If Unity Physics does not handle the zero length case internally, the user would need to check that case themselves which is not convenient and creates bloat in user code. If, say the API required a direction vector and a length, then the check could easily be done on the user side without much bloat. That’s just one example.

We will keep an eye open for these sorts of corner cases going forward and make sure that the functions are “sane” in that regard. In the ray cast case for example, to your point, I think we will put some more safety checks inside the underlying implementations going forward to make the API more convenient.

FYI, this will be fixed in one of the next 1.4.x releases.
Look out for the changelog entry “Division by zero errors causing NaN values when a Capsule degenerates into a Sphere”.