Search Unity

what does collisionEvents.Normal returns?

Discussion in 'Physics for ECS' started by Conspiracy, Jan 17, 2020.

  1. Conspiracy


    Oct 22, 2016
    I'm curious about this because I need to get the face normal directions of the collider that touches my player object, which is a sphere.

    I need it for the controls. So what does it returns?

    does it return the direction of the contact points between entityA and entityB?
  2. MaxAbernethy


    Mar 16, 2019
    Collision detection finds the closest points on the two colliders. The normal is the direction from the point on collider B to the point on collider A. Another way to think about it is, if you want to increase the distance from A to B as fast as possible, you should move A in the direction of the normal. Mathematically, it's the normalized gradient of the distance between the colliders as a function of their relative translation.

    If your sphere is colliding with a box for example, the closest point might be on a face of the box, in which case the collision normal will be the face normal. But the closest point might be on an edge or corner of the box instead. Here is an example:
    The closest points are in red and the normals are in green.
    If you want a face normal, you could use ConvexHull.GetSupportingFace(). This function looks at all of the faces containing the closest point and returns the one whose normal is closest to the collision normal.
    Last edited: Jan 17, 2020
    NotaNaN, Conspiracy and steveeHavok like this.
  3. mpforce1


    Apr 4, 2014
    Hi, do you know how GetSupportingFace is supposed to be used? For my use case I'm trying to find the the face normal for a collision in an attempt to stop phantom collisions with collider edges. I thought I might be able to use this to figure that out but I keep getting face normals that are really small, I had thought that normals would have a magnitude of 1.

    In particular, once I find the colliders involved, I pass the contact normal given by the ModifiableContactHeader.Normal from an IContactsJob to the respective ConvexHull.GetSupportingFace() method. The small normals I'm getting don't work when used in the ModifyNormalsJob in because the dot product always returns some small number which basically results in objects falling through the colliders.

    In addition, the ConvexHull type is internal to the physics package. I've solved this for now by modifying the source code to make it visible, however this obviously isn't a great solution.

    Any help would be appreciated.
  4. MaxAbernethy


    Mar 16, 2019
    Hi, GetSupportingFace() expects a direction in the ConvexHull's local space and, optionally, an index into ConvexHull.Vertices. From ModifiableContactHeader, the direction should just be the normal rotated into local space (and negated if you're getting a face from body A), but the vertex index is tricky, you might try without it to begin with. GetSupportingFace() returns an index into ConvexHull.Planes. ConvexHull.Planes[GetSupportingFace()].xyz is the face normal and should always be unit length. You can see some examples of usage in ConvexConvexManifold.cs.
  5. mpforce1


    Apr 4, 2014
    Thanks, I've rotated the contact normal into the plane's local space by multiplying the normal by the plane's orientation. However, the plane isn't actually rotated so the resultant normal is the same.

    Regarding the plane normals being a unit length, when I loop over all of the Planes exposed by the ConvexHull I find that there are 2 and they both have weird normals like (0f, -4.169755E-07f, 1.485376E-43f) and (0f, 0f, -4.169755E-07f). Additionally, the values seem to change slightly each time I check. I'm not sure why there are 2, this is a plane converted by the normal physics shape authoring into a PolygonCollider. I thought it could be 2 triangles instead of a single quad but it is a single quad with 2 planes. But still, the normals should have length 1, right? Furthermore, I thought that the normals of both planes should just be (0, 1, 0) since it's a flat plane.
  6. MaxAbernethy


    Mar 16, 2019
    Sounds strange, I think the elements of Planes should all have unit length xyz. Could be a bug, or it could be a problem on your side, eg. if you copied a collider by value then you get garbage. That's a tricky thing about colliders, if you for example make a function like void foo(ConvexCollider c) it will not work, because the collider is passed by value.

    If that's not it could you share your code?
    steveeHavok likes this.
  7. mpforce1


    Apr 4, 2014
    It would make sense if I was getting garbage considering the weird values I'm seeing. Can I just ask if I can pass around a pointer, i.e. Collider*, by value? For example I do:

    Code (CSharp):
    1. private unsafe bool TryFindSurfaceNormalFromConvexHull(
    2.     Collider* collider,
    3.     ColliderKey colliderKey,
    4.     float3 contactNormal,
    5.     out float3 surfaceNormal
    6. ) {
    I haven't worked with pointers before this so it's a bit new to me.
  8. MaxAbernethy


    Mar 16, 2019
    Passing pointers is fine, it's only passing the struct itself that won't work.
  9. mpforce1


    Apr 4, 2014
    Code (CSharp):
    1.     private struct ModifyNormalsJob : IContactsJob {
    2.         [ReadOnly]
    3.         public PhysicsWorld PhysicsWorld;
    4.         [ReadOnly]
    5.         public ComponentDataFromEntity<ModifyNarrowphaseContacts> ModifyNarrophaseContacts;
    7.         private float distanceScale;
    9.         public void Execute(ref ModifiableContactHeader contactHeader, ref ModifiableContactPoint contactPoint) {
    10.             var updateNormal = TryFindSurfaceDataByConvexHull(ref contactHeader, out var surfaceNormal);
    11.             if (updateNormal) {
    12.                 if (contactPoint.Index == 0) {
    13.                     var newNormal = surfaceNormal;
    14.                     distanceScale =, contactHeader.Normal);
    15.                     contactHeader.Normal = newNormal;
    16.                 }
    17.                 contactPoint.Distance *= distanceScale;
    18.             }
    19.         }
    21.         private readonly struct IndexAndKey {
    22.             public readonly int BodyIndex;
    23.             public readonly ColliderKey ColliderKey;
    24.             public IndexAndKey(int bodyIndex, ColliderKey colliderKey) {
    25.                 BodyIndex = bodyIndex;
    26.                 ColliderKey = colliderKey;
    27.             }
    28.         }
    30.         private unsafe bool TryFindSurfaceDataByConvexHull(
    31.             ref ModifiableContactHeader contactHeader,
    32.             out float3 surfaceNormal
    33.         ) {
    34.             IndexAndKey? indexAndKey = default;
    35.             if (ModifyNarrophaseContacts.HasComponent(contactHeader.Entities.EntityA)) {
    36.                 indexAndKey = new IndexAndKey(
    37.                     contactHeader.BodyIndexPair.BodyAIndex,
    38.                     contactHeader.ColliderKeys.ColliderKeyA
    39.                 );
    40.             } else if (ModifyNarrophaseContacts.HasComponent(contactHeader.Entities.EntityB)) {
    41.                 indexAndKey = new IndexAndKey(
    42.                     contactHeader.BodyIndexPair.BodyBIndex,
    43.                     contactHeader.ColliderKeys.ColliderKeyB
    44.                 );
    45.             }
    47.             if (indexAndKey.HasValue) {
    48.                 var contactNormalInColliderSpace = math.mul(
    49.                     PhysicsWorld.Bodies[indexAndKey.Value.BodyIndex].WorldFromBody.rot,
    50.                     contactHeader.Normal
    51.                 );
    52.                 return TryFindSurfaceNormalFromConvexHull(
    53.                     PhysicsWorld.Bodies[indexAndKey.Value.BodyIndex].Collider,
    54.                     indexAndKey.Value.ColliderKey,
    55.                     contactNormalInColliderSpace,
    56.                     out surfaceNormal
    57.                 );
    58.             } else {
    59.                 surfaceNormal = default;
    60.                 return false;
    61.             }
    62.         }
    64.         private unsafe bool TryFindSurfaceNormalFromConvexHull(
    65.             Collider* collider,
    66.             ColliderKey colliderKey,
    67.             float3 contactNormal,
    68.             out float3 surfaceNormal
    69.         ) {
    70.             switch (collider->Type) {
    71.                 case ColliderType.Mesh: {
    72.                     var meshCollider = (MeshCollider*) collider;
    73.                     meshCollider->GetLeaf(colliderKey, out var childCollector);
    74.                     return TryFindSurfaceNormalFromConvexHull(
    75.                         childCollector.Collider,
    76.                         colliderKey,
    77.                         contactNormal,
    78.                         out surfaceNormal
    79.                     );
    80.                 }
    81.                 case ColliderType.Compound: {
    82.                     var compoundCollider = (CompoundCollider*) collider;
    83.                     compoundCollider->GetLeaf(colliderKey, out var childCollider);
    84.                     return TryFindSurfaceNormalFromConvexHull(
    85.                         childCollider.Collider,
    86.                         colliderKey,
    87.                         contactNormal,
    88.                         out surfaceNormal
    89.                     );
    90.                 }
    91.                 default: {
    92.                     return TryFindSurfaceNormalFromConvexHull(collider, contactNormal, out surfaceNormal);
    93.                 }
    94.             }
    95.         }
    97.         private static unsafe bool TryFindSurfaceNormalFromConvexHull(
    98.             Collider* collider,
    99.             float3 contactNormal,
    100.             out float3 surfaceNormal
    101.         ) {
    102.             ConvexHull convexHull;
    103.             switch (collider->Type) {
    104.                 case ColliderType.Convex:
    105.                     convexHull = ((ConvexCollider*) collider)->ConvexHull;
    106.                     break;
    107.                 case ColliderType.Box:
    108.                     convexHull = ((BoxCollider*) collider)->ConvexHull;
    109.                     break;
    110.                 case ColliderType.Cylinder:
    111.                     convexHull = ((CylinderCollider*) collider)->ConvexHull;
    112.                     break;
    113.                 case ColliderType.Capsule:
    114.                     convexHull = ((CapsuleCollider*) collider)->ConvexHull;
    115.                     break;
    116.                 case ColliderType.Sphere:
    117.                     convexHull = ((SphereCollider*) collider)->ConvexHull;
    118.                     break;
    119.                 case ColliderType.Quad:
    120.                 case ColliderType.Triangle:
    121.                     convexHull = ((PolygonCollider*) collider)->ConvexHull;
    122.                     break;
    123.                 default:
    124.                     surfaceNormal = default;
    125.                     return false;
    126.             }
    128.             var supportingFaceIndex = convexHull.GetSupportingFace(contactNormal);
    129.             var plane = convexHull.Planes[supportingFaceIndex];
    131.             for (var planeIndex = 0; planeIndex < convexHull.NumFaces; planeIndex++) {
    132.                 Debug.Log($"Plane {planeIndex} Normal: {convexHull.Planes[planeIndex].Normal}");
    133.             }
    135.             surfaceNormal = plane.Normal;
    136.             return true;
    137.         }
    138.     }
    That's what I'm currently working with. When a dynamic entity, in this case a sphere, collides with an entity tagged with ModifyNarrowphaseContacts I am trying to modify the collision so that it uses a face normal instead of an edge normal. I think that should work based on the physics sample I linked, although I haven't got far enough to properly test it.

    I believe I'm doing the right thing with the ColliderKey here, at any rate I'm not actually hitting that code path in my test case since the plane is just a Quad rather than a Mesh or Compound collider. But in the future I'd like this to work with meshes.

    Please let me know if I'm making any mistakes here.
  10. MaxAbernethy


    Mar 16, 2019
    Like Collider, ConvexHull can't be copied by value, you have to pass around pointers/refs instead, so the copy in the last overload of TryFindSurfaceNormalFromConvexHull() is the problem.

    It is safe to cast any Collider* with CollisionType.Convex to ConvexCollider*. So you can remove the switch statement and just do ConvexCollider* convexCollider = (ConvexCollider*)collider; convexCollider->ConvexHull.GetSupportingFace(); etc.

    I also noticed while reading your code, you are doing contactNormalInColliderSpace = WorldFromBody.rot * contactHeader.Normal. That should be WorldFromBody.rot.inverse(), because contactHeader.Normal is already in world space, and you are trying to rotate it into body space. Also, if you are getting the surface normal on body A, you should negate the normal, since it points away from B and towards A. GetSupportingFace() returns the face whose normal is closest to the direction you pass in, and face normals point outwards from the shape, so if you are looking for a face normal from A that points towards B, you need to pass in a direction that points from A to B.

    Hope that helps
  11. mpforce1


    Apr 4, 2014
    Thank you so much. That was the problem, I didn't even think about the fact that I was making a local copy of the convex hull.