Search Unity

Bug Physics2D.OverlapCollider returning false positives

Discussion in 'Physics' started by PanopticMedia, Jun 3, 2023.

  1. PanopticMedia

    PanopticMedia

    Joined:
    Jul 4, 2019
    Posts:
    5
    Very strange situation I've run into recently. I'm using OverlapCollider to check if I'm already overlapping with a platform's collider of a specific type. I seem to be getting some false positives. I set a breakpoint in my script to see if somehow the objects actually were overlapped and my math elsewhere was wrong. I have verified that the colliders are absolutely not overlapping. I even put this log statement in:

    Debug.Log($"Bounds intersect: {raycastHit.collider.bounds.Intersects(_body.collider.bounds)}");

    I've also set Physics2D.autoSyncTransforms = true;
    for debug purposes, though only one of these two objects are moving, so I really don't think that's a problem.

    And that prints 'false' within the condition that claims they overlap.

    My question is this: is this by design? Is there meant to be some forgiveness area for two overlapping colliders? That makes sense for 'touching' colliders, but I don't think it makes sense for overlapping ones. Does anyone know if there's a filter parameter or something I can change that will prevent this from happening?

    I have for the time being managed to work around it by changing the condition to raycastHit.collider.bounds.Intersects(_body.collider.bounds), but that uses the full bounding box. That works just fine in this situation, but it's really not ideal as I'd like to abstractly determine if there are any overlaps with other colliders whether they're capsule, box, circle, etc.
     
  2. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,495
    Saying something is overlapping when it's not is clearly not "by design". :)

    In every single case I've seen this kind of thing reported (without exception) it's been a problem in the project.

    I don't understand why you'd need the legacy "autoSyncTransforms" and how it relates to this. You should never be modifying the Transform directly anyway so this shouldn't be related.

    Happy to guide you but I cannot really suggest anything unless you can provide some speicifc information such as showing the colliders in question, the query etc to get some sense of what is happening.

    Have you tried creating a simple test in a new project?
     
  3. PanopticMedia

    PanopticMedia

    Joined:
    Jul 4, 2019
    Posts:
    5
    Well the reason I ask is that it's the behavior I'm seeing. I agree it would be a bad design, but I've seen plenty of bad things, poorly-named functions, etc. before :)

    You probably won't like to hear this, but I generally do not use rigid bodies in 2D. I'm sure for good and proper physics simulations or games that are very physics-driven they're great, but for the types of projects I work on, they simply do not offer the precision, control, or responsiveness that I need. No amount of tweaking of the body types or update frequency/type or anything like that has ever removed the corner cases that I experience with rigid bodies, and working 99.8% of the time isn't good enough. To the extent that I do use them, it's for eventing purposes only (OnCollisionEnter etc.), and I always freeze position and rotation. I'm sure this is deeply irritating to a physics engineer, but I guarantee you that I am far from the only person who's needed to do this, and it's not a decision I've arrived at lightly. Even in Unity's Sample 2D projects you'll see a litany of collider casts and such designed to work around the types of problems that I deal with frequently.

    I do make liberal use of the 2D Colliders though--I use them for collider casts, ray casts, etc., and I find they provide me with very reliable results that I can use to drive my movement logic off of. That's why I bring up autoSyncTransforms, because I do modify the transform directly. Generally my strategy is to set up some barriers for updating the collider transforms so that it doesn't need to happen literally every time I move an object, but I will toggle autoSyncTransforms sometimes to eliminate that as a source of an issue.

    I just took some time to reproduce this in a test scene. No rigidbodies, only transform, sprite renderer, BoxCollider2D, and this script. Tested in 2021.3.9f1 and 2022.1.19f1. Project file is attached. The 'LogError' statement is hitting quite frequently for me.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class FallScript : MonoBehaviour
    6. {
    7.     public float minV = 1;
    8.     public float maxV = 5;
    9.  
    10.     private Vector3 _startPos;
    11.     private Vector2 _velocity;
    12.  
    13.     private Collider2D[] _overlapBuffer = new Collider2D[5];
    14.  
    15.     // Start is called before the first frame update
    16.     void Start()
    17.     {
    18.         Physics2D.autoSyncTransforms = true;
    19.         _startPos = transform.position;
    20.         _velocity = new Vector2(0, -Random.Range(minV, maxV));
    21.     }
    22.  
    23.     // Update is called once per frame
    24.     void Update()
    25.     {
    26.         int numOverlaps = GetComponent<Collider2D>().OverlapCollider(new ContactFilter2D(), _overlapBuffer);
    27.  
    28.         for (int i = 0; i < numOverlaps; i++)
    29.         {
    30.             Collider2D overlapCollider = _overlapBuffer[i];
    31.             if (!overlapCollider.bounds.Intersects(GetComponent<Collider2D>().bounds))
    32.             {
    33.                 Debug.LogError($"At velocity {_velocity}, {gameObject.name} overlaps collider {overlapCollider.name} when bounds do not intersect. Y position: {transform.position.y}, other Y Position: {overlapCollider.transform.position.y}");
    34.             }
    35.             else
    36.             {
    37.                 transform.position = _startPos;
    38.             }
    39.         }
    40.  
    41.         transform.position += (Vector3)(_velocity * Time.deltaTime);
    42.     }
    43. }
    44.  
    Every single collider in the scene has a size y component of 1, and these are the kinds of things I'm seeing in the error logs with the above statement:

    At velocity (0.00, -3.19), FallSquare (2) overlaps collider Square when bounds do not intersect. Y position: -3.304479, other Y Position: -4.32
     

    Attached Files:

  4. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,495
    Unfortunately this is ill informed. You mean a Rigidbody2D that is a Dynamic body-type which means that you'll get an automatic collision response. If you don't want this then you simply change it to use Kinematic body-type and you can use velocity or MovePosition/MoveRotation to move it. It doesn't respond to any collisions, gravity, user-forces etc.

    A scene full of Kinematic Rigidbody2D is all you need if you don't want any collision response. The physics engine doesn't modify them at all, it won't have any effect on you apart from giving you better performance.

    If you don't add a Rigidbody2D then the Collider is implicitly Static meaning you don't have a way to "move" it. You can change the Transform but this means the collider is recreated at the new position but that's expensive and doesn't scale well and you should never do this.

    If you want to avoid this expensive recreation cost then in the very least add a Rigidbody2D and set its body-type to Static. You say no body-type works but so you know, this cannot be true. Any collider and its shapes are ALWAYS created against a body, it's how it works internally so when you don't add a Rigidbody2D, it's implicitly created against the hidden Static Rigidbody2D that lives at the world origin.

    I suspect your issue is related to you modifying the Transform, it not happening there and then and then trying to sync it all up or something. There's simply no way to tell from here.

    If you have a simple reproduction project then please attach it and I'd be more than happy to check for you. Alternately, DM me with your email and I'll set-up a workspace for you to upload to. Please make it a simple project though.
     
  5. PanopticMedia

    PanopticMedia

    Joined:
    Jul 4, 2019
    Posts:
    5
    Yes, I've used the Kinematic body types as well. My memory of them is that they met almost all of my requirements for update timing, but I also remember them not always adjudicating collisions in a way that worked for the specifics of my project. Would be happy to mess around with them again though to see if they can at least save me a few of the things I'm doing manually, and if what you're saying is true, there's probably just some configuration detail I was missing before. Can you see the .zip file I attached above? It has an extremely simple test scene in it.

    Also just tried same script in super simple test scene modifying RigidBody2D's position rather than the transform. Bad news: same problem. Attached that version on this message too.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class FallScript : MonoBehaviour
    6. {
    7.     public float minV = 1;
    8.     public float maxV = 5;
    9.  
    10.     private Vector3 _startPos;
    11.     private Vector2 _velocity;
    12.  
    13.     private Collider2D[] _overlapBuffer = new Collider2D[5];
    14.  
    15.     // Start is called before the first frame update
    16.     void Start()
    17.     {
    18.         var body = GetComponent<Rigidbody2D>();
    19.         _startPos = body.position;
    20.         _velocity = new Vector2(0, -Random.Range(minV, maxV));
    21.     }
    22.  
    23.     // Update is called once per frame
    24.     void Update()
    25.     {
    26.         int numOverlaps = GetComponent<Collider2D>().OverlapCollider(new ContactFilter2D(), _overlapBuffer);
    27.         var body = GetComponent<Rigidbody2D>();
    28.  
    29.         for (int i = 0; i < numOverlaps; i++)
    30.         {
    31.             Collider2D overlapCollider = _overlapBuffer[i];
    32.             if (!overlapCollider.bounds.Intersects(GetComponent<Collider2D>().bounds))
    33.             {
    34.                 Debug.LogError($"At velocity {_velocity}, {gameObject.name} overlaps collider {overlapCollider.name} when bounds do not intersect. Y position: {body.position.y}, other Y Position: {overlapCollider.attachedRigidbody.position.y}");
    35.             }
    36.             else
    37.             {
    38.                 body.position = _startPos;
    39.             }
    40.         }
    41.  
    42.         body.MovePosition(body.position + (_velocity * Time.deltaTime));
    43.     }
    44. }
    45.  
     

    Attached Files:

  6. PanopticMedia

    PanopticMedia

    Joined:
    Jul 4, 2019
    Posts:
    5
    To further illustrate, with the script above, I put in a Debug.Break() call when the overlap was detected but a bounds intersection was not. Then I zoomed way in on the boxes in question and displayed their colliders. The error is about .02, not nearly small enough to be a floating point precision error. Image attached.
     

    Attached Files:

  7. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,495
    So the bounds are an exact outline of the AABB of the collider. This isn't exactly where the physics engine detects contacts, that's expanded by Physics2D.defaultContactOffset (defaults to 0.01).

    You can use Bounds.Expand to expand it to include this by adding twice the above amount so it's expanded by that amount in all directions. Note you'll need to do this on both colliders so either do it on both bounds or just add it x4 as I show on the code below. I've modified your script to show a few others things such as not needing to call move-position (you can set velocity), using "NoFilter()" on the ContactFilter and storing the RB/Collider, returning after you change the body position etc but the main part is the expansion on line 37 below:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class FallScript : MonoBehaviour
    6. {
    7.     public float minV = 1;
    8.     public float maxV = 5;
    9.  
    10.     private Vector3 _startPos;
    11.     private Vector2 _velocity;
    12.  
    13.     private Rigidbody2D localBody;
    14.     private Collider2D localCollider;
    15.  
    16.     private Collider2D[] _overlapBuffer = new Collider2D[5];
    17.  
    18.     // Start is called before the first frame update
    19.     void Start()
    20.     {
    21.         localBody = GetComponent<Rigidbody2D>();
    22.         _startPos = localBody.position;
    23.         _velocity = new Vector2(0, -Random.Range(minV, maxV));
    24.         localBody.velocity = _velocity;
    25.  
    26.         localCollider = GetComponent<Collider2D>();
    27.     }
    28.  
    29.     // Update is called once per frame
    30.     void Update()
    31.     {
    32.         var localColliderBounds = localCollider.bounds;
    33.  
    34.         // Expand the bounds due to the contact offset.
    35.         // Both colliders technically need this but we can add it here.
    36.         // It's 4 because expand needs to be expanded in all directions for both colliders.
    37.         localColliderBounds.Expand(Physics2D.defaultContactOffset * 4f);
    38.  
    39.         int numOverlaps = localCollider.OverlapCollider(new ContactFilter2D(), _overlapBuffer);
    40.  
    41.         for (int i = 0; i < numOverlaps; i++)
    42.         {
    43.             Collider2D overlapCollider = _overlapBuffer[i];
    44.             if (!overlapCollider.bounds.Intersects(localColliderBounds))
    45.             {
    46.                 Debug.LogError($"At velocity {_velocity}, {gameObject.name} overlaps collider {overlapCollider.name} when bounds do not intersect. Y position: {localBody.position.y}, other Y Position: {overlapCollider.attachedRigidbody.position.y}");
    47.             }
    48.             else
    49.             {
    50.                 localBody.position = _startPos;
    51.                 return;
    52.             }
    53.         }
    54.     }
    55. }
    56.  
    Note that none of this is to do with using a Rigidbody2D or not. You should always use one when moving. Use the body-type to select which features you do and don't want. Again, not using even a Static one will cause you performance issues.

    Also, so you can see that using a Kinematic body can have its advantages, I modified the project and uploaded it here to show you that you can turn on "useFullKinematicContacts" and then simply ask if something is touching a specific collider, layer, certain angles etc.

    Here's the attached project code:
    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class FallScript : MonoBehaviour
    4. {
    5.     public float minV = 1;
    6.     public float maxV = 5;
    7.     public LayerMask HitLayers;
    8.  
    9.     private Vector3 _startPos;
    10.     private Rigidbody2D localBody;
    11.  
    12.     void Start()
    13.     {
    14.         localBody = GetComponent<Rigidbody2D>();
    15.         _startPos = localBody.position;
    16.         localBody.velocity = new Vector2(0, -Random.Range(minV, maxV));
    17.     }
    18.  
    19.     void Update()
    20.     {
    21.         // Could call this on the collider if we wanted. This just checks all attached colliders.
    22.         // Can also pass a ContactFilter2D if we wanted to check things like contact direction etc.
    23.         if (localBody.IsTouchingLayers(HitLayers))
    24.             localBody.position = _startPos;
    25.     }
    26. }
    27.  
     

    Attached Files:

  8. PanopticMedia

    PanopticMedia

    Joined:
    Jul 4, 2019
    Posts:
    5
    The bounds don’t really matter to me, I was just using them to test if the colliders should actually overlap. It sounds like the answer to my original question “Is this by design?” Is actually “Yes.” So OverlapCollider doesn’t test if colliders overlap, it tests if the colliders expanded by some offset overlap.

    Rigidbody2d is not the substance of the question, I only brought it up because you asked why I was using autoSyncTransforms. I’ll take another look at using the kinematic ones, but as you said, has nothing to do with the issue at hand.

    IsTouchingLayers is insufficient because I need to know if I’m already overlapping a collider or not on a platform that disappears and reappears, so I need to selectively ignore collision on said platform. I could potentially do this with events, but I use collider.Cast to find upcoming collisions and need to filter those collisions by known overlaps. Another part of the issue is that collider.Cast doesn't use this defaultContactOffset (at least a simple test seems to confirm that it does not).

    It strikes me as wrong that an 'Overlap' function would use this contact offset because I'm specifically trying to determine if there is any space in the 'union' area of any collider with the one I'm calling the function on, and you seem to have conceded that yourself in your first message :). At the very least it would be useful to be able to remove or ignore the contact offset for a call such as this.
     
  9. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,495
    You can reduce the default contact offset to near-zero but not zero though. Even if it were zero, you would find that you'd still get the occasional discrepancy due to floating-point precision when comparing bounds. In other words, you should always expand the bounds by a tiny amount anyway so I'd suggest turning this option to near zero then when you get a bounds, expand it by a tiny amount and it'd work. I did this trivial on your project and it works all the time.

    Code (CSharp):
    1. var bounds = GetComponent<Collider2D>().bounds;
    2. bounds.Expand(0.001f);
    To be clear, that's not how it works internally although I appreciate the end result is similar; the colliders themselves are not expanded, the broadphase detects theirs proximity and uses the contact-offset only as a margin for polygons when checking the distance of contact points for stabliity. It doesn't do this for circle primitives, only polygon primitives. This is why it's not "by design", what it does is very specific and only applies to certain shapes.

    The physics queries are used to ask if the physics system considers them overlapping, not as a pure geometric intersection test (which as I understand is what you want) so what you get returned here is what the physics system will agree with when colliding. This is the same for all these queries including shape cast queries so not sure why you're not seeing the same there; perhaps you tried using a circle shape, not sure. FYI: this isn't a Unity thing, it's a pure Box2D thing and it's just how it works.

    Agreed, that's a potentially useful feature but would also require a fair few changes to be consistent across all queries. I've put some info about that in the backlog for consideration in the next round of R&D.

    I know the above tech-speak doesn't directly help you at hand but I try to clarify exactly what's going on behind the scenes for you and other readers who come across this thread.