Search Unity

  1. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Question How to get the contact points of the Rigidbody2Ds?

Discussion in 'Physics' started by djnX, Sep 17, 2023.

  1. djnX

    djnX

    Joined:
    Aug 6, 2019
    Posts:
    11
    In Unity Physics2D, It's able to get contact points in OnCollisionEnter2D().
    Code (CSharp):
    1. public void OnCollisionEnter2D(Collision2D collision)
    2. {
    3.     var contacts = collision.contacts;
    4.     foreach (var contact in contacts)
    5.     {
    6.         print(contact.point);
    7.     }
    8. }
    But the contact points is in world space, not the positions of the Rigidbody2Ds.
    Someone may say, a point in world space can be converted to local space by `Transform.InverseTransformPoint()`, and that is the position of the Rigidbody2D.
    But After some experiments, I found that only using `Transform.InverseTransformPoint()` would not get the precise position.

    Let me explain.



    In Unity's Order of execution for event functions, FixedUpdate, Internal Physics Update, OnCollisionEnter2D happens in order.
    Assume there are 2 small boxes, both have bounceness = 1. So after collision they will go apart.
    In FixedUpdate, they are coming near.
    In Internal Physics Update, they collide with each and then go apart. The contact pointis the real position they collider in world space.
    In OnCollisionEnter2D, they are no longer at the collision position, but at the position some distance apart from the collision position. So if I calculated the position by `Transform.InverseTransformPoint()` here, I would get a wrong answer.
    Example above neglects anglar velocity, if there are non-zero anglar velocities, things will be more complicated.

    So, how to get the contact points of the Rigidbody2Ds?
     
  2. djnX

    djnX

    Joined:
    Aug 6, 2019
    Posts:
    11
  3. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,105
    Unfortunatley, your diagram above isn't correct based upon your conclusions.

    The fault in your thinking is simply that, for some reason, you expect the Rigidbody2D to still be at the contact point even though you've clearly asked that it bounce (fully). You must know that this is a contradiction of expectation.

    The callbacks are there to tell you that you came into contact but in the case of a Rigidbody2D that is moving or bouncing (etc) you cannot expect it to still be there. In short, Physics has already solved the contacts it's reporting so expecting them to be at the contact point isn't correct. This is pretty standard whether it be Box2D/PhysX etc.

    Bouncing away from where you contacted is how it solves it in your case of Bounce=1. Also, the contact point isn't simply where it came into contact too; it's to tell you where the impulse was applied. This might be on the surface of a collider (assuming you're using continuous collision detection) or it might be inside colliders such as when using discrete collision detection.

    Also, ignore the FixedUpdate, "internal physics update" and callback stuff. None of that applies to your discussion here. When the simulation runs, it does everything all together as does 3D physics; it creates/updates contacts, integrates velocities, solves contacts and performs the callbacks all in one step; doesn't matter if Unity calls the simulation step in FixedUpdate/Update or you call it manually.

    In short, the contacts are created/update then solved. Solving a contact doesn't mean it'll still be at the point it contacted; that would mean it couldn't ever move.

    It's no different than how do you transform any point into world-space into some other local-space so the most basic way would be to simply inverse position/rotation it into the Rigidbody2D space. Be careful using the Transform as that will include scaling if you're using it which won't work. The only degrees of freedom here are XY position and Z rotation so if your Transform has anything beyond that, it won't work.

    If you're using 2023.1 onwards then you can use the inverse of: https://docs.unity3d.com/2023.1/Documentation/ScriptReference/Rigidbody2D-localToWorldMatrix.html

    Note if you're using a Unity version before this you can still create a Matrix4x4 using the Rigidbody2D position and the Z rotation and inverse it; that's all the call above does.
     
  4. djnX

    djnX

    Joined:
    Aug 6, 2019
    Posts:
    11
    Thanks, these really helps me understand how the contact work.
     
  5. djnX

    djnX

    Joined:
    Aug 6, 2019
    Posts:
    11
    Does it mean that:
    There is no way to get the position when the contacts are created.
    What I can get, is only the positon sovled.
     
  6. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,105
    That's not something that is passed back for 2D/3D. In theory this could be calculated and passed back as part of the ContactPoint2D structure but it isn't there now.

    The only time this is typically used is when using 2D physics queries for Kinematic motion wwhich is why the RaycastHit2D has a centroid property which returns just that, so if you were to cast the collider using the body-velocity direction with a distance of the time-step, it'd give you that.

    Here's an example of how to cast a collider or the whole collider and get the "centroid" (position at point of contact) given your situation above. Note here that I'm asuming that the collider is positioned at the body position. The example I just typed-up also shows you both the body and collider cast methods but any cast method such as BoxCast would work fine too.

    You would do this before the simuation step.

    Hope this helps.

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3.  
    4. public class CastShapeCentroid : MonoBehaviour
    5. {
    6.     public ContactFilter2D Filter;
    7.  
    8.     private Collider2D m_Collider;
    9.     private Rigidbody2D m_Rigidbody;
    10.     private readonly List<RaycastHit2D> m_Results = new ();
    11.  
    12.     void Start()
    13.     {
    14.         m_Rigidbody = GetComponent<Rigidbody2D>();
    15.         m_Collider = GetComponent<Collider2D>();
    16.     }
    17.  
    18.     void Update()
    19.     {
    20. #if true
    21.         // Use the specific collider to query.
    22.         if (m_Collider.Cast(m_Rigidbody.velocity, Filter, m_Results, Time.fixedDeltaTime) == 0)
    23.             return;
    24. #else
    25.         // Use the whole rigidbody to query.
    26.         if (m_Rigidbody.Cast(m_Rigidbody.velocity, Filter, m_Results, Time.fixedTime) == 0)
    27.             return;
    28. #endif
    29.         Debug.Log(m_Results[0].centroid);
    30.     }
    31. }
    32.  
     
  7. djnX

    djnX

    Joined:
    Aug 6, 2019
    Posts:
    11
    Thanks, it helps.

    I know about Raycast and have used it several times. But I have no experience with Rigidbody.Cast and Collider.Cast.

    It is not clear to me what does the centroid property of RaycastHit2D do, and this really helps me understanding.
     
  8. MelvMay

    MelvMay

    Unity Technologies

    Joined:
    May 24, 2013
    Posts:
    11,105
    Yes, it's the point where the shape is when it hits. The "point" is the point of contact, "centroid" is where the shape is.

    Imagine a CircleCollider2D of radius 1 being cast rightwards towards a BoxCollider2D. Let's say the hit point you hit is at (3,0), this would mean the centroid would be (2,0) i.e. where the Circle was when it hit. This is exactly what you asked for.

    If you look at the docs, there's a code example to dfemonstrate it visiually: https://docs.unity3d.com/ScriptReference/RaycastHit2D-centroid.html

    As the docs state, when you use this with a Line/Raycast then the "point" and "centroid" are the same because a line/ray has no area. A circle is offset by its radius, a box by its size etc.

    You don't need experience with them, they're pretty simple calls with docs to either cast all the colliders attached to the Rigidbody2D or cast a specific collider respectively.

    Using Collider2D.Cast means you don't need to know what shape it is, it'll do any collider, even a TilemapCollider2D if you were crazy enough. :) Far too many bad tutorials tell users to create a collider such as a Box then they perform a Physics2D.BoxCast by reading the collider position and size and using it in a query which is crazy; use Collider2D.Cast!