Geometry `ClosestPoint` calculations are inconsistent between shape types

Geometry ClosestPoint calculations are inconsistent between different shape types when the input point is inside the shape.
Specifically, PolygonGeometry.ClosestPoint returns the inside point itself, and CircleGeometry.ClosestPoint and CapsuleGeometry.ClosestPoint return a point on the perimeter which is closest to the input.

I would like the behavior to be consistent. In my mind, it makes most sense to return the inside point itself for all shapes (just like PolygonGeometry.ClosestPoint already does), at least in part because a big/concave polygon shape is composed from multiple PolygonGeometry parts, thus a point on PolygonGeometry perimeter will often not be a point on the overall shape perimeter.

This comes from Box2D itself so I’ll check that out. None of these queries are used by physics itself so it’s possible to change the behaviour without breaking the physics simulation.

Confirmed in this little test so I’ll get that fixed.

No, that would be bad as that behaviour would reduce flexibility. If you want to know if it overlaps then use OverlapPoint and use your input point in that case. This is no different than performing (say) CastRay where the start point is already overlapped. You need to detect that yourself.

Closest point should always return the closest point on the exterior of the shape.

I don’t see that as an argument to change the behaviour of all the shapes or how it relates TBH. You cannot use closest point to detect some arbitrary concave outline. If you wanted to do that then you’d use an outline of SegmentGeometry (even temporarily) and detect the closest point on them.

using System;
using UnityEngine;
using UnityEngine.InputSystem;

#if UNITY_6000_5_OR_NEWER
using Unity.U2D.Physics;
#else
using UnityEngine.LowLevelPhysics2D;
#endif

public class ScratchPad : MonoBehaviour
{
    public Camera camera;
    
    public PhysicsTransform physicsTransform = PhysicsTransform.identity;
    
    public PhysicsShape.ShapeType GeometryType = PhysicsShape.ShapeType.Circle;
    public CircleGeometry circle = CircleGeometry.defaultGeometry;
    public CapsuleGeometry capsule = CapsuleGeometry.defaultGeometry;
    public PolygonGeometry polygon = PolygonGeometry.defaultGeometry;
    public SegmentGeometry segment = SegmentGeometry.defaultGeometry;
    public ChainSegmentGeometry chainSegment = ChainSegmentGeometry.defaultGeometry;
    
    private void Start()
    {
        circle = circle.Transform(physicsTransform);
        capsule = capsule.Transform(physicsTransform);
        polygon = polygon.Transform(physicsTransform);
        segment = segment.Transform(physicsTransform);
        chainSegment = chainSegment.Transform(physicsTransform);
    }

    private void Update()
    {
        var world = PhysicsWorld.defaultWorld;
        Vector2 worldPoint = camera.ScreenToWorldPoint(Mouse.current.position.value);

        Vector2 closestPoint;
        switch (GeometryType)
        {
            case PhysicsShape.ShapeType.Circle:
                closestPoint = circle.ClosestPoint(worldPoint);
                world.DrawGeometry(circle, PhysicsTransform.identity, Color.forestGreen);
                break;
            case PhysicsShape.ShapeType.Capsule:
                closestPoint = capsule.ClosestPoint(worldPoint);
                world.DrawGeometry(capsule, PhysicsTransform.identity, Color.forestGreen);
                break;
            case PhysicsShape.ShapeType.Polygon:
                closestPoint = polygon.ClosestPoint(worldPoint);
                world.DrawGeometry(polygon, PhysicsTransform.identity, Color.forestGreen);
                break;
            case PhysicsShape.ShapeType.Segment:
                closestPoint = segment.ClosestPoint(worldPoint);
                world.DrawGeometry(segment, PhysicsTransform.identity, Color.forestGreen);
                break;
            case PhysicsShape.ShapeType.ChainSegment:
                closestPoint = chainSegment.ClosestPoint(worldPoint);
                world.DrawGeometry(chainSegment.segment, PhysicsTransform.identity, Color.forestGreen);
                break;
            default:
                throw new ArgumentOutOfRangeException();
        }

        world.DrawLine(worldPoint, closestPoint, Color.gray3);
        world.DrawPoint(worldPoint, 4f, Color.deepPink);
        world.DrawPoint(closestPoint, 4f, Color.mintCream);
    }
}

Fixed.

The public tracking link can be found here.

To confirm, this is a Box2D v3.x issue with b2ShapeDistance but for now I’ve fixed it locally.

Thank you for the local fix. I thought I was going insane a couple weeks ago when I was getting wierd results for only certain shapes with ClosestPoint. Thought I made a mistake and didn’t even realize it was a bug.

Running on the build farm now for 6.6, 6.5, 6.4 and 6.3. Phew!

This fix has landed and will be in:

  • 6000.6.0a1
  • 6000.5.0a8
  • 6000.4.0b11
  • 6000.3.11f1

FYI: 6000.3.11f1 has been released with the above changes.

For some reason, a few of the release note entries are duplicated but I’ve messaged the release management team about that so it should be sorted next week.

Hope this resolves your issue.